Algoritmos y estructuras de datos en Swift: de la teoría a proyectos reales de iOS
\nSwift no es solo un lenguaje de programación moderno y seguro para el ecosistema Apple. Es una herramienta poderosa que permite escribir código eficiente y de alto rendimiento para iOS, macOS, watchOS y tvOS. Sin embargo, para crear aplicaciones verdaderamente rápidas y escalables, no basta con conocer la sintaxis. Comprender los algoritmos y estructuras de datos fundamentales es lo que diferencia a un desarrollador junior de un ingeniero senior.
\nEn este artículo, analizaremos las estructuras de datos y algoritmos clave en el lenguaje Swift con ejemplos prácticos. Verás cómo implementar una pila, una cola, la búsqueda binaria y la ordenación, y entenderás dónde se aplican estos conocimientos en el desarrollo real de aplicaciones iOS. ¡Comencemos!
\n\n1. Pila (Stack) y Cola (Queue): estructuras fundamentales
\nLa pila y la cola son dos estructuras de datos básicas que se utilizan con frecuencia en el desarrollo de iOS. Por ejemplo, la pila se usa para la navegación en UINavigationController, y la cola para gestionar tareas en OperationQueue o DispatchQueue.
Implementación de la pila (LIFO — Last In, First Out)
\nLa pila funciona bajo el principio "último en entrar, primero en salir". La adición de un elemento (push) y la eliminación (pop) ocurren desde un mismo extremo: la cima de la pila.
\nstruct Stack<Element> {\n private var storage: [Element] = []\n \n mutating func push(_ element: Element) {\n storage.append(element)\n }\n \n @discardableResult\n mutating func pop() -> Element? {\n return storage.popLast()\n }\n \n func peek() -> Element? {\n return storage.last\n }\n \n var isEmpty: Bool {\n return storage.isEmpty\n }\n}\n\n// Ejemplo de uso\nvar navigationStack = Stack<String>()\nnavigationStack.push("Pantalla principal")\nnavigationStack.push("Detalles del producto")\nnavigationStack.push("Carrito")\nprint(navigationStack.pop() ?? "") // Imprime: "Carrito"\nprint(navigationStack.peek() ?? "") // Imprime: "Detalles del producto"\nDónde se aplica: Deshacer operaciones (Undo/Redo), verificación de paréntesis en analizadores sintácticos, recorrido de árbol en profundidad (DFS).
\n\nImplementación de la cola (FIFO — First In, First Out)
\nLa cola funciona bajo el principio "primero en entrar, primero en salir". Los elementos se añaden al final (enqueue) y se eliminan del principio (dequeue).
\nstruct Queue<Element> {\n private var storage: [Element] = []\n \n mutating func enqueue(_ element: Element) {\n storage.append(element)\n }\n \n @discardableResult\n mutating func dequeue() -> Element? {\n guard !storage.isEmpty else { return nil }\n return storage.removeFirst()\n }\n \n func peek() -> Element? {\n return storage.first\n }\n \n var isEmpty: Bool {\n return storage.isEmpty\n }\n}\n\n// Ejemplo de uso\nvar taskQueue = Queue<String>()\ntaskQueue.enqueue("Carga de imágenes")\ntaskQueue.enqueue("Sincronización de datos")\ntaskQueue.enqueue("Envío de analíticas")\nprint(taskQueue.dequeue() ?? "") // Imprime: "Carga de imágenes"\nDónde se aplica: Procesamiento de solicitudes de red, recorrido de árbol en anchura (BFS), colas de tareas en hilos en segundo plano.
\n\n2. Búsqueda binaria: velocidad frente a simplicidad
\nLa búsqueda binaria es un algoritmo clásico que encuentra un elemento en un array ordenado en tiempo logarítmico O(log n). En iOS, se aplica a menudo al trabajar con grandes conjuntos de datos ordenados, como al buscar contactos o registros en una base de datos.
\nfunc binarySearch<T: Comparable>(in array: [T], target: T) -> Int? {\n var lowerBound = 0\n var upperBound = array.count - 1\n \n while lowerBound <= upperBound {\n let midIndex = (lowerBound + upperBound) / 2\n let midValue = array[mid