map(), filter() y reduce() en Python con ejemplos

En este tutorial, repasaremos ejemplos de las funciones map(), filter() y reduce() en Python, tanto con Lambdas como con funciones regulares.

Introducción

Las funciones map(), filter() y reduce() aportan un poco de programación funcional a Python. Las tres son funciones de conveniencia que se pueden reemplazar con Lista de comprensiones o bucles, pero brindan un enfoque más elegante y abreviado para algunos problemas.

Antes de continuar, repasaremos algunas cosas con las que debe estar familiarizado antes de leer sobre los métodos mencionados anteriormente:

¿Qué es una función/método anónimo o lambda?

Un método anónimo es un método sin nombre, es decir, no vinculado a un identificador como cuando definimos un método usando def method:.

Nota: Aunque la mayoría de las personas usan los términos "función anónima" y "función lambda" indistintamente, no son lo mismo. Este error ocurre porque en la mayoría de los lenguajes de programación las lambdas son anónimas y todas las funciones anónimas son lambdas. Este es también el caso en Python. Por lo tanto, no profundizaremos en esta distinción en este artículo.

¿Cuál es la sintaxis de una función lambda (u operador lambda)?

1
lambda arguments: expression

Piense en las lambdas como métodos de una línea sin nombre. Funcionan prácticamente igual que cualquier otro método en Python, por ejemplo:

1
2
def add(x,y):
    return x + y

Se puede traducir a:

1
lambda x, y: x + y

Las lambdas se diferencian de los métodos normales de Python porque solo pueden tener una expresión, no pueden contener declaraciones y su tipo de retorno es un objeto función. Entonces, la línea de código anterior no devuelve exactamente el valor x + y, sino la función que calcula x + y.

¿Por qué las lambdas son relevantes para map(), filter() y reduce()?

Estos tres métodos esperan un objeto función como primer argumento. Este objeto función puede ser un método predefinido con un nombre (como def add(x,y)).

Aunque, la mayoría de las veces, las funciones pasadas a map(), filter() y reduce() son las que usaría solo una vez, por lo que a menudo no tiene sentido definir un función referenciable.

Para evitar definir una nueva función para sus diferentes necesidades de map()/filter()/reduce(), una solución más elegante sería usar una función corta, desechable y anónima que solo usará una vez y nunca más - una lambda.

La función map()

La función map() itera a través de todos los elementos en el iterable dado y ejecuta la función que pasamos como argumento en cada uno de ellos.

La sintaxis es:

1
map(function, iterable(s))

Podemos pasar tantos objetos iterables como queramos después de pasar la función que queremos usar:

1
2
3
4
5
6
7
8
# Without using lambdas
def starts_with_A(s):
    return s[0] == "A"

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(starts_with_A, fruit)

print(list(map_object))

Este código dará como resultado:

1
[True, False, False, True, False]

Como podemos ver, terminamos con una nueva lista donde se evaluó la función comienza_con_A() para cada uno de los elementos de la lista fruta. Los resultados de esta función se agregaron a la lista secuencialmente.

Una forma más bonita de hacer exactamente lo mismo es usando lambdas:

1
2
3
4
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(lambda s: s[0] == "A", fruit)

print(list(map_object))

Obtenemos la misma salida:

1
[True, False, False, True, False]

Nota: Es posible que hayas notado que hemos enviado map_object a una lista para imprimir el valor de cada elemento. Hicimos esto porque llamar a print() en una lista imprimirá los valores reales de los elementos. Llamar a print() en map_object imprimiría las direcciones de memoria de los valores en su lugar.

La función map() devuelve el tipo map_object, que es iterable y podríamos haber impreso los resultados así también:

1
2
for value in map_object:
    print(value)

Si desea que la función map() devuelva una lista en su lugar, puede lanzarla al llamar a la función:

1
result_list = list(map(lambda s: s[0] == "A", fruit))

La función de filtro()

Similar a map(), filter() toma un objeto function y un iterable y crea una nueva lista.

Como sugiere el nombre, filter() forma una nueva lista que contiene solo elementos que satisfacen una determinada condición, es decir, la función que pasamos devuelve Verdadero.

La sintaxis es:

1
filter(function, iterable(s))

Usando el ejemplo anterior, podemos ver que la nueva lista solo contendrá elementos para los cuales la función comienza_con_A() devuelve Verdadero:

1
2
3
4
5
6
7
8
# Without using lambdas
def starts_with_A(s):
    return s[0] == "A"

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(starts_with_A, fruit)

print(list(filter_object))

Ejecutar este código dará como resultado una lista más corta:

1
['Apple', 'Apricot']

O, reescrito usando una lambda:

1
2
3
4
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(lambda s: s[0] == "A", fruit)

print(list(filter_object))

La impresión nos da el mismo resultado:

1
['Apple', 'Apricot']

La función reduce()

reduce() funciona de manera diferente a map() y filter(). No devuelve una nueva lista basada en la función e iterable que hemos pasado. En su lugar, devuelve un único valor.

Además, en Python 3 reduce() ya no es una función integrada y se puede encontrar en el módulo functools.

La sintaxis es:

1
reduce(function, sequence[, initial])

reduce() funciona llamando a la función que pasamos para los dos primeros elementos de la secuencia. El resultado devuelto por la función se usa en otra llamada a función junto con el siguiente elemento (el tercero en este caso).

Este proceso se repite hasta que hayamos pasado por todos los elementos de la secuencia.

El argumento opcional inicial se usa, cuando está presente, al principio de este "bucle" con el primer elemento en la primera llamada a función. En cierto modo, el elemento inicial es el elemento 0, antes del primero, cuando se proporciona.

reduce() es un poco más difícil de entender que map() y filter(), así que veamos un ejemplo paso a paso:

  1. Comenzamos con una lista [2, 4, 7, 3] y pasamos la función add(x, y) a reduce() junto a esta lista, sin un valor inicial

  2. reduce() llama a add(2, 4), y add() devuelve 6

  3. reduce() llama a add(6, 7) (resultado de la llamada anterior a add() y al siguiente elemento de la lista como parámetros), y add() devuelve 13

  4. reduce() llama a add(13, 3), y add() devuelve 16

  5. Dado que no quedan más elementos en la secuencia, reduce() devuelve 16

La única diferencia, si hubiéramos dado un valor inicial habría sido un paso adicional - 1.5. donde reduce() llamaría a add(initial, 2) y usaría ese valor devuelto en el paso * 2*.

Avancemos y usemos la función reduce():

1
2
3
4
5
6
7
from functools import reduce

def add(x, y):
    return x + y

list = [2, 4, 7, 3]
print(reduce(add, list))

Ejecutar este código produciría:

1
16

Nuevamente, esto podría escribirse usando lambdas:

1
2
3
4
5
from functools import reduce

list = [2, 4, 7, 3]
print(reduce(lambda x, y: x + y, list))
print("With an initial value: " + str(reduce(lambda x, y: x + y, list, 10)))

Y el código daría como resultado:

1
2
16
With an initial value: 26

Conclusión

Como se mencionó anteriormente, estas funciones son funciones de conveniencia. Están allí para que pueda evitar escribir un código más engorroso, pero evite usar demasiado tanto ellos como las expresiones lambda.

No fuerce estas herramientas porque "puede", ya que a menudo puede conducir a un código ilegible que es difícil de mantener. Úselos solo cuando esté absolutamente claro lo que está sucediendo tan pronto como observe la función o la expresión lambda.

Si te encuentras luchando para encajar la lógica necesaria en una función map(), o una expresión lambda, es mucho mejor escribir un método for-loop/defined un poco más largo y evitar confusiones innecesarias más adelante.

Licensed under CC BY-NC-SA 4.0