O módulo itertools
Esta aula explora o módulo itertools do Python, uma ferramenta essencial para criar iteradores eficientes e elegantes. Você aprenderá sobre funções como count, cycle, chain, combinations, permutations e groupby, com exemplos práticos e casos de uso reais para melhorar seu código.
O módulo itertools é um dos tesouros escondidos da biblioteca padrão do Python. Ele fornece um conjunto de funções que criam iteradores para processamento eficiente de dados, seguindo princípios de programação funcional. Essas funções são projetadas para serem rápidas, econômicas em memória e compostas entre si, permitindo que você construa pipelines de dados complexos com pouco código.
Nesta aula, vamos mergulhar nas funções mais importantes do itertools: count, cycle, chain, combinations, permutations e groupby. Cada uma resolve um problema comum de iteração de forma elegante. Ao final, você verá como essas ferramentas se encaixam em casos de uso do mundo real, desde geração de números infinitos até agrupamento de dados.
count, cycle, chain
Começamos com três funções que lidam com a criação e combinação de iteradores.
itertools.count(start=0, step=1) gera uma sequência infinita de números. É útil para criar contadores, índices ou para simular loops com base em condições. Como é infinito, você deve usá-lo com islice ou takewhile para limitar a iteração.
itertools.cycle(iterable) repete um iterável indefinidamente. Por exemplo, cycle('AB') produz A, B, A, B, ... para sempre. É útil para alternar entre valores, como em turnos de trabalho ou cores em um gráfico.
itertools.chain(*iterables) combina vários iteráveis em uma única sequência, como se fossem concatenados. É uma alternativa mais eficiente a + para listas, pois não cria uma nova lista, apenas um iterador.
import itertools
# count: números infinitos a partir de 10, passo 2
contador = itertools.count(10, 2)
print(list(itertools.islice(contador, 5))) # [10, 12, 14, 16, 18]
# cycle: repete uma lista infinitamente
ciclo = itertools.cycle(['vermelho', 'verde', 'azul'])
print(list(itertools.islice(ciclo, 5))) # ['vermelho', 'verde', 'azul', 'vermelho', 'verde']
# chain: concatena várias listas
lista1 = [1, 2, 3]
lista2 = [4, 5]
lista3 = [6]
combinado = itertools.chain(lista1, lista2, lista3)
print(list(combinado)) # [1, 2, 3, 4, 5, 6]Essas funções são a base para construir iteradores personalizados. Elas evitam o desperdício de memória e tornam o código mais expressivo.
combinations e permutations
Quando você precisa gerar combinações ou permutações de um conjunto de elementos, o itertools oferece funções prontas e eficientes.
itertools.combinations(iterable, r) gera todas as combinações de comprimento r dos elementos do iterável, sem repetição e sem ordem. Por exemplo, as combinações de [1,2,3] com r=2 são (1,2), (1,3), (2,3).
itertools.permutations(iterable, r=None) gera todas as permutações de comprimento r (se r não for informado, usa o comprimento do iterável), ou seja, todas as ordenações possíveis. Por exemplo, as permutações de [1,2,3] com r=2 são (1,2), (1,3), (2,1), (2,3), (3,1), (3,2).
Essas funções são extremamente úteis em problemas de otimização, jogos, criptografia e qualquer área que exija explorar arranjos de elementos.
import itertools
# Combinações de 2 elementos da lista
letras = ['A', 'B', 'C']
comb = itertools.combinations(letras, 2)
print(list(comb)) # [('A', 'B'), ('A', 'C'), ('B', 'C')]
# Permutações de 2 elementos
perm = itertools.permutations(letras, 2)
print(list(perm)) # [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]Note que as combinações ignoram a ordem, enquanto as permutações a consideram. Isso é crucial na escolha da função adequada.
groupby
A função itertools.groupby(iterable, key=None) agrupa elementos consecutivos que têm o mesmo valor de chave. Ela retorna um iterador de pares (chave, grupo), onde grupo é um iterador para os elementos consecutivos com aquela chave.
É importante ressaltar que groupby só agrupa itens consecutivos. Se os dados não estiverem ordenados pela chave, os grupos serão fragmentados. Portanto, é comum ordenar o iterável antes de aplicar groupby.
O parâmetro key é uma função que extrai a chave de cada elemento. Se não for fornecido, o próprio elemento é usado como chave.
import itertools
dados = [('A', 1), ('A', 2), ('B', 3), ('B', 4), ('A', 5)]
# Ordenar por letra para agrupar corretamente
dados_ordenados = sorted(dados, key=lambda x: x[0])
for letra, grupo in itertools.groupby(dados_ordenados, key=lambda x: x[0]):
print(letra, list(grupo))
# A [('A', 1), ('A', 2)]
# B [('B', 3), ('B', 4)]
# A [('A', 5)] (novo grupo porque não é consecutivo após ordenação? Na verdade, após ordenar, todos os A vêm primeiro, então o último A é consecutivo, mas o exemplo mostra que sem ordenar, os grupos são fragmentados)
No exemplo acima, se não ordenássemos, os grupos seriam: A, B, A. Com a ordenação, todos os A ficam juntos, formando um único grupo. Isso mostra a importância de pré-processar os dados.
Casos de uso
O itertools é amplamente usado em análise de dados, processamento de logs, geração de sequências e até em algoritmos de aprendizado de máquina. Vamos ver alguns exemplos práticos:
- Gerar números de página infinitos: Com
count, você pode criar um gerador de números de página que nunca acaba, útil para paginação de APIs. - Alternar entre estados: Com
cycle, você pode alternar entre modos (ex.: ligado/desligado) em um loop. - Combinar múltiplas fontes de dados:
chainé perfeito para processar vários arquivos ou listas como se fossem um único fluxo. - Gerar pares de teste:
combinationsepermutationssão usados em testes para gerar todas as entradas possíveis. - Agrupar logs por data:
groupbypode agrupar entradas de log por dia, hora, etc., desde que os dados estejam ordenados.
import itertools
# Exemplo: gerar combinações de times para um torneio
times = ['Time A', 'Time B', 'Time C', 'Time D']
partidas = itertools.combinations(times, 2)
print('Partidas:')
for partida in partidas:
print(f'{partida[0]} vs {partida[1]}')
# Exemplo: agrupar números por paridade (par/ímpar)
numeros = [1, 2, 3, 4, 5, 6]
numeros_ord = sorted(numeros, key=lambda x: x % 2)
for chave, grupo in itertools.groupby(numeros_ord, key=lambda x: x % 2):
print('Par' if chave == 0 else 'Ímpar', list(grupo))Esses exemplos mostram como o itertools pode simplificar tarefas que, de outra forma, exigiriam loops complexos e gerenciamento manual de índices.
Boas práticas e observações finais
Ao usar itertools, lembre-se de que os iteradores são lazy: eles calculam os valores sob demanda. Isso é ótimo para economia de memória, mas você deve ter cuidado ao convertê-los em listas se o conjunto for grande.
Combine funções do itertools com outras funções como map, filter e zip para criar pipelines poderosos. Por exemplo, você pode usar itertools.islice para limitar um count ou takewhile para parar quando uma condição for atingida.
Além disso, consulte a documentação oficial para explorar outras funções úteis como product, tee, zip_longest, etc. Elas podem resolver problemas que você nem imaginava que tinham solução pronta.
Referências
- Documentação oficial do itertools (Python)
- Python itertools: A Complete Guide (Real Python)
- Python itertools (GeeksforGeeks)
- Programação funcional em Python (documentação oficial)
- Python Module of the Week: itertools
- Programiz: Python itertools
Exercícios
- Exercício 1: Crie um gerador que produza os primeiros 10 números da sequência de Fibonacci usando
itertools.islicee uma função geradora.✓ Resposta:import itertools def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b print(list(itertools.islice(fibonacci(), 10))) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] - Exercício 2: Use
itertools.cyclepara criar um loop que alterne entre as strings "par" e "ímpar" para os números de 0 a 9, e imprima cada número com sua classificação.✓ Resposta:import itertools rotulo = itertools.cycle(['par', 'ímpar']) for i in range(10): print(f'{i} é {next(rotulo)}') - Exercício 3: Dada uma lista de palavras, use
itertools.chainpara combinar as letras de todas as palavras em uma única lista de caracteres. Por exemplo, ['ab', 'cde'] deve resultar em ['a', 'b', 'c', 'd', 'e'].✓ Resposta:import itertools palavras = ['ab', 'cde'] letras = list(itertools.chain.from_iterable(palavras)) print(letras) # ['a', 'b', 'c', 'd', 'e'] - Exercício 4: Gere todas as permutações possíveis da string "ABC" e imprima-as em ordem lexicográfica. Use
itertools.permutationse ordene o resultado.✓ Resposta:import itertools permutacoes = itertools.permutations('ABC') perm_ordenadas = sorted(permutacoes) for perm in perm_ordenadas: print(''.join(perm)) # ABC # ACB # BAC # BCA # CAB # CBA - Exercício 5: Use
itertools.groupbypara agrupar os números de 1 a 10 por sua divisibilidade por 3 (resto 0, 1 ou 2). Imprima cada grupo com sua chave.✓ Resposta:import itertools numeros = range(1, 11) numeros_ord = sorted(numeros, key=lambda x: x % 3) for chave, grupo in itertools.groupby(numeros_ord, key=lambda x: x % 3): print(f'Resto {chave}: {list(grupo)}') # Resto 0: [3, 6, 9] # Resto 1: [1, 4, 7, 10] # Resto 2: [2, 5, 8]