Itertools de Python - compress(), dropwhile(), takewhile() y groupby()

En esta guía, profundizaremos en cómo usar las funciones compress(), dropwhile(), takewhile() y groupby() del módulo itertools de Python.

Introducción

En esta guía, veremos cómo aprovechar el poder de los iteradores usando el módulo itertools de Python.

El módulo itertools nos proporciona una interfaz para crear iteradores rápidos y eficientes en memoria. Estos iteradores pueden ser infinitos, combinatorios o terminantes.

Iterador vs Iterable

Un Iterador es un puntero inteligente que puede guiarnos (iterar) a través de los elementos de un Iterable (contenedor) en un cierto orden. Considere una lista de colores, así como una lista de números enteros:

1
2
colors = ['red', 'blue', 'pink']
ints = [1, 3, 5, 4, 2]

Aunque definimos estas listas en un orden particular, no tienen que almacenarse en el mismo orden cuando se colocan en la memoria:

1
2
3
iterators:  it1                 it2 
             V                   V
memory:     red   4   2   blue   1    3    pink   5

Si revisamos la memoria en orden, obtendríamos que el segundo elemento de la matriz colors es 4, por lo que necesitamos iteradores.

El trabajo del iterador es encontrar el siguiente elemento de la lista en la memoria, sin importar dónde se encuentre. Esto se hace a través del método next() que devuelve el siguiente elemento al que apunta el iterador. it1 recorrería la memoria a la que tiene acceso y devolvería blue, mientras que it2 devolvería 3.

Una gran característica de los iteradores es que podemos definir cómo buscan elementos en sus respectivos iterables. Podemos, por ejemplo, pedirle que salte todos los números impares y devuelva un subconjunto. Esto se logra implementando un método next() personalizado o usando las itertools integradas que nos permiten generar un iterador específico para iterar a través de objetos de varias maneras.

Las herramientas de iteración que revisaremos son:

  • comprimir()
  • dejar caer()
  • ``mientras()`'
  • agrupar por ()

Cada una de estas funciones de creación de iteradores (generan iteradores) se puede usar sola o combinada.

La función comprimir()

La función compress(data, selector) crea un iterador que selecciona selectivamente los valores de data según la lista booleana - selector. Si un valor de data corresponde a un valor True en la lista selector, se seleccionará y se omitirá de lo contrario.

Si data y selector no tienen el mismo tamaño, compress() se detiene cuando se agotan las listas data o selector:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Importing the compress tool
from itertools import compress


cars = ['Audi', 'Volvo', 'Benz', 
        'BMW', 'Nissan', 'Mazda',
        'Ford']
        
selector = [True, True, False, False, 
            False, True, False]

# This makes an iterator that filters elements, 
# from data, for which selector values amount to True
my_cars = compress(cars, selector)

for each in my_cars:
    print(each)

Esto resulta en:

1
2
3
Audi
Volvo
Mazda

El selector también puede ser una lista de 1's y 0's, o cualquier valor verdadero/falso.

Por lo general, adquiere estas listas booleanas a través de algún tipo de condición, como:

1
2
3
4
5
6
7
8
int_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
boolean_list = [True if x % 2 == 0 else False for x in int_list]

# OR

boolean_list = [1 if x % 2 == 0 else 0 for x in int_list]

print(boolean_list)

Aquí, hemos generado una lista_booleana con un Verdadero para cada número par:

1
2
3
4
5
[False, True, False, True, False, True, False, True, False, True]

# OR

[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]

Por lo general, para acortar las cosas, usará la herramienta compress(), así como otras herramientas, sin asignar los resultados a una nueva variable:

1
2
3
4
5
6
7
import itertools

word =  'wikihtp'
selector = [1, 0, 1, 0, 0, 0, 0, 1, 1, 1]

for each in itertools.compress(word, selector ):
    print(each)

El resultado es:

1
2
3
4
5
S
A 
U 
S 
E

Además, técnicamente, podemos mezclar y combinar los valores en el selector con cualquier valor verdadero/falso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from itertools import compress

cars = ['Audi', 'Volvo', 'Benz',
        'BMW', 'Nissan', 'Mazda', 'Ford']

# Empty string is falsy, non empty is truthy
selector = [True, 1, 0, 0, '', 1, 'string']

for each in compress(cars, selector):
    print(each)

La salida es:

1
2
3
4
Audi
Volvo
Mazda
Ford

Sin embargo, vale la pena señalar que mezclar manzanas y peras de esta manera no se recomienda.

La función dropwhile()

La función dropwhile(criterios, secuencia) crea un iterador que descarta (omite) cada elemento en la secuencia, que devuelve Verdadero cuando se pasa a través de la función criterios.

La función criterios suele ser una función lambda, pero no tiene por qué serlo. Por lo general, si es una función simple, se abrevia como lambda, mientras que las funciones complejas no lo son:

1
2
3
4
5
6
from itertools import dropwhile

int_list = [0, 1, 2, 3, 4, 5, 6]
result = list(dropwhile(lambda x : x < 3, int_list))

print(result)

Dada esta función lambda, todos los elementos con un valor inferior a ‘3’ devolverán ‘Verdadero’, por lo que todos los elementos inferiores a 3 se omiten. Se eliminan mientras que los criterios son verdaderos:

1
[3, 4, 5, 6]

En lugar de una función lambda, podemos definir una más complicada y pasarle una referencia:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from itertools import dropwhile

def doesnt_contain_character(str):
    substring = 'a'
    if substring in str:
        return False
    else:
        return True
        
string_list = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
print(list(dropwhile(doesnt_contain_character, string_list)))

Por ejemplo, este método verifica si una cadena no contiene una subcadena - en este caso, solo a. Si la cadena dada contiene a, se devuelve False y si no la contiene, se devuelve True. Por lo tanto, todas las palabras de la secuencia, hasta amet, devuelven True y se eliminan del resultado:

1
['amet']

Sin embargo, se incluirán todos los elementos después de que falle el criterio. En nuestro caso, se incluirá todo lo que esté después del elemento 'amet', independientemente de los criterios:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from itertools import dropwhile

def doesnt_contain_character(str):
    substring = 'a'
    if substring in str:
        return False
    else:
        return True
        
string_list = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'a', 'b']
print(list(dropwhile(doesnt_contain_character, string_list)))

Esto suelta los elementos hasta 'amet' y deja de soltarlos después de eso:

1
['amet', 'a', 'b']

La función takewhile()

La función takewhile(criterios, secuencia) es el polo opuesto de dropwhile(). Conserva todos los elementos para los que la función no falla. Reescribamos el ejemplo anterior para comprobar si una palabra contiene un determinado carácter:

Vamos a ver:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from itertools import takewhile

def contains_character(str):
    substring = 'o'
    if substring in str:
        return True
    else:
        return False
        
string_list = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
print(list(takewhile(contains_character, string_list)))
1
['lorem']

Dado que criteria falla en el segundo elemento, aunque 'dolor' también contiene el carácter o, no se tiene en cuenta.

La función groupby()

El groupby(iterable, key_function) es una función que genera un iterador que agrupa elementos consecutivos que pertenecen al mismo grupo. Que un elemento pertenezca o no a un grupo depende de la key_function. Calcula el valor clave para cada elemento, el valor clave en este caso es el id de un grupo específico.

Un clúster finaliza y se crea uno nuevo cuando key_function devuelve una nueva identificación, incluso si se ha visto antes.

Si no se especifica key_function, entonces por defecto es la función de identidad. Sin embargo, vale la pena señalar que incluso con valores duplicados, no se agruparán si están separados por otro grupo:

1
2
3
4
5
6
from itertools import groupby

word = "aaabbbccaabbbbb"

for key, group in groupby(word):
    print(key, list(group))

Intuitivamente, puede esperar que todas las instancias de a y b se agrupen juntas, pero dado que hay grupos entre ellas, se separan en grupos propios:

1
2
3
4
5
a ['a', 'a', 'a'] 
b ['b', 'b', 'b'] 
c ['c', 'c'] 
a ['a', 'a'] 
b ['b', 'b', 'b', 'b', 'b']

Nota: La única forma de evitar esto es ordenar previamente el iterable según las claves.

Ahora, definamos una key_function personalizada, que puede ser una función lambda o dedicada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from itertools import groupby

some_list = [("Animal", "cat"), 
          ("Animal", "dog"),
          ("Animal", "lion"),
          ("Plant", "dandellion"),
          ("Plant", "blumen")]
  
for key, group in groupby(some_list, lambda x : x[0]):
    key_and_group = { key : list(group) }
    print(key_and_group)

Hemos hecho una lista de tuplas, donde el primer elemento denota una categorización general, si una entrada es un Animal o una Planta, y el segundo elemento denota el nombre de un animal o una planta.

Luego, los agrupamos según el primer elemento e imprimimos cada elemento de la secuencia:

1
2
{'Animal': [('Animal', 'cat'), ('Animal', 'dog'), ('Animal', 'lion')]}
{'Plant': [('Plant', 'dandellion'), ('Plant', 'blumen')]}

Conclusión

En esta guía, hemos echado un vistazo a las herramientas de iteración compress(), dropwhile(), takewhile() y groupby() en el módulo integrado itertools de Python. .

Si desea obtener más información sobre el módulo itertools y los iteradores en general, no dude en consultar nuestras otras guías:

Licensed under CC BY-NC-SA 4.0