deque en Python: cola doble eficiente y buffer de tamaño fijo con maxlen

collections.deque (pronounced "deck") es una cola de doble extremo optimizada para inserciones y eliminaciones O(1) por ambos lados. A diferencia de una lista, donde pop(0) es O(n), deque.popleft() es siempre O(1). El parámetro maxlen la convierte en un buffer circular que descarta automáticamente los elementos más antiguos.

Operaciones básicas: appendleft y popleft

from collections import deque

d = deque([1, 2, 3])

# Por la derecha (igual que list)
d.append(4)
print(d)         # deque([1, 2, 3, 4])
ultimo = d.pop()
print(ultimo, d) # 4  deque([1, 2, 3])

# Por la izquierda (O(1), imposible en list)
d.appendleft(0)
print(d)         # deque([0, 1, 2, 3])
primero = d.popleft()
print(primero, d)  # 0  deque([1, 2, 3])

deque como cola FIFO eficiente

from collections import deque

cola_tareas = deque()

# Añadir tareas al final
cola_tareas.append("tarea_1")
cola_tareas.append("tarea_2")
cola_tareas.append("tarea_3")

# Procesar en orden FIFO
while cola_tareas:
    tarea = cola_tareas.popleft()
    print(f"Procesando: {tarea}")

# Procesando: tarea_1
# Procesando: tarea_2
# Procesando: tarea_3

maxlen: buffer de tamaño fijo

Cuando la deque está llena y añades un elemento por la derecha, descarta automáticamente el elemento más antiguo (el de la izquierda). Al revés con appendleft():

from collections import deque

# Buffer de los últimos 5 valores de un sensor
buffer = deque(maxlen=5)

for valor in range(10):
    buffer.append(valor)
    print(list(buffer))

# [0]
# [0, 1]
# [0, 1, 2]
# [0, 1, 2, 3]
# [0, 1, 2, 3, 4]
# [1, 2, 3, 4, 5]   ? 0 descartado
# [2, 3, 4, 5, 6]
# [3, 4, 5, 6, 7]
# [4, 5, 6, 7, 8]
# [5, 6, 7, 8, 9]

Historial de deshacer (undo) con maxlen

from collections import deque

class Editor:
    def __init__(self, max_historial=10):
        self.texto     = ""
        self.historial = deque(maxlen=max_historial)

    def escribir(self, texto):
        self.historial.append(self.texto)
        self.texto += texto

    def deshacer(self):
        if self.historial:
            self.texto = self.historial.pop()
        else:
            print("Nada que deshacer")

    def __str__(self):
        return f"'{self.texto}'"

e = Editor(max_historial=3)
e.escribir("Hola")
e.escribir(", ")
e.escribir("mundo")
print(e)         # 'Hola, mundo'
e.deshacer()
print(e)         # 'Hola, '
e.deshacer()
print(e)         # 'Hola'

rotate(): desplazar elementos

from collections import deque

# Turnos rotativos
turnos = deque(["Ana", "Luis", "María", "Pedro"])

def siguiente_turno(d):
    actual = d[0]
    d.rotate(-1)    # mueve el primero al final
    return actual

for _ in range(6):
    print(f"Turno: {siguiente_turno(turnos)}")
# Turno: Ana, Luis, María, Pedro, Ana, Luis...

deque vs list: cuándo usar cada una

# list: acceso por índice O(1), append/pop al final O(1)
#       pop(0) / insert(0, x) son O(n)

# deque: appendleft/popleft O(1), append/pop O(1)
#        acceso por índice O(n) para el centro

# Regla: si solo operas en los extremos ? deque
#         si necesitas acceso aleatorio ? list

deque es la estructura correcta siempre que necesites una cola FIFO, un buffer de tamaño fijo o una pila bidireccional. Para acceso aleatorio frecuente a elementos intermedios, la lista sigue siendo mejor opción.

COMPARTE ESTE ARTÍCULO

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