Swift Algorithms y Collections: chunked, windows, product, Deque y OrderedDictionary

La librería estándar de Swift es sólida, pero hay operaciones que aparecen constantemente en código real y que no están incluidas: partir un array en grupos de N elementos, generar la ventana deslizante sobre una secuencia, obtener todas las combinaciones de un conjunto, o una estructura de cola eficiente. Apple mantiene dos paquetes oficiales que añaden exactamente eso: swift-algorithms y swift-collections.

Swift Algorithms: operaciones sobre secuencias

El paquete swift-algorithms añade más de 40 algoritmos sobre Sequence y Collection. Los más usados:

chunked: dividir en grupos

import Algorithms

let numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Grupos de tamaño fijo
let grupos = numeros.chunks(ofCount: 3)
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

// Grupos según un criterio de cambio
let letras = ["a", "a", "b", "b", "b", "c", "a"]
let porLetra = letras.chunked(by: { $0 == $1 })
// [["a", "a"], ["b", "b", "b"], ["c"], ["a"]]

// Por clave
let palabras = ["hola", "hey", "adios", "ala"]
let porInicial = palabras.chunked(on: { $0.first! })
// [["hola", "hey"], ["adios", "ala"]]

windows: ventana deslizante

let precios = [10.0, 12.0, 11.5, 13.0, 14.5, 13.0]

// Media móvil de 3 elementos
let mediaMovil = precios.windows(ofCount: 3).map { ventana in
    ventana.reduce(0, +) / Double(ventana.count)
}
// [11.17, 12.17, 13.0, 13.5]

// Detectar tendencias
for ventana in precios.windows(ofCount: 2) {
    let tendencia = ventana[ventana.startIndex] < ventana[ventana.index(after: ventana.startIndex)]
    print(tendencia ? "sube" : "baja")
}

product: producto cartesiano

let colores = ["rojo", "verde", "azul"]
let tallas = ["S", "M", "L", "XL"]

for (color, talla) in product(colores, tallas) {
    print("(color)-(talla)")
}
// rojo-S, rojo-M, rojo-L, rojo-XL, verde-S, ...

// Generar todas las combinaciones de parámetros para tests:
let bases = [2, 8, 10, 16]
let exponentes = [0, 1, 2, 3, 4]
let casosTest = Array(product(bases, exponentes))

combinations y permutations

let elementos = [1, 2, 3, 4]

// Combinaciones de 2 (sin importar orden, sin repetición)
for combo in elementos.combinations(ofCount: 2) {
    print(combo) // [1,2], [1,3], [1,4], [2,3], [2,4], [3,4]
}

// Permutaciones de 3
for perm in elementos.permutations(ofCount: 3) {
    print(perm) // todas las permutaciones de 3 elementos
}

// Caso real: generar todas las contraseñas posibles de longitud N para auditoría
let caracteres = Array("abcdefgh")
let contraseñas = caracteres.permutations(ofCount: 4).prefix(100)

Otros algoritmos útiles

// firstNonNil: el primer resultado no nil de un transform
let strings = ["uno", "2", "tres", "4", "5"]
let primeroNumero = strings.firstNonNil { Int($0) } // 2

// minAndMax: mínimo y máximo en una sola pasada
let (min, max) = numeros.minAndMax()!

// uniqued: eliminar duplicados preservando orden
let conDuplicados = [1, 3, 2, 1, 4, 3, 5]
let sinDuplicados = conDuplicados.uniqued() // [1, 3, 2, 4, 5]

// Con clave personalizada
let usuarios = [Usuario(id: 1, nombre: "Ana"), Usuario(id: 2, nombre: "Ana")]
let usuariosUnicos = usuarios.uniqued(on: .nombre) // solo Ana (el primero)

// interspersed: insertar separador entre elementos
let partes = ["uno", "dos", "tres"]
let conComas = partes.interspersed(with: ", ") // ["uno", ", ", "dos", ", ", "tres"]

Swift Collections: estructuras de datos avanzadas

El paquete swift-collections añade estructuras de datos que la librería estándar no incluye por razones de scope.

Deque: cola de doble extremo

Deque (double-ended queue) permite inserciones y eliminaciones eficientes tanto por el principio como por el final, a diferencia de Array que es lento en el principio:

import Collections

var cola: Deque = [1, 2, 3, 4, 5]

// O(1) en ambos extremos (Array.prepend es O(n))
cola.prepend(0)           // [0, 1, 2, 3, 4, 5]
cola.append(6)            // [0, 1, 2, 3, 4, 5, 6]
let primero = cola.popFirst() // 0
let ultimo = cola.popLast()   // 6

// Implementar un historial con límite de tamaño
var historial: Deque<String> = []
let maxHistorial = 50

func registrar(_ accion: String) {
    historial.append(accion)
    if historial.count > maxHistorial {
        historial.removeFirst()
    }
}

OrderedDictionary: diccionario con orden garantizado

var config: OrderedDictionary = [
    "host": "localhost",
    "port": "5432",
    "database": "produccion",
    "ssl": "true"
]

// A diferencia de Dictionary, el orden de inserción se preserva
for (clave, valor) in config {
    print("(clave)=(valor)") // Siempre en orden host, port, database, ssl
}

// Acceso por índice (imposible con Dictionary)
print(config.elements[0]) // ("host", "localhost")
config.move(fromIndex: 3, toIndex: 0) // Mueve "ssl" al principio

// Útil para formularios donde el orden de campos importa
var campos: OrderedDictionary<String, String> = [:]
campos["nombre"] = ""
campos["apellidos"] = ""
campos["email"] = ""

OrderedSet: conjunto con orden

var etiquetas: OrderedSet = ["swift", "ios", "programacion", "apple"]

etiquetas.insert("swiftui")     // Se añade al final
etiquetas.insert("swift")       // Ya existe, no se añade
let primera = etiquetas[0]      // "swift" — acceso O(1) por índice
etiquetas.sort()                // Ordena manteniendo la unicidad

// Combinar conjuntos preservando orden
let etiquetasPost1: OrderedSet = ["swift", "ios"]
let etiquetasPost2: OrderedSet = ["ios", "macos", "swift"]
let todas = etiquetasPost1.union(etiquetasPost2) // ["swift", "ios", "macos"]

Heap: cola de prioridad

var colaPrioridad = Heap<Int>()
colaPrioridad.insert(5)
colaPrioridad.insert(3)
colaPrioridad.insert(8)
colaPrioridad.insert(1)

// Siempre extrae el mínimo (min-heap por defecto)
print(colaPrioridad.popMin()) // 1
print(colaPrioridad.popMin()) // 3

// Max-heap: el comparador invierte el orden
var tareasPrioridad = Heap<Tarea> { $0.prioridad > $1.prioridad }
tareasPrioridad.insert(Tarea(nombre: "Normal", prioridad: 1))
tareasPrioridad.insert(Tarea(nombre: "Urgente", prioridad: 10))
let siguiente = tareasPrioridad.popMax() // La de prioridad 10

Añadir los paquetes al proyecto

// Package.swift
dependencies: [
    .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
    .package(url: "https://github.com/apple/swift-collections", from: "1.1.0")
],
targets: [
    .target(
        name: "MiApp",
        dependencies: [
            .product(name: "Algorithms", package: "swift-algorithms"),
            .product(name: "Collections", package: "swift-collections")
        ]
    )
]

Resumen

swift-algorithms aporta operaciones de alto nivel sobre secuencias que evitan tener que implementar patrones comunes como chunked, windows o product, con implementaciones eficientes y bien testeadas. swift-collections llena el hueco de estructuras de datos que la librería estándar omite deliberadamente: Deque para colas eficientes, OrderedDictionary y OrderedSet cuando el orden importa, y Heap para colas de prioridad. Ambos son mantenidos por Apple y siguen la misma filosofía de la librería estándar.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP