O módulo itertools
A aula 58 explora o módulo itertools do Python, apresentando funções como count, cycle, chain, combinations, permutations e groupby. Com exemplos práticos e exercícios, o aluno aprenderá a criar iteradores eficientes e a resolver problemas de programação de forma elegante.
O módulo itertools é uma das joias da biblioteca padrão do Python. Ele fornece um conjunto de funções que criam iteradores eficientes para loops, combinando conceitos de programação funcional e manipulação de dados. Essas funções são projetadas para serem rápidas e econômicas em memória, tornando o código mais limpo e expressivo. Nesta aula, vamos explorar algumas das funções mais úteis do módulo, com exemplos práticos e casos de uso do mundo real.
Entender o itertools é essencial para quem deseja escrever código Python idiomático e eficiente. Em vez de reinventar a roda com loops aninhados ou listas intermediárias, você pode usar essas ferramentas compostas para criar pipelines de dados sofisticados. Vamos mergulhar nas funções principais e ver como elas podem simplificar seu dia a dia como programador.
count, cycle, chain
A função count() cria um iterador que gera números consecutivos, começando de um valor inicial e incrementando por um passo especificado. É útil para gerar índices ou contadores infinitos, mas lembre-se de que, como é infinito, você deve usá-lo com cuidado, geralmente com islice() ou em loops com condição de saída.
Já cycle() itera sobre uma sequência indefinidamente, repetindo-a após chegar ao fim. É perfeito para alternar entre valores ou criar padrões repetitivos. Por fim, chain() combina vários iteráveis em um único iterador, percorrendo-os sequencialmente. Isso é ideal para concatenar listas, tuplas ou qualquer outro iterável sem criar cópias desnecessárias.
from itertools import count, cycle, chain, islice
# count: gera números infinitos
contador = count(start=10, step=2)
primeiros = list(islice(contador, 5))
print(primeiros) # [10, 12, 14, 16, 18]
# cycle: repete uma sequência infinitamente
ciclo = cycle(['vermelho', 'verde', 'azul'])
print(list(islice(ciclo, 5))) # ['vermelho', 'verde', 'azul', 'vermelho', 'verde']
# chain: concatena iteráveis
lista1 = [1, 2, 3]
lista2 = [4, 5]
concatenado = list(chain(lista1, lista2))
print(concatenado) # [1, 2, 3, 4, 5]Essas funções são blocos de construção para iteradores mais complexos. Elas evitam a criação de listas intermediárias, economizando memória, especialmente quando se trabalha com grandes volumes de dados. Por exemplo, em vez de lista1 + lista2 (que cria uma nova lista), chain apenas percorre os elementos sem cópia.
combinations e permutations
As funções combinations() e permutations() geram todas as combinações e permutações possíveis de um iterável, respectivamente. Uma combinação ignora a ordem, enquanto uma permutação considera a ordem. Ambas são extremamente úteis em problemas de matemática, jogos, análise de dados e otimização.
Por exemplo, se você precisa testar todas as formas de escolher 2 itens de uma lista, combinations retorna os pares únicos. Já permutations retorna todos os arranjos ordenados. É importante notar que o número de resultados cresce rapidamente, então use com moderação.
from itertools import combinations, permutations
letras = ['A', 'B', 'C']
# Combinações de 2 elementos (ordem não importa)
combos = list(combinations(letras, 2))
print(combos) # [('A', 'B'), ('A', 'C'), ('B', 'C')]
# Permutações de 2 elementos (ordem importa)
perms = list(permutations(letras, 2))
print(perms) # [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]Essas funções são implementadas de forma eficiente, não gerando todas as combinações de uma vez, mas sim uma por vez, o que permite iterar sobre elas sem estourar a memória. Isso é crucial em problemas de força bruta ou quando se explora um espaço de busca grande.
groupby
A função groupby() agrupa elementos consecutivos de um iterável que compartilham o mesmo valor de uma chave. É importante destacar que ela agrupa apenas elementos consecutivos, então a sequência deve estar ordenada pela chave para agrupar todos os elementos semelhantes. Caso contrário, você pode obter múltiplos grupos para a mesma chave.
O retorno é um iterador de tuplas (chave, grupo), onde grupo é um iterador dos elementos que pertencem àquele grupo. É uma ferramenta poderosa para processamento de dados, como agrupar registros por categoria, desde que os dados estejam ordenados.
from itertools import groupby
dados = [
{'nome': 'Ana', 'cidade': 'SP'},
{'nome': 'Bruno', 'cidade': 'RJ'},
{'nome': 'Carla', 'cidade': 'SP'},
{'nome': 'Diego', 'cidade': 'RJ'},
]
# Ordenar por cidade para agrupar corretamente
dados_ordenados = sorted(dados, key=lambda x: x['cidade'])
for cidade, grupo in groupby(dados_ordenados, key=lambda x: x['cidade']):
nomes = [pessoa['nome'] for pessoa in grupo]
print(f'{cidade}: {nomes}')
# Saída:
# RJ: ['Bruno', 'Diego']
# SP: ['Ana', 'Carla']Neste exemplo, os dados foram ordenados por cidade antes de usar groupby. Sem essa ordenação, teríamos grupos separados para cada ocorrência de 'SP', o que não é desejado. Portanto, lembre-se sempre de ordenar quando necessário.
Casos de uso
O itertools é amplamente utilizado em várias áreas. Em análise de dados, groupby é usado para agregar informações por categoria. Em simulações, cycle e count ajudam a criar loops infinitos controlados. Em algoritmos de busca, combinations e permutations exploram todas as possibilidades. Além disso, funções como islice, takewhile e dropwhile (não abordadas aqui) complementam o kit.
Vamos ver um exemplo prático: gerar uma lista de horários em intervalos de 30 minutos usando count e islice. Ou ainda, criar um sistema de rodízio de tarefas com cycle. Esses casos mostram como o módulo resolve problemas de forma concisa e legível.
from itertools import count, islice, cycle
# Gerar os primeiros 10 horários a partir de 9:00 com incremento de 30 minutos
inicio = 9 * 60 # 9:00 em minutos
horarios = [f'{h//60:02d}:{h%60:02d}' for h in islice(count(inicio, 30), 10)]
print(horarios)
# ['09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '12:00', '12:30', '13:00', '13:30']
# Rodízio de pessoas para limpeza
pessoas = ['Alice', 'Bob', 'Carol']
escalas = list(islice(cycle(pessoas), 7))
print(escalas) # ['Alice', 'Bob', 'Carol', 'Alice', 'Bob', 'Carol', 'Alice']Esses exemplos ilustram como você pode usar itertools para criar soluções elegantes para problemas comuns. Dominar essas funções fará seu código mais Pythonico e eficiente.
Boas práticas e observações finais
Ao usar itertools, lembre-se de que muitas funções retornam iteradores, não listas. Isso significa que elas são preguiçosas e só geram elementos quando consumidos. Isso é ótimo para eficiência, mas pode causar surpresas se você tentar reutilizar o iterador. Sempre converta para lista ou use list() se precisar de uma coleção reutilizável.
Outra dica: combine itertools com funções como map, filter e functools.reduce para criar pipelines de processamento de dados. Isso promove um estilo de programação funcional que é poderoso e expressivo. Finalmente, consulte a documentação oficial para explorar mais funções como product, zip_longest e accumulate.
Referências
- Documentação oficial do itertools (Python)
- Functional Programming HOWTO (Python)
- Real Python: Python itertools
- GeeksforGeeks: Python itertools
- Funções embutidas do Python (útil para combinar com itertools)
Exercícios
- Exercício 1: Use
count()eislice()para gerar os primeiros 15 números pares (começando em 0). Imprima a lista. - Exercício 2: Dada a string "ABC", use
combinations()para listar todas as combinações de 2 letras. Imprima o resultado. - Exercício 3: Crie um iterador com
cycle()que repita a sequência [1, 2, 3] e extraia os primeiros 10 elementos usandoislice(). Imprima a lista. - Exercício 4: Use
groupby()para agrupar uma lista de números por paridade (par/ímpar). Lembre-se de ordenar antes. A lista é [1, 2, 3, 4, 5]. Imprima cada grupo. - Exercício 5: Escreva uma função que receba uma lista de nomes e use
chain()para concatenar duas listas de nomes (primeiros e últimos) em uma única lista, preservando a ordem. Teste com as listas ["Ana", "Bruno"] e ["Silva", "Souza"].
from itertools import count, islice
pares = list(islice(count(0, 2), 15))
print(pares) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]from itertools import combinations
letras = "ABC"
combos = list(combinations(letras, 2))
print(combos) # [('A', 'B'), ('A', 'C'), ('B', 'C')]from itertools import cycle, islice
ciclo = cycle([1, 2, 3])
primeiros = list(islice(ciclo, 10))
print(primeiros) # [1, 2, 3, 1, 2, 3, 1, 2, 3, 1]from itertools import groupby
nums = [1, 2, 3, 4, 5]
ordenados = sorted(nums, key=lambda x: x % 2)
for chave, grupo in groupby(ordenados, key=lambda x: x % 2):
print(f"{'par' if chave == 0 else 'ímpar'}: {list(grupo)}")
# Saída:
# ímpar: [1, 3, 5]
# par: [2, 4]from itertools import chain
def concatenar_listas(lista1, lista2):
return list(chain(lista1, lista2))
primeiros = ["Ana", "Bruno"]
sobrenomes = ["Silva", "Souza"]
print(concatenar_listas(primeiros, sobrenomes)) # ['Ana', 'Bruno', 'Silva', 'Souza']