Contar coisas, agrupar coisas e manter uma fila são três laços que a maioria das pessoas escreve na mão. O módulo collections já tem os três.
Tudo aqui está na biblioteca padrão.
Contando na mão
A versão com dict.get é a mais comum.
# 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)
Ele imprime:
{'apple': 3, 'bread': 2, 'milk': 1}
Counter
O Counter recebe qualquer iterável e conta. Chaves ausentes devolvem 0 em vez de levantar erro.
# 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'])
Ele imprime:
Counter({'apple': 3, 'bread': 2, 'milk': 1})
[('apple', 3), ('bread', 2)]
3 0
O most_common é o motivo pelo qual as pessoas recorrem a ele. Ordenar um dicionário por valor custa uma linha e um lambda.
Aritmética com Counter
Counters somam e subtraem. A subtração descarta tudo que chega a zero ou abaixo.
# Counter arithmetic
stock = Counter(apple=5, bread=2)
sold = Counter(apple=3, bread=1, milk=1)
print(stock - sold)
print(stock + sold)
Ele imprime:
Counter({'apple': 2, 'bread': 1})
Counter({'apple': 8, 'bread': 3, 'milk': 1})
Prático para níveis de estoque, diferenças entre duas contagens e trabalho simples com saco de palavras.
Agrupando com defaultdict
defaultdict(list) chama list() para qualquer chave que não existe, então você pode dar append sem verificar 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))
Ele imprime:
{'eng': ['ada', 'alan'], 'ops': ['grace']}
A pegadinha do defaultdict
Ler uma chave ausente cria ela. Esse é o comportamento que surpreende as pessoas, normalmente quando um dicionário tem mais chaves do que o esperado depois de um laço de consulta.
# the gotcha, reading creates the key
d = defaultdict(list)
print(d['not-there'])
print(dict(d))
plain = {}
print(plain.get('not-there'))
print(plain)
Ele imprime:
[]
{'not-there': []}
None
{}
Um dicionário comum com .get() não faz isso. Se você só quer uma leitura segura, use .get().
setdefault faz o mesmo trabalho
Se você prefere não importar nada, o setdefault te leva lá.
# setdefault does the same without a factory
grouped = {}
for team, name in people:
grouped.setdefault(team, []).append(name)
print(grouped)
Ele imprime:
{'eng': ['ada', 'alan'], 'ops': ['grace']}
Ele é mais lento num laço apertado, porque a lista vazia é construída em toda passagem, seja ela necessária ou não.
deque para filas
Remover do começo de uma lista é lento, porque tudo depois desloca. Um deque adiciona e remove nas duas pontas em tempo 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))
Ele imprime:
start ['a', 'b', 'c']
[3, 4, 5]
O maxlen te dá uma janela deslizante que descarta o item mais antigo, o que cai bem para itens recentes e médias móveis.
O que lembrar
-
Counterconta qualquer iterável e te dámost_common. -
Counters aceitam
+e-. -
defaultdictcria a chave na leitura, não só na escrita. -
dequeé o tipo certo quando você remove do começo.
O mesmo módulo também tem namedtuple e ChainMap. Ler o collections inteiro uma vez é uma das melhores horas que você pode passar com a biblioteca padrão.