itertools en Python: chain, islice, groupby, product y combinations

El módulo itertools de la biblioteca estándar ofrece herramientas para construir pipelines de iteradores eficientes en memoria. Sus funciones trabajan con iteradores y devuelven iteradores, por lo que combinan sin coste de copia de datos.

chain: concatenar iterables sin copiarlos

from itertools import chain

lista1 = [1, 2, 3]
lista2 = [4, 5, 6]
tupla  = (7, 8, 9)

# Concatena sin crear una nueva lista
combinado = chain(lista1, lista2, tupla)
print(list(combinado))  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# chain.from_iterable: aplanar un nivel de anidamiento
anidado = [[1, 2], [3, 4], [5, 6]]
plano = list(chain.from_iterable(anidado))
print(plano)  # [1, 2, 3, 4, 5, 6]

islice: cortar generadores

from itertools import islice

def naturales():
    n = 1
    while True:
        yield n
        n += 1

# Tomar los primeros 5
print(list(islice(naturales(), 5)))     # [1, 2, 3, 4, 5]
# Desde la posición 10, hasta 15
print(list(islice(naturales(), 10, 15))) # [11, 12, 13, 14, 15]
# Con paso 3
print(list(islice(naturales(), 0, 20, 3)))  # [1, 4, 7, 10, 13, 16, 19]

groupby: agrupar elementos consecutivos

Importante: groupby agrupa solo los elementos consecutivos iguales. Si los datos no están ordenados por la clave, necesitas ordenarlos primero con sorted():

from itertools import groupby

registros = [
    {"dept": "ventas",   "nombre": "Ana"},
    {"dept": "ventas",   "nombre": "Luis"},
    {"dept": "tech",     "nombre": "María"},
    {"dept": "tech",     "nombre": "Pedro"},
    {"dept": "ventas",   "nombre": "Juan"},  # ventas no consecutivo
]

# NECESARIO: ordenar por la clave antes de agrupar
registros_ord = sorted(registros, key=lambda r: r["dept"])

for dept, grupo in groupby(registros_ord, key=lambda r: r["dept"]):
    miembros = [r["nombre"] for r in grupo]
    print(f"{dept}: {miembros}")

# tech: ['María', 'Pedro']
# ventas: ['Ana', 'Luis', 'Juan']

product: producto cartesiano

from itertools import product

# Equivalente a bucles for anidados
colores = ["rojo", "azul"]
tallas  = ["S", "M", "L"]

for color, talla in product(colores, tallas):
    print(f"{color}-{talla}", end="  ")
# rojo-S  rojo-M  rojo-L  azul-S  azul-M  azul-L

# repeat: repetir el mismo iterable N veces
# Generar todas las combinaciones de 2 dígitos binarios
print(list(product([0, 1], repeat=2)))
# [(0, 0), (0, 1), (1, 0), (1, 1)]

combinations y permutations

from itertools import combinations, permutations, combinations_with_replacement

letras = ["A", "B", "C", "D"]

# combinations: subconjuntos sin repetición, sin orden
print(list(combinations(letras, 2)))
# [('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]

# permutations: todas las ordenaciones posibles
print(list(permutations(["X", "Y", "Z"])))
# [('X','Y','Z'), ('X','Z','Y'), ('Y','X','Z'), ...]

# combinations_with_replacement: permite repetir elementos
print(list(combinations_with_replacement([1, 2, 3], 2)))
# [(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)]

Errores habituales con itertools

from itertools import groupby

# MAL: intentar consumir el iterador de grupo dos veces
for clave, grupo in groupby(datos, key=lambda x: x["dept"]):
    lista = list(grupo)     # OK: consume el grupo
    # print(list(grupo))    # MAL: ya está consumido, devuelve []

# MAL: olvidar que los iteradores se agotan
import itertools
gen = itertools.chain([1, 2], [3, 4])
print(list(gen))  # [1, 2, 3, 4]
print(list(gen))  # [] — ya estaba agotado

itertools es especialmente útil cuando trabajas con conjuntos de datos grandes que no caben en memoria: todas sus funciones producen elementos uno a uno sin cargar el conjunto completo.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP