Ir al contenido principal

Funciones map, filter y reduce

Introducción

En la programación funcional, es común necesitar transformar, filtrar o combinar los elementos de una colección de datos. Python proporciona tres funciones fundamentales para estas operaciones: map(), filter() y reduce(). Estas funciones nos permiten trabajar con colecciones de datos de manera más declarativa y expresiva, centrándonos en qué queremos hacer y no tanto en cómo hacerlo paso a paso.

En este artículo, aprenderemos cómo utilizar estas tres poderosas herramientas y cómo pueden mejorar la eficiencia y legibilidad de nuestro código Python. Veremos ejemplos prácticos que ilustran su uso y compararemos estos enfoques funcionales con las alternativas imperativas tradicionales.

La función map()

La función map() aplica una función a cada elemento de un iterable (como una lista, tupla o cadena) y devuelve un objeto map, que puede ser convertido a una lista u otro iterable.

Sintaxis básica

map(funcion, iterable)
  • funcion: La función que se aplicará a cada elemento
  • iterable: Colección de elementos a procesar

Ejemplos prácticos

Ejemplo 1: Convertir cadenas a números

# Convertir una lista de cadenas a números enteros
cadenas = ["1", "2", "3", "4", "5"]
numeros = list(map(int, cadenas))
print(numeros)  # [1, 2, 3, 4, 5]

Ejemplo 2: Calcular el cuadrado de cada número

# Usando una función definida
def calcular_cuadrado(x):
    return x ** 2

numeros = [1, 2, 3, 4, 5]
cuadrados = list(map(calcular_cuadrado, numeros))
print(cuadrados)  # [1, 4, 9, 16, 25]

# O usando una función lambda (anónima)
cuadrados_lambda = list(map(lambda x: x ** 2, numeros))
print(cuadrados_lambda)  # [1, 4, 9, 16, 25]

Ejemplo 3: Trabajar con múltiples iterables

map() puede trabajar con múltiples iterables. En este caso, la función debe aceptar tantos argumentos como iterables se proporcionen:

# Sumar elementos de dos listas
lista1 = [1, 2, 3]
lista2 = [10, 20, 30]
suma = list(map(lambda x, y: x + y, lista1, lista2))
print(suma)  # [11, 22, 33]

Comparación con enfoque imperativo

Comparemos el uso de map() con un enfoque tradicional usando bucles:

# Enfoque funcional con map()
numeros = [1, 2, 3, 4, 5]
cuadrados_map = list(map(lambda x: x ** 2, numeros))

# Enfoque imperativo con bucle for
cuadrados_for = []
for num in numeros:
    cuadrados_for.append(num ** 2)
    
print(cuadrados_map)  # [1, 4, 9, 16, 25]
print(cuadrados_for)  # [1, 4, 9, 16, 25]

El enfoque con map() es más conciso y expresa directamente nuestra intención: aplicar una operación a cada elemento.

La función filter()

La función filter() crea un iterador con los elementos de un iterable para los cuales una función devuelve True.

Sintaxis básica

filter(funcion, iterable)
  • funcion: Función que devuelve True o False para cada elemento
  • iterable: Colección de elementos a filtrar

Ejemplos prácticos

Ejemplo 1: Filtrar números pares

# Filtrar números pares
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares)  # [2, 4, 6, 8, 10]

Ejemplo 2: Filtrar cadenas vacías

# Eliminar cadenas vacías de una lista
cadenas = ["hola", "", "Python", "", "programación", "funcional", ""]
no_vacias = list(filter(lambda x: x != "", cadenas))
print(no_vacias)  # ['hola', 'Python', 'programación', 'funcional']

# Alternativa más pythónica
no_vacias_alt = list(filter(bool, cadenas))
print(no_vacias_alt)  # ['hola', 'Python', 'programación', 'funcional']

Ejemplo 3: Filtrar elementos según una condición compleja

# Filtrar estudiantes aprobados (nota >= 5)
estudiantes = [
    {"nombre": "Ana", "nota": 8.5},
    {"nombre": "Luis", "nota": 4.7},
    {"nombre": "Carmen", "nota": 9.2},
    {"nombre": "Pedro", "nota": 3.8},
    {"nombre": "Sofia", "nota": 7.1}
]

aprobados = list(filter(lambda estudiante: estudiante["nota"] >= 5, estudiantes))
for estudiante in aprobados:
    print(f"{estudiante['nombre']}: {estudiante['nota']}")
# Ana: 8.5
# Carmen: 9.2
# Sofia: 7.1

Comparación con enfoque imperativo

# Enfoque funcional con filter()
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares_filter = list(filter(lambda x: x % 2 == 0, numeros))

# Enfoque imperativo con bucle for
pares_for = []
for num in numeros:
    if num % 2 == 0:
        pares_for.append(num)
        
print(pares_filter)  # [2, 4, 6, 8, 10]
print(pares_for)     # [2, 4, 6, 8, 10]

El enfoque con filter() comunica más claramente nuestra intención de seleccionar elementos que cumplen cierta condición.

La función reduce()

La función reduce() aplica cumulativamente una función a los elementos de un iterable, de izquierda a derecha, para reducirlo a un único valor. A diferencia de map() y filter(), reduce() no está disponible directamente y debe importarse del módulo functools.

Sintaxis básica

from functools import reduce
reduce(funcion, iterable[, inicial])
  • funcion: Función que toma dos argumentos (acumulador y elemento actual)
  • iterable: Colección de elementos a reducir
  • inicial: Valor inicial del acumulador (opcional)

Ejemplos prácticos

Ejemplo 1: Suma de todos los elementos

from functools import reduce

# Sumar todos los elementos de una lista
numeros = [1, 2, 3, 4, 5]
suma = reduce(lambda acum, valor: acum + valor, numeros)
print(suma)  # 15

# Con valor inicial
suma_con_inicio = reduce(lambda acum, valor: acum + valor, numeros, 10)
print(suma_con_inicio)  # 25 (10 + 1 + 2 + 3 + 4 + 5)

Ejemplo 2: Calcular el máximo valor

from functools import reduce

# Encontrar el valor máximo
numeros = [23, 12, 45, 67, 9, 34]
maximo = reduce(lambda x, y: x if x > y else y, numeros)
print(maximo)  # 67

# Equivalente a usar la función max()
print(max(numeros))  # 67

Ejemplo 3: Composición de funciones

from functools import reduce

# Composición de funciones
def componer(*funciones):
    def composed_function(x):
        return reduce(lambda valor, funcion: funcion(valor), funciones, x)
    return composed_function

# Funciones individuales
def duplicar(x):
    return x * 2

def incrementar(x):
    return x + 1

def cuadrado(x):
    return x ** 2

# Crear una composición: f(x) = cuadrado(incrementar(duplicar(x)))
f = componer(duplicar, incrementar, cuadrado)

print(f(5))  # 121 (((5*2)+1)^2)

Cómo funciona reduce()

Para entender mejor cómo funciona reduce(), veamos paso a paso su ejecución en el primer ejemplo:

reduce(lambda acum, valor: acum + valor, [1, 2, 3, 4, 5])

Pasos:
1. acum = 1, valor = 2 → acum = 3
2. acum = 3, valor = 3 → acum = 6
3. acum = 6, valor = 4 → acum = 10
4. acum = 10, valor = 5 → acum = 15

Resultado: 15

Si proporcionamos un valor inicial:

reduce(lambda acum, valor: acum + valor, [1, 2, 3, 4, 5], 10)

Pasos:
1. acum = 10, valor = 1 → acum = 11
2. acum = 11, valor = 2 → acum = 13
3. acum = 13, valor = 3 → acum = 16
4. acum = 16, valor = 4 → acum = 20
5. acum = 20, valor = 5 → acum = 25

Resultado: 25

Comparación con enfoque imperativo

from functools import reduce

# Enfoque funcional con reduce()
numeros = [1, 2, 3, 4, 5]
producto_reduce = reduce(lambda x, y: x * y, numeros)

# Enfoque imperativo con bucle for
producto_for = 1
for num in numeros:
    producto_for *= num
    
print(producto_reduce)  # 120 (1*2*3*4*5)
print(producto_for)     # 120 (1*2*3*4*5)

Combinando map, filter y reduce

Estas funciones pueden combinarse para crear operaciones de procesamiento de datos más complejas:

from functools import reduce

# Lista de precios de productos
precios = [29.99, 10.50, 55.00, 7.95, 15.75, 49.99, 12.25]

# Calcular el total de los productos con precio > 20, con 10% de descuento
total = reduce(
    lambda acum, precio: acum + precio,
    map(
        lambda precio: precio * 0.9,  # Aplicar 10% de descuento
        filter(
            lambda precio: precio > 20,  # Filtrar precios > 20
            precios
        )
    )
)

print(f"Total con descuento: {total:.2f} €")  # Total con descuento: 121.78 €

Este ejemplo muestra cómo:

  1. Primero filtramos los precios mayores de 20 con filter()
  2. Luego aplicamos un 10% de descuento a cada precio con map()
  3. Finalmente, sumamos todos los precios con reduce()

Alternativas a map, filter y reduce

Python ofrece otras formas de implementar estas operaciones, a menudo más legibles:

Comprensión de listas

numeros = [1, 2, 3, 4, 5]

# map con comprensión de listas
cuadrados = [x**2 for x in numeros]

# filter con comprensión de listas
pares = [x for x in numeros if x % 2 == 0]

print(cuadrados)  # [1, 4, 9, 16, 25]
print(pares)      # [2, 4]

Funciones integradas

numeros = [1, 2, 3, 4, 5]

# reduce para suma → función sum()
suma = sum(numeros)

# reduce para producto
from functools import reduce
producto = reduce(lambda x, y: x * y, numeros)
# No hay equivalente directo en funciones integradas

print(suma)      # 15
print(producto)  # 120

Resumen

En este artículo hemos explorado las funciones map(), filter() y reduce(), herramientas fundamentales de la programación funcional en Python. Estas funciones nos permiten transformar, filtrar y combinar datos de manera concisa y expresiva, lo que resulta en código más limpio y mantenible.

Aunque Python ofrece alternativas como las comprensiones de listas que pueden ser más legibles en muchos casos, comprender estas funciones funcionales nos proporciona mayor flexibilidad y nos ayuda a pensar en términos de transformaciones de datos. En el próximo artículo, exploraremos los decoradores, otra poderosa característica de Python relacionada con la programación funcional.