Blog

Counter, defaultdict e deque em Python

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

  • Counter conta qualquer iterável e te dá most_common.

  • Counters aceitam + e -.

  • defaultdict cria 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.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.