Iteradores

Una de las cosas más maravillosas de las compus es que podemos repetir un mismo cálculo para muchos valores de forma automática. Ya hemos visto al menos un iterator (iterador), que no es una lista... es otro objeto.


In [2]:
for i in range(10):
    print(i, end=' ')


0 1 2 3 4 5 6 7 8 9 

Pero range, la verdad no es una lista. Es un iterador y aprender cómo funciona es útil en varios ámbitos.

Iterar sobre listas


In [3]:
for value in [2, 4, 6, 8, 10]:
    # do some operation
    print(value + 1, end=' ')


3 5 7 9 11 

En este caso, lo primero que hace el iterador es chequear si el objeto del otro lado del in es un iterador. Esto se puede chequear con la función iter, parecida a type.


In [4]:
iter([2, 4, 6, 8, 10])


Out[4]:
<list_iterator at 0x7f94b05a3c88>

In [5]:
I = iter([2, 4, 6, 8, 10])

In [6]:
print(next(I))


2

In [7]:
print(next(I))


4

range(): Una lista no siempre es una lista

range() como una lista, expone un iterador:


In [8]:
range(10)


Out[8]:
range(0, 10)

In [9]:
iter(range(10))


Out[9]:
<range_iterator at 0x7f94b05ef9c0>

Y es así como Python lo trata como si fuera una lista:


In [10]:
N = 10 ** 12
for i in range(N):
    if i >= 10: break
    print(i, end=', ')


0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 

Si una lista fuera a crear un trillón de valores ($10^{12}$), necesitaríamos terabytes de memoria para almacenarlos.

Algunos iteradores útiles

enumerate

Algunas veces queremos no solo iterar sobre los valores en una lista, sino también imprimir el índice de ellos.


In [11]:
L = [2, 4, 6, 8, 10]
for i in range(len(L)):
    print(i, L[i])


0 2
1 4
2 6
3 8
4 10

Pero hay una sintaxis más limpia para esto:


In [12]:
for i, val in enumerate(L):
    print(i, val)


0 2
1 4
2 6
3 8
4 10

zip

La función zip itera sobre dos iterables y produce una tupla:


In [13]:
L = [2, 4, 6, 8, 10]
R = [3, 6, 9, 12, 15]
for lval, rval in zip(L, R):
    print(lval, rval)


2 3
4 6
6 9
8 12
10 15

Si las listas son de diferente largo, el largo del zip va a estar dado por la lista más corta.

map y filter

Un poco más intenso: el iterador map toma una función y la aplica sobre todos los valores de un iterador:


In [14]:
# find the first 10 square numbers
square = lambda x: x ** 2
for val in map(square, range(10)):
    print(val, end=' ')


0 1 4 9 16 25 36 49 64 81 

El iterador filter toma una función y la aplica sobre todos los valores de un iterador devolviendo sólo aquellos valores que "pasan" el filtro.


In [15]:
# find values up to 10 for which x % 2 is zero
is_even = lambda x: x % 2 == 0
for val in filter(is_even, range(10)):
    print(val, end=' ')


0 2 4 6 8 

iteradores especializados: itertools

Ya vimos el count de itertools. Este módulo contiene un montón de funciones útiles. Por ejemplo, aqui veremos itertools.permutations, itertools.combinations, itertools.product.


In [16]:
from itertools import permutations
p = permutations(range(3))
print(*p)


(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)

In [18]:
from itertools import combinations
c = combinations(range(4), 2)
print(*c)


(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)

In [19]:
from itertools import product
p = product('ab', range(3))
print(*p)


('a', 0) ('a', 1) ('a', 2) ('b', 0) ('b', 1) ('b', 2)