CAP 11 · LEC 02·Estructuras de datos modernas

collections: deque, Counter, defaultdict y OrderedDict

El módulo collections es la caja de herramientas de estructuras de datos de Python. deque para colas eficientes, Counter para contar, defaultdict para evitar KeyError.

● INTERMEDIO9 min lectura5 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

deque — appendleft/popleft en O(1)

deque (double-ended queue) es una lista optimizada para insertar y eliminar en ambos extremos en tiempo O(1). Una lista normal tarda O(n) al insertar al inicio porque debe desplazar todos los elementos.

from collections import deque # Crear un deque cola = deque([1, 2, 3]) # Añadir al final (como lista) y al inicio — ambos O(1) cola.append(4) # [1, 2, 3, 4] cola.appendleft(0) # [0, 1, 2, 3, 4] # Eliminar del final y del inicio — ambos O(1) ultimo = cola.pop() # 4 — cola: [0, 1, 2, 3] primero = cola.popleft() # 0 — cola: [1, 2, 3] print(cola) # deque([1, 2, 3]) print(ultimo) # 4 print(primero) # 0 # maxlen: ventana deslizante — descarta el extremo opuesto ultimas_5 = deque(maxlen=5) for n in range(10): ultimas_5.append(n) print(ultimas_5) # deque([5, 6, 7, 8, 9], maxlen=5) # Rotar: mover n posiciones a la derecha (negativo = izquierda) d = deque([1, 2, 3, 4, 5]) d.rotate(2) print(d) # deque([4, 5, 1, 2, 3])
Salidadeque([1, 2, 3]) 4 0 deque([5, 6, 7, 8, 9], maxlen=5) deque([4, 5, 1, 2, 3])
list.insert(0, x)deque.appendleft(x)
O(n) — desplaza todos los elementosO(1) — puntero doble, sin desplazar nada
Lento para colas FIFO de alto volumenDiseñado exactamente para ese uso

Counter — contar todo

Counter es un dict especializado en contar. Acepta cualquier iterable, cuenta las ocurrencias de cada elemento y ofrece métodos útiles como most_common().

from collections import Counter # Contar letras en un string letras = Counter("abracadabra") print(letras) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) # Contar palabras en un texto texto = "el cielo es azul el mar es azul el aire es fresco" palabras = Counter(texto.split()) print(palabras.most_common(3)) # [('el', 3), ('es', 3), ('azul', 2)] # Operaciones aritméticas entre Counters c1 = Counter({"python": 3, "js": 2, "go": 1}) c2 = Counter({"python": 1, "rust": 4, "go": 2}) print(c1 + c2) # Counter({'rust': 4, 'python': 4, 'go': 3, 'js': 2}) print(c1 - c2) # Counter({'python': 2, 'js': 2}) — solo positivos # Acceso a elementos no presentes: retorna 0, no KeyError print(letras["z"]) # 0 # total() para la suma de todos los conteos print(letras.total()) # 11 # Actualizar con más datos letras.update("aaa") print(letras["a"]) # 8
SalidaCounter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) [('el', 3), ('es', 3), ('azul', 2)] Counter({'rust': 4, 'python': 4, 'go': 3, 'js': 2}) Counter({'python': 2, 'js': 2}) 0 11 8

defaultdict — valores por defecto automáticos

defaultdict evita el KeyError al acceder a claves inexistentes. Acepta una función de fábrica que genera el valor por defecto automáticamente cuando la clave no existe.

from collections import defaultdict # Sin defaultdict: error al acumular sin inicializar try: d = {} d["python"].append("Ana") # KeyError: 'python' except KeyError as e: print(f"Error: {e}") # Con defaultdict(list): la lista se crea automáticamente grupos = defaultdict(list) datos = [("python", "Ana"), ("js", "Carlos"), ("python", "Diana"), ("js", "Eva")] for lenguaje, nombre in datos: grupos[lenguaje].append(nombre) # sin verificar si la clave existe print(dict(grupos)) # {'python': ['Ana', 'Diana'], 'js': ['Carlos', 'Eva']} # defaultdict(int): contador manual visitas = defaultdict(int) paginas = ["/home", "/about", "/home", "/contact", "/home"] for pagina in paginas: visitas[pagina] += 1 print(dict(visitas)) # {'/home': 3, '/about': 1, '/contact': 1} # defaultdict(set): agrupar en sets (sin duplicados) tags_por_post = defaultdict(set) tags_por_post["post1"].add("python") tags_por_post["post1"].add("python") # ignorado (ya existe) tags_por_post["post1"].add("tutorial") print(dict(tags_por_post)) # {'post1': {'python', 'tutorial'}}
SalidaError: 'python' {'python': ['Ana', 'Diana'], 'js': ['Carlos', 'Eva']} {'/home': 3, '/about': 1, '/contact': 1} {'post1': {'python', 'tutorial'}}

OrderedDict y ChainMap

OrderedDict mantiene el orden de inserción (como los dicts normales en Python 3.7+), pero ofrece move_to_end() y comparación sensible al orden. ChainMap combina múltiples dicts en una vista única.

from collections import OrderedDict, ChainMap # OrderedDict: útil cuando move_to_end() importa cache = OrderedDict() cache["a"] = 1 cache["b"] = 2 cache["c"] = 3 cache.move_to_end("a") # mueve "a" al final print(list(cache.keys())) # ['b', 'c', 'a'] cache.move_to_end("c", last=False) # mueve "c" al inicio print(list(cache.keys())) # ['c', 'b', 'a'] # OrderedDict LRU cache manual (pop el primero cuando está lleno) lru = OrderedDict(maxlen=3) if False else OrderedDict() def lru_get(key): if key in lru: lru.move_to_end(key) # marca como recientemente usado return lru.get(key) # ChainMap: busca en múltiples dicts en orden config_default = {"debug": False, "timeout": 30, "host": "localhost"} config_usuario = {"debug": True, "host": "produccion.example.com"} # config_usuario tiene prioridad sobre config_default config = ChainMap(config_usuario, config_default) print(config["debug"]) # True (del usuario) print(config["timeout"]) # 30 (del default) print(config["host"]) # 'produccion.example.com' (del usuario) # Útil para configuraciones con capas: CLI > env > archivo > defaults
Salida['b', 'c', 'a'] ['c', 'b', 'a'] True 30 produccion.example.com
ChainMap es una vista, no una copia

ChainMap no fusiona los dicts — crea una vista que busca en orden. Si modificas el ChainMap, los cambios van al primer dict. Los dicts originales no se modifican al leer.

Practica