Ciencias de la computación

Pilas y colas: por qué Swift no tiene un tipo Stack

Una pila extrae datos en orden LIFO y una cola, en FIFO. Este artículo explica por qué Swift no incluye un Stack dedicado, cómo implementarlo con Array y la trampa de rendimiento de removeFirst en las colas.

5 min de lectura
Imagen de portada de Pilas y colas: por qué Swift no tiene un tipo Stack

Al empezar a estudiar estructuras de datos, los dos primeros hermanos que aparecen son la pila (Stack) y la cola (Queue).

Los conceptos se entienden en cinco minutos. Pero Swift plantea una duda: la biblioteca estándar tiene Array, Dictionary y Set, pero no tiene los tipos Stack ni Queue. Este artículo explica por qué y cómo trabajar sin ellos.

La trampa de rendimiento (removeFirst()) que aparece al imitar colas con arrays causa muchos timeouts en pruebas de programación, así que la tratamos aparte.

Aquí, pila se refiere a una estructura de datos, no a una zona de asignación de memoria. La diferencia entre ambos nombres se explica en Pila de memoria frente a heap; el siguiente paso, las colas con prioridad, aparece en Heap y cola de prioridad.


El concepto se reduce a platos y una fila

Una pila es LIFO (Last In, First Out). El último elemento añadido sale primero. Piensa en una pila de platos: push arriba y pop desde arriba. Sacar elementos del medio rompe las reglas.

Una cola es FIFO (First In, First Out). El primer elemento añadido sale primero. Es como una fila de caja: enqueue por detrás y dequeue por delante.

Cada una tiene solo dos operaciones.

Insertar Extraer Consultar
Pila push pop peek(top)
Cola enqueue dequeue front

¿Por qué importa? Porque esta regla simple sustenta muchas partes del desarrollo para iOS.

  • Pila de llamadas: las llamadas y retornos de funciones son exactamente push/pop. Cuando la recursión se hace profunda, el crash se llama stack overflow por algo.
  • UINavigationController: pushViewController / popViewController — las transiciones de pantalla son literalmente una pila.
  • Deshacer (undo): se apilan los comandos y se revierten en orden inverso.
  • DispatchQueue de GCD (Grand Central Dispatch): como indica su nombre, es una cola. Las tareas de una serial queue se ejecutan en el orden en que se añaden.
  • BFS/DFS: en un recorrido de grafos, una cola produce BFS (Breadth-First Search, búsqueda en anchura) y una pila produce DFS (Depth-First Search, búsqueda en profundidad). Elegir la estructura es elegir el algoritmo.

Por qué Swift no tiene Stack

La conclusión es simple: Array ya es una pila perfecta.

var stack: [Int] = []
stack.append(3)      // push — O(1)
stack.append(7)
let top = stack.last // peek — O(1)
let popped = stack.popLast() // pop — O(1), Si está vacío nil

Insertar y extraer al final del array cuesta O(1); más exactamente, amortized O(1), porque solo se paga la expansión cuando se llena el búfer interno. Un tipo separado solo envolvería Array, así que la biblioteca estándar decidió no añadirlo.

Si quieres dejar clara la intención, lo habitual es usar una envoltura fina.

struct Stack<Element> {
    private var storage: [Element] = []
    var isEmpty: Bool { storage.isEmpty }
    var top: Element? { storage.last }
    mutating func push(_ element: Element) { storage.append(element) }
    mutating func pop() -> Element? { storage.popLast() }
}

Bloquea subscript y fuerza mediante el tipo la regla de la pila: «prohibido acceder al medio».

Diagrama LIFO/FIFO en paralelo con los flujos push/pop de una pila y enqueue/dequeue de una cola
Basta recordar la dirección de inserción y extracción.

Crear una cola con un array es una trampa

Una cola construida con la misma lógica que una pila queda así.

var queue: [Int] = []
queue.append(1)              // enqueue — O(1), Sin problema
let first = queue.removeFirst() // dequeue — O(n), Aquí está la trampa

removeFirst() elimina el primer elemento y ** desplaza todos los demás una posición hacia delante.** Con 100.000 elementos, un dequeue implica 100.000 movimientos. Si se repite miles de veces, como en BFS, todo pasa a ser O(n²) y la prueba de programación termina en timeout.

Hay tres soluciones.

1. Avanzar el frente con un índice (la opción estándar en pruebas)

var queue: [Int] = []
var head = 0
// enqueue
queue.append(5)
// dequeue
let value = queue[head]
head += 1

En lugar de eliminar, mueve solo la posición de lectura. dequeue pasa a O(1); la memoria del prefijo usado permanece, pero en pruebas suele bastar.

2. Crear una cola con dos pilas (clásico de entrevistas)

Al insertar, apila en in. Al extraer, si out está vacío, pasa todo in invertido y haz pop. Cada elemento se mueve como máximo dos veces: amortized O(1). También es la respuesta estándar a «implementa una cola con pilas».

3. Deque de swift-collections (la opción profesional)

El paqueteswift-collections gestionado por Apple ofrece Deque, basado en un búfer circular, por lo que insertar y eliminar en ambos extremos cuesta O(1).

import DequeModule

var queue: Deque<Int> = []
queue.append(1)            // enqueue
let v = queue.popFirst()   // dequeue — O(1)

Su API es casi igual que la de Array, así que sustituirlo cuesta poco. En producción, usa esto cuando necesites una cola.


Criterios para elegir una u otra

Cuando dudes, basta una pregunta: «¿Debo procesar primero lo último que llegó o lo primero?»

  • Deshacer, comprobar paréntesis, retroceder por rutas visitadas y búsqueda en profundidad → lo más reciente primero → pila
  • Colas de trabajo, eventos, salida de impresora y búsqueda en anchura → respetar el orden de llegada → cola

Comprobar paréntesis es un uso clásico de las pilas: haz push al abrir, pop al cerrar y verifica la pareja. Si al final la pila está vacía, la expresión es válida. Los compiladores comprueban las llaves con el mismo principio.

Ilustración de una cola con cinta transportadora que muestra la trampa O(n) de removeFirst en un array de Swift
Cada extracción mueve una posición hacia delante todo lo que queda detrás.

Resumen

  • La pila es LIFO y la cola FIFO: con platos y una fila de caja basta para entenderlo.
  • La pila de llamadas, la pila de navegación y undo son pilas; DispatchQueue, el procesamiento de eventos y BFS son colas. Ya están por todo el desarrollo para iOS.
  • Swift no tiene Stack porque append/popLast de Array ya forman una pila O(1).
  • Al crear una cola con un array, removeFirst() cuesta O(n): una causa habitual de timeout en pruebas de programación.
  • Las soluciones son un índice head, dos pilas o, en producción, Deque de swift-collections.
  • Elegir pila o cola depende de una sola pregunta: ¿primero lo más reciente o primero lo que llegó antes?

En el próximo artículo continuaremos con sus variantes: heap y cola de prioridad, además de la tabla hash que se oculta tras Dictionary.