Contar cosas, agrupar cosas y mantener una cola son tres bucles que casi todo el mundo escribe a mano. El módulo collections ya tiene los tres.
Todo lo que hay aquí viene en la biblioteca estándar.
Contar a mano
La versión con dict.get es la común.
# counting the long way
words = ['apple', 'bread', 'apple', 'milk', 'bread', 'apple']
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts)
Imprime:
{'apple': 3, 'bread': 2, 'milk': 1}
Counter
Counter toma cualquier iterable y lo cuenta. Las claves que faltan devuelven 0 en lugar de lanzar un error.
# Counter does it in one line
from collections import Counter, defaultdict, deque
c = Counter(words)
print(c)
print(c.most_common(2))
print(c['apple'], c['missing'])
Imprime:
Counter({'apple': 3, 'bread': 2, 'milk': 1})
[('apple', 3), ('bread', 2)]
3 0
most_common es la razón por la que la gente lo usa. Ordenar un diccionario por valor cuesta una línea y una lambda.
Aritmética de Counter
Los Counter se suman y se restan. La resta descarta todo lo que llegue a cero o menos.
# Counter arithmetic
stock = Counter(apple=5, bread=2)
sold = Counter(apple=3, bread=1, milk=1)
print(stock - sold)
print(stock + sold)
Imprime:
Counter({'apple': 2, 'bread': 1})
Counter({'apple': 8, 'bread': 3, 'milk': 1})
Práctico para niveles de inventario, diferencias entre dos conteos y trabajo simple de bolsa de palabras.
Agrupar con defaultdict
defaultdict(list) llama a list() para cualquier clave que no exista, así que puedes agregar sin comprobar antes.
# grouping with defaultdict
people = [('eng', 'ada'), ('ops', 'grace'), ('eng', 'alan')]
groups = defaultdict(list)
for team, name in people:
groups[team].append(name)
print(dict(groups))
Imprime:
{'eng': ['ada', 'alan'], 'ops': ['grace']}
La trampa de defaultdict
Leer una clave que falta la crea. Este es el comportamiento que sorprende a la gente, normalmente cuando un diccionario tiene más claves de las esperadas después de un bucle de consultas.
# the gotcha, reading creates the key
d = defaultdict(list)
print(d['not-there'])
print(dict(d))
plain = {}
print(plain.get('not-there'))
print(plain)
Imprime:
[]
{'not-there': []}
None
{}
Un diccionario normal con .get() no hace esto. Si solo quieres una lectura segura, usa .get().
setdefault hace el mismo trabajo
Si prefieres no importar nada, setdefault te lleva al mismo lugar.
# setdefault does the same without a factory
grouped = {}
for team, name in people:
grouped.setdefault(team, []).append(name)
print(grouped)
Imprime:
{'eng': ['ada', 'alan'], 'ops': ['grace']}
Es más lento en un bucle apretado, porque la lista vacía se construye en cada pasada, se necesite o no.
deque para colas
Sacar del frente de una lista es lento, porque todo lo que va después se corre. Un deque agrega y saca en los dos extremos en tiempo constante.
# deque for queues, because list.pop(0) is slow
q = deque(['a', 'b', 'c'])
q.appendleft('start')
print(q.popleft(), list(q))
recent = deque(maxlen=3)
for item in range(6):
recent.append(item)
print(list(recent))
Imprime:
start ['a', 'b', 'c']
[3, 4, 5]
maxlen te da una ventana móvil que descarta el elemento más viejo, lo cual encaja muy bien para «los últimos N» y para promedios móviles.
Qué recordar
-
Countercuenta cualquier iterable y te damost_common. -
Los Counter soportan
+y-. -
defaultdictcrea la clave al leer, no solo al escribir. -
dequees el tipo correcto cuando sacas del frente.
El mismo módulo también tiene namedtuple y ChainMap. Leer collections de punta a punta una vez es una de las mejores horas que puedes pasar con la biblioteca estándar.