O módulo collections é uma das bibliotecas padrão mais úteis do Python. Ele fornece alternativas elegantes e eficientes para as estruturas de dados built-in, como dicionários, listas e tuplas. Nesta aula, vamos explorar quatro dessas estruturas: defaultdict, Counter, deque e namedtuple. Cada uma resolve problemas comuns de forma mais concisa e performática.

defaultdict

O defaultdict é uma subclasse de dict que fornece um valor padrão para chaves inexistentes. Isso elimina a necessidade de verificar se uma chave existe antes de acessá-la ou modificá-la, tornando o código mais limpo e evitando erros de KeyError.

Para usar, importe de collections e passe uma função de fábrica (callable) que retorna o valor padrão. Por exemplo, defaultdict(list) cria um dicionário onde valores ausentes são inicializados como listas vazias. Isso é útil para agrupar itens.

from collections import defaultdict

# Agrupar palavras por primeira letra
words = ['banana', 'abacaxi', 'abacate', 'beterraba', 'ameixa']
groups = defaultdict(list)
for word in words:
    groups[word[0]].append(word)

print(groups)
# defaultdict(<class 'list'>, {'b': ['banana', 'beterraba'], 'a': ['abacaxi', 'abacate', 'ameixa']})

Counter

Counter é uma subclasse de dict usada para contar objetos hasháveis. Ele recebe um iterável e cria um dicionário onde as chaves são os elementos e os valores são suas contagens. É ideal para tarefas como contar frequências de palavras, caracteres, etc.

Oferece métodos úteis como most_common(n) que retorna os n elementos mais frequentes, e suporte para operações aritméticas entre contadores.

from collections import Counter

# Contar letras em uma string
texto = "paralelepipedo"
contagem = Counter(texto)
print(contagem)
# Counter({'p': 3, 'e': 3, 'a': 2, 'l': 2, 'r': 1, 'i': 1, 'd': 1, 'o': 1})

# Os 3 mais comuns
print(contagem.most_common(3))
# [('p', 3), ('e', 3), ('a', 2)]

deque

deque (double-ended queue) é uma lista otimizada para inserções e remoções rápidas em ambas as extremidades. Enquanto listas Python têm complexidade O(n) para inserir/remover no início, deque faz isso em O(1). É ideal para filas, pilhas e buffers circulares.

Principais métodos: append, appendleft, pop, popleft e rotate. Suporta também um tamanho máximo (maxlen), descartando automaticamente itens do lado oposto quando o limite é atingido.

from collections import deque

# Fila simples
fila = deque(['a', 'b', 'c'])
fila.append('d')
fila.popleft()  # remove 'a'
print(fila)  # deque(['b', 'c', 'd'])

# Buffer circular com maxlen
buffer = deque(maxlen=3)
for i in range(5):
    buffer.append(i)
    print(buffer)
# deque([0], maxlen=3)
# deque([0, 1], maxlen=3)
# deque([0, 1, 2], maxlen=3)
# deque([1, 2, 3], maxlen=3)
# deque([2, 3, 4], maxlen=3)

namedtuple

namedtuple é uma fábrica de tuplas com campos nomeados. Ela cria classes de tupla imutáveis que podem ser acessadas tanto por índice quanto por nome, tornando o código mais legível sem perder a eficiência da tupla.

Para criar, use namedtuple('Nome', 'campo1 campo2') ou uma lista de strings. Os objetos gerados são imutáveis e consomem a mesma memória de uma tupla.

from collections import namedtuple

# Definir um tipo Ponto
Ponto = namedtuple('Ponto', ['x', 'y'])
p = Ponto(10, 20)
print(p.x, p.y)  # 10 20
print(p[0])      # 10 (acesso por índice)

# Útil para registros simples
Aluno = namedtuple('Aluno', 'nome matricula nota')
a = Aluno('João', '1234', 9.5)
print(f'{a.nome} - {a.nota}')  # João - 9.5

Boas Práticas

  • Use defaultdict para evitar verificações de existência em dicionários.
  • Prefira Counter para contagens, em vez de dicionários manuais.
  • Use deque quando precisar de filas ou pilhas com alta performance nas extremidades.
  • namedtuple é ideal para dados imutáveis e legíveis, como registros leves.

Referências

Exercícios

  1. Usando defaultdict, escreva uma função que receba uma lista de números e retorne um dicionário onde as chaves são os números e os valores são listas de índices onde cada número aparece.
  2. ✓ Resposta:
    from collections import defaultdict
    
    def indices_numeros(lista):
        d = defaultdict(list)
        for i, num in enumerate(lista):
            d[num].append(i)
        return d
    
    print(indices_numeros([1, 2, 1, 3, 2, 1]))
    # defaultdict(<class 'list'>, {1: [0, 2, 5], 2: [1, 4], 3: [3]})
  3. Dada uma string, use Counter para encontrar o caractere mais frequente (desconsiderando espaços).
  4. ✓ Resposta:
    from collections import Counter
    
    def caractere_mais_frequente(texto):
        texto = texto.replace(' ', '')
        if not texto:
            return None
        contagem = Counter(texto)
        return contagem.most_common(1)[0][0]
    
    print(caractere_mais_frequente("hello world"))  # 'l'
  5. Implemente uma fila usando deque que permita enfileirar e desenfileirar elementos, e também permita ver o primeiro elemento sem removê-lo.
  6. ✓ Resposta:
    from collections import deque
    
    class Fila:
        def __init__(self):
            self._dados = deque()
    
        def enfileirar(self, item):
            self._dados.append(item)
    
        def desenfileirar(self):
            if not self._dados:
                raise IndexError("Fila vazia")
            return self._dados.popleft()
    
        def primeiro(self):
            if not self._dados:
                raise IndexError("Fila vazia")
            return self._dados[0]
    
        def __len__(self):
            return len(self._dados)
    
    f = Fila()
    f.enfileirar(1)
    f.enfileirar(2)
    print(f.primeiro())  # 1
    print(f.desenfileirar())  # 1
    print(len(f))  # 1
  7. Crie um tipo namedtuple chamado Livro com campos titulo, autor e ano. Instancie dois livros e crie uma lista com eles.
  8. ✓ Resposta:
    from collections import namedtuple
    
    Livro = namedtuple('Livro', ['titulo', 'autor', 'ano'])
    livro1 = Livro('1984', 'George Orwell', 1949)
    livro2 = Livro('Dom Casmurro', 'Machado de Assis', 1899)
    livros = [livro1, livro2]
    for livro in livros:
        print(f'{livro.titulo} ({livro.ano}) - {livro.autor}')
    # 1984 (1949) - George Orwell
    # Dom Casmurro (1899) - Machado de Assis
  9. Escreva uma função que receba uma lista de palavras e retorne um dicionário com a contagem de palavras, mas ignorando diferenças entre maiúsculas/minúsculas. Use Counter.
  10. ✓ Resposta:
    from collections import Counter
    
    def contar_palavras(lista):
        # Converter todas para minúsculas
        lista_lower = [palavra.lower() for palavra in lista]
        return Counter(lista_lower)
    
    print(contar_palavras(['Python', 'python', 'PYTHON', 'Java', 'java']))
    # Counter({'python': 3, 'java': 2})