Itertools de Python: cuenta (), ciclo () y cadena ()

En este tutorial, echaremos un vistazo al módulo `itertools` en Python y veremos ejemplos de count(), cycle() y eval().

Introducción

Python tiene muchas herramientas integradas que nos permiten iterar y transformar datos. Un gran ejemplo es el módulo itertools, que ofrece varias funciones de iteración convenientes. Cada una de estas funciones de construcción de iteradores (generan iteradores) se puede usar sola o combinada.

El módulo se inspiró en lenguajes funcionales como APL, Haskell y SPL y los elementos dentro de itertools forman el álgebra iteradora de Python.

Iterable vs Iterador

Antes de sumergirnos en la iteración, primero definamos la distinción entre dos términos importantes: iterable e iterador.

Un iterable es un objeto sobre el que se puede iterar. Cuando se utiliza la función iter(), se genera un iterador. En términos generales, la mayoría de las secuencias son iterables, como listas, tuplas, cadenas, etc.

Un iterador también es un objeto, que se usa para iterar sobre un iterable y un iterador también puede iterar sobre sí mismo. Esto se hace usando el método next(), pasando el iterador que estamos tratando de atravesar.

El método next() devuelve el siguiente elemento de un objeto iterable. Se puede generar un iterador a partir de un iterable (usando iter()):

1
2
3
4
list = [1,2,3,4,5]
iterator = iter(list)

print(iterator)

Esto resulta en:

1
<list_iterator object at 0x0000018E393A0F28>

Ahora, accedamos al elemento next() (empezando por el primero) usando nuestro iterador:

1
print(next(iterator))

Esto resulta en:

1
1

Esto es prácticamente lo que sucede bajo el capó del bucle for: llama a iter() en la colección sobre la que está iterando y, después de eso, se accede n veces al elemento next(). .

En este tutorial, veremos algunas herramientas de iteración de Python:

La función de conteo()

La función count(start, step) crea un iterador y se usa para generar valores espaciados uniformemente, donde el espacio entre ellos está definido por el argumento step. El argumento start define el valor inicial del iterador, y estos se establecen en start=0 y step=1 de forma predeterminada.

Sin una condición de ruptura, la función count() continuará contando indefinidamente (en un sistema con memoria indefinida):

1
2
3
4
5
6
7
8
from itertools import count

iterator_count = count(start=0, step=5)

for i in iterator_count:
    if(i == 25):
        break
    print(i)

Nota: Usar count() de esta manera es inusual. Normalmente lo encadenarías con otros métodos, como zip(), map() o imap().

El iterador itera sobre sí mismo aquí, imprimiendo valores en pasos de 5:

1
2
3
4
5
0
5
10
15
20

Dada su naturaleza generativa, esta función se usa más comúnmente con otras funciones que esperan nuevas o generan secuencias.

Por ejemplo, al usar zip() para comprimir varios elementos de una lista, es posible que desee anotarlos a través de un índice posicional. Al comprimir, usaríamos count() para generar valores para estos índices:

1
2
3
4
5
from itertools import count

list = ['John', 'Marie', 'Jack', 'Anna']
for i in zip(count(), list):
    print(i)

Lo que resulta en:

1
2
3
4
(0, 'John')
(1, 'Marie')
(2, 'Jack')
(3, 'Anna')

Si desea leer más sobre la función zip(), así como algunas otras funciones de uso común junto con ella, lea nuestra guía sobre [Herramientas de iteración de Python: filter(), islice(), map(]) y zip()](/the-python-iteration-tools-filter-the-islice-and-zip-map/).

La función ciclo()

La función cycle() acepta un iterable y genera un iterador, que contiene todos los elementos del iterable. Además de estos elementos, contiene una copia de cada elemento.

Una vez que iteramos hasta el final del elemento, comenzamos a iterar a través de las copias. Mientras se itera a través de las copias, se crean nuevas copias. Una vez que se agota el primer conjunto de copias, iteramos a través del nuevo conjunto.

Este proceso se repite indefinidamente.

Nota: Teniendo en cuenta este hecho, usar cycle(), especialmente para secuencias más largas, consume mucha memoria. Tenga cuidado con la lógica de creación infinita y recursiva, ya que fácilmente se quedará sin memoria para albergarlo todo:

1
2
3
4
5
6
7
from itertools import cycle

list = [1,2,3,4]
iterator = cycle(list)

for i in iterator:
    print(i)

Esto resulta en:

1
2
3
4
5
6
7
8
9
1
2
3
4
1
2
3
4
...

Hasta que finalicemos el programa o nos quedemos sin memoria. Dicho esto, siempre debe tener una condición de salida/terminación para la función cycle().

Dado el hecho de que cycle() puede recorrer cualquier iterable, también podemos aplicarlo fácilmente a cadenas y tuplas:

1
2
3
4
5
6
7
from itertools import cycle

string = "This is a random string"
iterator = cycle(string)

for i in iterator:
    print(i)

Esto da como resultado una secuencia interminable de:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
T
h
i
s
i
s
a
r
a
n
d
o
...

La función cadena()

La función chain() se usa para encadenar varios iterables juntos, generando un iterador que los atraviesa secuencialmente, uno tras otro:

1
2
3
4
5
6
result = list(chain([1, 2, 3], 
        ["one", "two", "three"], 
        "String", 
        ("this", "is", "a", "tuple")))
        
print(result)

La salida será:

1
[1, 2, 3, 'one', 'two', 'three', 'S', 't', 'r', 'i', 'n', 'g', 'this', 'is', 'a', 'tuple']

Aquí, tenemos cuatro tipos diferentes de iterables, cada uno encadenado.

Aunque ["uno", "dos", "tres"] es una lista de cadenas, chain() trata esto como una lista y simplemente encadena sus elementos sin llamar a una subsiguiente chain() para cada uno de ellos. las cuerdas. Por otro lado, "String" se descompone en sus caracteres constituyentes.

Lo primero se puede lograr con otro método, derivado de la función chain() - chain.from_iterable():

1
2
3
4
5
result2 = list(chain(["one", "two", "three"]))
result3 = list(chain.from_iterable(["one", "two", "three"]))

print(result2)
print(result3)

La función chain() se comporta de la misma manera que hemos observado anteriormente: encadena los elementos tal como son. Por otro lado, el método chain.from_iterable() trata cada elemento como un iterable y devuelve sus elementos constituyentes junto con otros elementos desglosados ​​de la misma manera:

1
2
['one', 'two', 'three']
['o', 'n', 'e', 't', 'w', 'o', 't', 'h', 'r', 'e', 'e']

Comúnmente, usaría chain.from_iterable() para calcular la suma de dígitos, contenidos dentro de varias colecciones que primero encadena, y luego calcula sum() para:

1
2
3
4
5
from itertools import chain

number_list = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
result = list(chain.from_iterable(number_list))
print(sum(result))

Cada elemento de la colección number_list es otra lista. Dado que las listas son iterables, la llamada chain.from_iterable() las divide en una sola lista que contiene elementos de [1..9], después de lo cual calculamos su sum() e imprimimos el resultado:

1
45

Conclusión

El módulo itertools nos presenta varias funciones de conveniencia útiles para trabajar con iterables e iteraciones.

Muchos de estos se pueden usar como funciones de conveniencia independientes, pero generalmente se encadenan con otras funciones para transformar datos.

Licensed under CC BY-NC-SA 4.0