itertools — Funciones de iterador¶
Propósito: | El módulo itertools incluye un conjunto de funciones para trabajar con conjuntos de datos de secuencia. |
---|
Las funciones proporcionadas por itertools
están inspiradas en
características de lenguajes de programación funcional como Clojure, Haskell,
APL y SML. Están destinadas a ser rápidas y usar la memoria de manera
eficiente, y también para ser enganchadas juntas para expresar algoritmos más
complicados basados en iteración.
El código basado en iteradores ofrece mejores características de mejor consumo de memoria que el código que usa listas. Dado que los datos no se producen a partir del iterador hasta que sea necesario, no es necesario que todos los datos sean almacenados en la memoria al mismo tiempo. Este modelo de procesamiento «perezoso» puede reducir el intercambio y otros efectos secundarios de grandes conjuntos de datos, mejorando el rendimiento.
Además de las funciones definidas en itertools
, los ejemplos en esta
sección también se basan en algunas de las funciones incorporadas para
iteración.
Fusionando y dividiendo iteradores¶
La función chain()
toma varios iteradores como argumentos y devuelve un
único iterador que produce el contenido de todas las entradas como si vinieran
de un solo iterador.
from itertools import *
for i in chain([1, 2, 3], ['a', 'b', 'c']):
print(i, end=' ')
print()
chain()
facilita el procesamiento de varias secuencias sin construir una
lista grande.
$ python3 itertools_chain.py
1 2 3 a b c
Si los elementos que se van a combinar no son conocidos por adelantado o no son
necesarios para ser evaluado perezosamente, se puede chain.from_iterable()
usar para construir la cadena en su lugar.
from itertools import *
def make_iterables_to_chain():
yield [1, 2, 3]
yield ['a', 'b', 'c']
for i in chain.from_iterable(make_iterables_to_chain()):
print(i, end=' ')
print()
$ python3 itertools_chain_from_iterable.py
1 2 3 a b c
La función incorporada zip()
devuelve un iterador que combina los elementos
de varios iteradores en tuplas.
for i in zip([1, 2, 3], ['a', 'b', 'c']):
print(i)
Al igual que con las otras funciones de este módulo, el valor de retorno es un objeto iterable que produce valores uno a la vez.
$ python3 itertools_zip.py
(1, 'a')
(2, 'b')
(3, 'c')
zip()
se detiene cuando el primer iterador de entrada se agota. Para
procesar todas las entradas, incluso si los iteradores producen diferentes
números de valores, usa zip_longest()
.
from itertools import *
r1 = range(3)
r2 = range(2)
print('zip stops early:')
print(list(zip(r1, r2)))
r1 = range(3)
r2 = range(2)
print('\nzip_longest processes all of the values:')
print(list(zip_longest(r1, r2)))
Por defecto, zip_longest()
sustituye valores faltantes por None
. Usa
el argumento fillvalue
para usar un valor sustituto diferente.
$ python3 itertools_zip_longest.py
zip stops early:
[(0, 0), (1, 1)]
zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]
La función islice()
devuelve un iterador que devuelve elementos
seleccionados del iterador de entrada, por índice.
from itertools import *
print('Stop at 5:')
for i in islice(range(100), 5):
print(i, end=' ')
print('\n')
print('Start at 5, Stop at 10:')
for i in islice(range(100), 5, 10):
print(i, end=' ')
print('\n')
print('By tens to 100:')
for i in islice(range(100), 0, 100, 10):
print(i, end=' ')
print('\n')
islice()
toma los mismos argumentos que el operador de división para
listas: start
, stop
, y step
. Los argumentos de inicio y paso son
opcionales.
$ python3 itertools_islice.py
Stop at 5:
0 1 2 3 4
Start at 5, Stop at 10:
5 6 7 8 9
By tens to 100:
0 10 20 30 40 50 60 70 80 90
La función tee()
devuelve varios iteradores independientes (por defecto 2)
basados en una sola entrada original.
from itertools import *
r = islice(count(), 5)
i1, i2 = tee(r)
print('i1:', list(i1))
print('i2:', list(i2))
tee()
tiene una semántica similar a la utilidad tee
de Unix, que repite
los valores que lee de su entrada y los escribe en un archivo nombrado y la
salida estándar. Los iteradores devueltos por tee()
se puede utilizar para
alimentar el mismo conjunto de datos en múltiples algoritmos para ser
procesados en paralelo.
$ python3 itertools_tee.py
i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]
Los nuevos iteradores creados por tee()
comparten su entrada, por lo que el
iterador original no debe usarse después de que se creen los nuevos.
from itertools import *
r = islice(count(), 5)
i1, i2 = tee(r)
print('r:', end=' ')
for i in r:
print(i, end=' ')
if i > 1:
break
print()
print('i1:', list(i1))
print('i2:', list(i2))
Si los valores se consumen desde la entrada original, los nuevos iteradores no producirán estos valores:
$ python3 itertools_tee_error.py
r: 0 1 2
i1: [3, 4]
i2: [3, 4]
Conversión de entradas¶
La función incorporada map()
devuelve un iterador que invoca una función
con los valores en los iteradores de entrada, y devuelve los resultados. Se
detiene cuando se agota cualquier iterador de entrada.
def times_two(x):
return 2 * x
def multiply(x, y):
return (x, y, x * y)
print('Doubles:')
for i in map(times_two, range(5)):
print(i)
print('\nMultiples:')
r1 = range(5)
r2 = range(5, 10)
for i in map(multiply, r1, r2):
print('{:d} * {:d} = {:d}'.format(*i))
print('\nStopping:')
r1 = range(5)
r2 = range(2)
for i in map(multiply, r1, r2):
print(i)
En el primer ejemplo, la función lambda multiplica los valores de entrada por 2. En un segundo ejemplo, la función lambda multiplica dos argumentos, tomados de iteradores separados, y devuelve una tupla con los argumentos originales y el valor calculado. El tercer ejemplo se detiene después de producir dos tuplas porque el segundo rango está agotado.
$ python3 itertools_map.py
Doubles:
0
2
4
6
8
Multiples:
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36
Stopping:
(0, 0, 0)
(1, 1, 1)
La función starmap()
es similar a map()
, pero en lugar de contruir una
tupla de múltiples iteradores, divide los elementos en un solo iterador como
argumentos a la función de mapeo usando la sintaxis *
.
from itertools import *
values = [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]
for i in starmap(lambda x, y: (x, y, x * y), values):
print('{} * {} = {}'.format(*i))
Donde la función de mapeo a map()
se llama f(i1, i2)
, la función de
mapeo pasada a starmap()
se llama f(*i)
.
$ python3 itertools_starmap.py
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36
Produciendo nuevos valores¶
La función count()
devuelve un iterador que produce enteros consecutivos,
indefinidamente. El primer número se puede pasar como un argumento (el valor
predeterminado es cero). No hay un argumento de límite superior (Consulta la
función incorporada range()
para obtener más control sobre el conjunto de
resultados).
from itertools import *
for i in zip(count(1), ['a', 'b', 'c']):
print(i)
Este ejemplo se detiene porque el argumento de la lista se consume.
$ python3 itertools_count.py
(1, 'a')
(2, 'b')
(3, 'c')
Los argumentos de inicio y paso a count()
pueden ser valores numéricos que
se pueden agregar juntos.
import fractions
from itertools import *
start = fractions.Fraction(1, 3)
step = fractions.Fraction(1, 3)
for i in zip(count(start, step), ['a', 'b', 'c']):
print('{}: {}'.format(*i))
En este ejemplo, el punto de inicio y los pasos son objetos Fraction
del
módulo fraction
.
$ python3 itertools_count_step.py
1/3: a
2/3: b
1: c
La función cycle()
devuelve un iterador que repite el contenido de los
argumentos recibidos indefinidamente. Como tiene que recordar todo el
contenido del iterador de entrada, puede consumir bastante memoria si el
iterador es largo.
from itertools import *
for i in zip(range(7), cycle(['a', 'b', 'c'])):
print(i)
Se usa una variable de contador para salir del ciclo después de algunos ciclos en este ejemplo.
$ python3 itertools_cycle.py
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'a')
(4, 'b')
(5, 'c')
(6, 'a')
La función repeat()
devuelve un iterador que produce el mismo valor cada
vez que se accede.
from itertools import *
for i in repeat('over-and-over', 5):
print(i)
El iterador devuelto por repeat()
se devuelve datos para siempre a menos
que se proporcione el argumento opcional times
para limitarlo.
$ python3 itertools_repeat.py
over-and-over
over-and-over
over-and-over
over-and-over
over-and-over
Es útil combinar repeat()
con zip()
o map()
cuando los valores
invariantes deben ser incluidos con los valores de otros iteradores.
from itertools import *
for i, s in zip(count(), repeat('over-and-over', 5)):
print(i, s)
Un valor de contador se combina con la constante devuelta por repeat()
en
este ejemplo.
$ python3 itertools_repeat_zip.py
0 over-and-over
1 over-and-over
2 over-and-over
3 over-and-over
4 over-and-over
Este ejemplo usa map()
para multiplicar por dos los números en el rango 0 a
4.
from itertools import *
for i in map(lambda x, y: (x, y, x * y), repeat(2), range(5)):
print('{:d} * {:d} = {:d}'.format(*i))
El iterador repeat()
no necesita ser explícitamente limitado, ya que
map()
detiene el procesamiento cuando termina cualquiera de sus entradas, y
el range()
devuelve solo cinco elementos.
$ python3 itertools_repeat_map.py
2 * 0 = 0
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
Filtrando¶
La función dropwhile()
devuelve un iterador que produce elementos del
iterador de entrada después de que la condición se vuelva falsa por primera
vez.
from itertools import *
def should_drop(x):
print('Testing:', x)
return x < 1
for i in dropwhile(should_drop, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
dropwhile()
no filtra todos los elementos de la entrada; después de que la
condición es falsa la primera vez, todos los elementos restantes en la entrada
son devueltos.
$ python3 itertools_dropwhile.py
Testing: -1
Testing: 0
Testing: 1
Yielding: 1
Yielding: 2
Yielding: -2
Lo contrario de dropwhile()
es takewhile()
. Devuelve un iterador que
devuelve elementos del iterador de entrada, siempre que la función de prueba
devuelve verdadero.
from itertools import *
def should_take(x):
print('Testing:', x)
return x < 2
for i in takewhile(should_take, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
Tan pronto como should_take()
devuelve False
, takewhile()
deja de
procesar la entrada.
$ python3 itertools_takewhile.py
Testing: -1
Yielding: -1
Testing: 0
Yielding: 0
Testing: 1
Yielding: 1
Testing: 2
La función incorporada filter()
devuelve un iterador que incluye solo
elementos para los que la función de prueba devuelve verdadero.
from itertools import *
def check_item(x):
print('Testing:', x)
return x < 1
for i in filter(check_item, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
filter()
es diferente a dropwhile()
y takewhile()
en que cada
elemento es probado antes de ser devuelto.
$ python3 itertools_filter.py
Testing: -1
Yielding: -1
Testing: 0
Yielding: 0
Testing: 1
Testing: 2
Testing: -2
Yielding: -2
filterfalse()
devuelve un iterador que incluye solo elementos donde la
función de prueba devuelve falso.
from itertools import *
def check_item(x):
print('Testing:', x)
return x < 1
for i in filterfalse(check_item, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
La expresión de prueba en check_item()
es la misma, por lo que los
resultados en este ejemplo con filterfalse()
son lo opuesto a los
resultados del ejemplo anterior.
$ python3 itertools_filterfalse.py
Testing: -1
Testing: 0
Testing: 1
Yielding: 1
Testing: 2
Yielding: 2
Testing: -2
compress()
ofrece otra forma de filtrar el contenido de un iterable. En
lugar de llamar a una función, usa los valores en otro iterable para indicar
cuándo aceptar un valor y cuándo ignorarlo.
from itertools import *
every_third = cycle([False, False, True])
data = range(1, 10)
for i in compress(data, every_third):
print(i, end=' ')
print()
El primer argumento son los datos iterables para procesar y el segundo es un selector iterable produciendo valores booleanos que indican qué elementos tomar de la entrada de datos (un valor verdadero hace que el valor sea producido, un valor falso hace que se ignore).
$ python3 itertools_compress.py
3 6 9
Agrupación de datos¶
La función groupby()
devuelve un iterador que produce conjuntos de valores
organizados por una clave común. Este ejemplo ilustra la agrupación de valores
relacionados basados en un atributo.
import functools
from itertools import *
import operator
import pprint
@functools.total_ordering
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return '({}, {})'.format(self.x, self.y)
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __gt__(self, other):
return (self.x, self.y) > (other.x, other.y)
# Create a dataset of Point instances
data = list(map(Point,
cycle(islice(count(), 3)),
islice(count(), 7)))
print('Data:')
pprint.pprint(data, width=35)
print()
# Try to group the unsorted data based on X values
print('Grouped, unsorted:')
for k, g in groupby(data, operator.attrgetter('x')):
print(k, list(g))
print()
# Sort the data
data.sort()
print('Sorted:')
pprint.pprint(data, width=35)
print()
# Group the sorted data based on X values
print('Grouped, sorted:')
for k, g in groupby(data, operator.attrgetter('x')):
print(k, list(g))
print()
La secuencia de entrada debe estar ordenarda por el valor clave para que las agrupaciones para funcionen como se espera.
$ python3 itertools_groupby_seq.py
Data:
[(0, 0),
(1, 1),
(2, 2),
(0, 3),
(1, 4),
(2, 5),
(0, 6)]
Grouped, unsorted:
0 [(0, 0)]
1 [(1, 1)]
2 [(2, 2)]
0 [(0, 3)]
1 [(1, 4)]
2 [(2, 5)]
0 [(0, 6)]
Sorted:
[(0, 0),
(0, 3),
(0, 6),
(1, 1),
(1, 4),
(2, 2),
(2, 5)]
Grouped, sorted:
0 [(0, 0), (0, 3), (0, 6)]
1 [(1, 1), (1, 4)]
2 [(2, 2), (2, 5)]
Combinando entradas¶
La función accumulate()
procesa el iterable de entrada, pasando el enésimo
y el elemento n+1 a una función y produciendo el valor de retorno en lugar de
cualquiera de las entradas. La función predeterminada es usada para combinar
los dos valores los agrega, por lo que accumulate()
se puede usar para
generar la suma acumulativa de una serie de entradas numéricas.
from itertools import *
print(list(accumulate(range(5))))
print(list(accumulate('abcde')))
Cuando se usa con una secuencia de valores no enteros, los resultados dependen
de lo que significa «agregar» dos elementos juntos. El segundo ejemplo en esta
secuencia de comandos muestra que cuando accumulate()
recibe una cadena de
entrada cada respuesta es un prefijo progresivamente más largo de esa cadena.
$ python3 itertools_accumulate.py
[0, 1, 3, 6, 10]
['a', 'ab', 'abc', 'abcd', 'abcde']
Es posible combinar accumulate()
con cualquier otra función que toma dos
valores de entrada para lograr resultados diferentes.
from itertools import *
def f(a, b):
print(a, b)
return b + a + b
print(list(accumulate('abcde', f)))
Este ejemplo combina los valores de cadena de una manera que hace una serie de
palíndromos (sin sentido). Cada paso del camino cuando f()
es invocada,
imprime los valores de entrada que le pasa accumulate()
.
$ python3 itertools_accumulate_custom.py
a b
bab c
cbabc d
dcbabcd e
['a', 'bab', 'cbabc', 'dcbabcd', 'edcbabcde']
Los bucles anidados for
que iteran sobre múltiples secuencias a menudo
pueden ser reemplazados por product()
, que produce un único iterable cuyo
valores son el producto cartesiano del conjunto de valores de entrada.
from itertools import *
import pprint
FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')
DECK = list(
product(
chain(range(2, 11), FACE_CARDS),
SUITS,
)
)
for card in DECK:
print('{:>2}{}'.format(*card), end=' ')
if card[1] == SUITS[-1]:
print()
Los valores producidos por product()
son tuplas, con los miembros tomados
de cada uno de los iterables pasados como argumentos en el orden en que son
pasados. La primera tupla devuelta incluye el primer valor de cada iterable.
El último iterable pasado a product()
es procesado primero, seguido de el
penúltimo, y así sucesivamente. El resultado es que los valores de retorno
están en orden basados en el primer iterable, luego el siguiente iterable, etc.
En este ejemplo, las tarjetas se ordenan por valor y luego por palo.
$ python3 itertools_product.py
2H 2D 2C 2S
3H 3D 3C 3S
4H 4D 4C 4S
5H 5D 5C 5S
6H 6D 6C 6S
7H 7D 7C 7S
8H 8D 8C 8S
9H 9D 9C 9S
10H 10D 10C 10S
JH JD JC JS
QH QD QC QS
KH KD KC KS
AH AD AC AS
Para cambiar el orden de las tarjetas, cambia el orden de los argumentos para
product()
.
from itertools import *
import pprint
FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')
DECK = list(
product(
SUITS,
chain(range(2, 11), FACE_CARDS),
)
)
for card in DECK:
print('{:>2}{}'.format(card[1], card[0]), end=' ')
if card[1] == FACE_CARDS[-1]:
print()
El bucle de impresión en este ejemplo busca una carta as, en lugar de juego de espadas, y luego agrega una nueva línea para dividir la salida.
$ python3 itertools_product_ordering.py
2H 3H 4H 5H 6H 7H 8H 9H 10H JH QH KH AH
2D 3D 4D 5D 6D 7D 8D 9D 10D JD QD KD AD
2C 3C 4C 5C 6C 7C 8C 9C 10C JC QC KC AC
2S 3S 4S 5S 6S 7S 8S 9S 10S JS QS KS AS
Para calcular el producto de una secuencia consigo mismo, especifica cuántas veces la entrada debe ser repetida.
from itertools import *
def show(iterable):
for i, item in enumerate(iterable, 1):
print(item, end=' ')
if (i % 3) == 0:
print()
print()
print('Repeat 2:\n')
show(list(product(range(3), repeat=2)))
print('Repeat 3:\n')
show(list(product(range(3), repeat=3)))
Como repetir un solo iterable es como pasar el mismo iterable varias veces,
cada tupla producida por product()
contendrá un número de elementos igual
al contador de repetición.
$ python3 itertools_product_repeat.py
Repeat 2:
(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)
Repeat 3:
(0, 0, 0) (0, 0, 1) (0, 0, 2)
(0, 1, 0) (0, 1, 1) (0, 1, 2)
(0, 2, 0) (0, 2, 1) (0, 2, 2)
(1, 0, 0) (1, 0, 1) (1, 0, 2)
(1, 1, 0) (1, 1, 1) (1, 1, 2)
(1, 2, 0) (1, 2, 1) (1, 2, 2)
(2, 0, 0) (2, 0, 1) (2, 0, 2)
(2, 1, 0) (2, 1, 1) (2, 1, 2)
(2, 2, 0) (2, 2, 1) (2, 2, 2)
La función permutations()
produce elementos a partir de el iterable de
entrada combinado en las posibles permutaciones de la longitud dada. Eso se
predetermina a producir el conjunto completo de todas las permutaciones.
from itertools import *
def show(iterable):
first = None
for i, item in enumerate(iterable, 1):
if first != item[0]:
if first is not None:
print()
first = item[0]
print(''.join(item), end=' ')
print()
print('All permutations:\n')
show(permutations('abcd'))
print('\nPairs:\n')
show(permutations('abcd', r=2))
Usa el argumento r
para limitar la longitud y el número de permutaciones
individuales devueltas.
$ python3 itertools_permutations.py
All permutations:
abcd abdc acbd acdb adbc adcb
bacd badc bcad bcda bdac bdca
cabd cadb cbad cbda cdab cdba
dabc dacb dbac dbca dcab dcba
Pairs:
ab ac ad
ba bc bd
ca cb cd
da db dc
Para limitar los valores a combinaciones únicas en lugar de permutaciones, usa
combinations()
. Siempre y cuando los miembros de la entrada sean únicos,
el resultado no incluirá ningún valor repetido.
from itertools import *
def show(iterable):
first = None
for i, item in enumerate(iterable, 1):
if first != item[0]:
if first is not None:
print()
first = item[0]
print(''.join(item), end=' ')
print()
print('Unique pairs:\n')
show(combinations('abcd', r=2))
A diferencia de las permutaciones, el argumento r
para combinations()
es requerido.
$ python3 itertools_combinations.py
Unique pairs:
ab ac ad
bc bd
cd
Mientras que combinations()
no repite elementos de entrada individuales, a
veces es útil considerar combinaciones que sí incluyen elementos repetidos.
Para esos casos, usa combinations_with_replacement()
.
from itertools import *
def show(iterable):
first = None
for i, item in enumerate(iterable, 1):
if first != item[0]:
if first is not None:
print()
first = item[0]
print(''.join(item), end=' ')
print()
print('Unique pairs:\n')
show(combinations_with_replacement('abcd', r=2))
En esta salida, cada elemento de entrada está emparejado consigo mismo y con todos los otros miembros de la secuencia de entrada.
$ python3 itertools_combinations_with_replacement.py
Unique pairs:
aa ab ac ad
bb bc bd
cc cd
dd
Ver también
- Documentación de la biblioteca estándar para itertools
- Notas para portar Python 2 a 3 para itertools
- La Biblioteca de base de Standard ML) – La biblioteca de SML.
- Definición de Haskell y las Bibliotecas Estándar – Especificación de la Biblioteca Estándar para el lenguaje funcional Haskell.
- Clojure – Clojure es un lenguaje funcional dinámico que se ejecuta en la máquina virtual de Java.
- tee – Herramienta Unix de línea de comandos para dividir una entrada en múltiples flujos de salida identicos.
- Producto cartesiano – Definición matemática del producto cartesiano de dos secuencias.