Pensamiento Computacional · Unidad 4 — Estructuras de datos
map, filter y lambda
map le aplica una función a cada elemento; filter se queda con los elementos para los que la función da True. Hacen lo mismo que un for con append, en una línea.
La explicación
Una función también es un dato
En el tema 4.3 ya apareció una función pasada a otra: sorted(palabras, key=len).
Ahí len va sin paréntesis: no se la llama, se la entrega para que
sorted la use con cada elemento. map y filter funcionan
con la misma idea: reciben una función y una secuencia, y se encargan de recorrerla.
map: transformar cada elemento
return x * 2
print(list(map(doble, [3, 5, 8])))
map(funcion, secuencia) le aplica la función a cada elemento y junta los
resultados. La lista nueva tiene siempre la misma cantidad de elementos que la original; lo que
cambia es cada valor. La original no se toca.
list(...). map no devuelve una lista sino un
objeto de tipo map, que va entregando los resultados de a uno cuando se lo recorre.
Si se lo imprime directo, se ve algo como <map object at 0x7f...>, que no dice
nada. Con list(...) se recorre entero y queda una lista. También se lo puede recorrer
con un for, como a cualquier secuencia.
filter: quedarse con algunos
return x % 2 == 0
print(list(filter(es_par, [3, 4, 7, 10])))
filter(funcion, secuencia) le pregunta a la función por cada elemento y se queda con
los que dan True. Los elementos que quedan no cambian: salen tal cual
estaban, en el mismo orden. Por eso la función de un filter tiene que contestar una pregunta
(devolver un booleano), y la de un map tiene que devolver el valor transformado.
if. Si la función devuelve un número en vez de un booleano, el 0 cuenta
como falso y cualquier otro número como verdadero. Una función que devuelve x % 2
deja pasar los impares (dan resto 1), justo al revés de lo que sugiere su nombre si se
llama es_par. Para no tropezar: que la función de un filter termine siempre en una
comparación.
Cómo reconocer cuál usar
¿quiero quedarme con algunos? → filter (igual o más corta)
¿las dos cosas? → primero filter, después map
Se pueden encadenar, porque lo que devuelve uno es una secuencia que el otro puede recorrer:
return x > 0
def cuadrado(x):
return x * x
print(list(map(cuadrado, filter(positivo, [3, -1, 0, 2]))))
El orden importa. Al revés, filter(positivo, map(cuadrado, ...)), el
−1 primero se eleva al cuadrado, pasa a ser 1, y el filtro lo deja pasar: da
[9, 1, 4]. Se filtra mirando el dato original, y recién después se transforma.
Las dos sirven con cualquier secuencia, no sólo con listas: list(map(int, "2026"))
recorre el texto letra por letra y da [2, 0, 2, 6], y
list(map(int, "3 8 5".split())) convierte de una todos los números de un renglón
leído con input().
lambda: una función sin nombre
Escribir un def de dos líneas para usarlo una sola vez es mucho. Para eso está
lambda, que arma una función de una sola expresión en el mismo lugar donde se
la usa:
print(list(map(lambda p: p + 100, precios)))
print(list(filter(lambda p: p < 2000, precios)))
[1500, 700]
A la izquierda de los dos puntos van los parámetros y a la derecha lo que devuelve; no lleva
return. Sirve igual para el key de sorted:
sorted(alumnos, key=lambda a: a[1]) ordena una lista de tuplas por su segundo dato.
Si la cuenta necesita más de una línea, o se va a usar en varios lugares, conviene un
def con nombre.
Tres formas de escribir lo mismo
dobles = []
for x in L:
dobles.append(x * 2)
# con map
dobles = list(map(lambda x: x * 2, L))
# con una lista por comprensión
dobles = [x * 2 for x in L]
Y para filtrar, list(filter(lambda x: x > 4, L)) es lo mismo que
[x for x in L if x > 4]. La lista por comprensión (que ya apareció en el tema
4.1) hace map y filter a la vez: lo de la izquierda es la transformación, el if del
final es el filtro. Cuando un ejercicio pide «sin usar ciclos» o «usando map y filter», es para
practicar esta forma de pensar: describir qué se le hace a cada elemento, sin escribir
el recorrido a mano.
El map que se gasta
return x ** 2
c = map(cuad, [1, 2, 3])
print(list(c))
print(list(c))
[]
map o filter se puede recorrer una sola vez: entrega los
resultados a medida que se los piden y no los guarda. El primer list(c) lo vacía, y
el segundo encuentra que no queda nada. Si se lo va a usar más de una vez, se lo convierte en
lista una vez y se guarda esa lista: c = list(map(cuad, [1, 2, 3])).
Dada notas = [3, 7, 10, 5, 9], armar con filter la lista de las notas aprobadas (4 o más) y después, con map, subirle un punto a cada una de ésas, sin pasar de 10.
Dos pasos, uno por herramienta. Primero se decide cuáles quedan (una pregunta: filter) y después se cambia cada una (una transformación: map).
def aprobo(n):
return n >= 4
def subir(n):
return min(n + 1, 10)
aprobadas = list(filter(aprobo, notas))
print(aprobadas)
print(list(map(subir, aprobadas)))
[8, 10, 6, 10]
El 3 se queda afuera porque aprobo(3) da False. Las otras cuatro pasan, en
el mismo orden y sin cambiar.
min(n + 1, 10). Con n + 1 a secas, el 10 pasaría a
11, que no es una nota. min elige el menor entre el número subido y el tope: si
n + 1 se pasa de 10, gana el 10.
Lo mismo con lambda y con una lista por comprensión, para comparar:
[min(n + 1, 10) for n in notas if n >= 4]
Control: la lista final no puede ser más larga que la de aprobadas (map no agrega ni saca elementos), y ningún valor puede pasar de 10. Las dos se cumplen: cuatro notas, la más alta 10.
Un almacén guarda sus productos como tuplas (nombre, precio, stock): productos = [("yerba", 2500, 0), ("galletitas", 900, 12), ("queso", 4100, 0), ("pan", 1300, 0), ("leche", 1100, 5)] Usando filter, sorted y map, armar la lista de los nombres en mayúscula de los productos sin stock, ordenados del más barato al más caro.
Tres operaciones, y el orden en que se hacen es la mitad del ejercicio: filtrar los que no tienen stock, ordenarlos por precio, y recién al final quedarse con el nombre en mayúscula.
("pan", 1300, 0), ("leche", 1100, 5)]
def sin_stock(p):
return p[2] == 0
def precio(p):
return p[1]
def nombre_mayus(p):
return p[0].upper()
faltan = sorted(filter(sin_stock, productos), key=precio)
print(faltan)
print(list(map(nombre_mayus, faltan)))
['PAN', 'YERBA', 'QUESO']
Paso 1, filter. sin_stock mira el tercer dato de cada tupla (la posición 2).
Quedan yerba, queso y pan, con las tuplas enteras: filter no las cambia.
Paso 2, sorted con key. sorted acepta lo que devuelve filter sin pasarlo por
list(), porque sólo necesita poder recorrerlo, y devuelve una lista. El key=precio
hace que compare por el segundo dato: 1300, 2500, 4100.
Paso 3, map. Cada tupla se reemplaza por su nombre en mayúscula. Va al final porque después de este paso ya no hay precio para ordenar ni stock para filtrar: sólo quedan textos.
La versión con lambda, sin funciones con nombre:
nombres = list(map(lambda p: p[0].upper(), faltan))
El control: son tres productos sin stock (los que tienen un 0 al final), así que la respuesta tiene que tener tres nombres; y leyendo los precios a mano, 1300 < 2500 < 4100, el orden es pan, yerba, queso ✓
Dónde se pierde el punto: hacer el map antes de ordenar. Con
sorted(map(nombre_mayus, filter(sin_stock, productos))) lo que se ordena son los
nombres, alfabéticamente, y da ['PAN', 'QUESO', 'YERBA']: los mismos tres, en el
orden equivocado. El otro error típico es filtrar con p[1] == 0, que pregunta por
el precio y no deja pasar a ninguno.
Dónde se cae la mayoría
- Pasar la función con paréntesis, map(doble(), L): eso la llama sin argumentos y da TypeError. Va el nombre solo, map(doble, L).
- Imprimir el map o el filter sin list() y ver <map object at ...> en vez de los valores.
- Usar en filter una función que devuelve el valor transformado en vez de True o False: filter no cambia los elementos, sólo decide cuáles quedan.
- Recorrer dos veces el mismo objeto map o filter: la segunda vez está vacío.
Hasta acá la explicación. La práctica es la otra mitad: 8 ejercicios de este tema, en cuatro niveles, que no te dicen sólo si está bien al final sino que te corrigen en cada paso y te explican por qué. Y uno para escribir el programa vos, que se prueba ejecutándolo en Python ahí mismo, en el navegador. Además trae una animación que muestra la idea en movimiento.
Practicar map, filter y lambda →Los otros temas de la unidad 4
Estructuras de datos