Una de las principales características de Python es que este es un lenguaje multiparadigma. Podemos desarrollar proyectos utilizando un enfoque orientado a objetos, imperativo e inclusive (en menor medida) funcional.
🐋
En este post trabajaremos con las tres funciones más populares en cuanto a programación funcional se refiere. Estamos hablando de la función map, filter y reduce.
Funciones
Antes de entrar de lleno con el tema de programación funcional hagamos un rápido repaso sobre las funciones en Python.
Para este post estaré trabajando con la versión 3.6.
Algo interesante de las funciones en Python es que estas pueden ser asignadas a variables, lo cual nos abre la puerta a que funciones puedan ser usadas como argumento de otras funciones y que funciones retornen funciones.
Aquí un ejemplo.
def suma(val1=0, val2=0):
return val1 + val2
def operacion(funcion, val1=0, val2=0):
return funcion(val1, val2)
funcion_suma = suma
resultado = operacion(funcion_suma, 10, 20)
print(resultado)
Almacenamos nuestra función suma en la variable funcion_suma, variable que es utilizada como argumento en la función operacion. Esta función se encarga de ejecutar nuestro argumento y retorna el resultado de la operación. Con esto nuestra función operacion puede ser utilizada para ejecutar sumas, restas, multiplicaciones o cualquier tipo de operación que necesitemos. Esta función puede actuar como un wrapper 😎.
Un ejemplo más.
def crear_funcion(operador):
if operador == '+':
def suma(val1=0, val2=0):
return val1 + val2
return suma
def operacion(funcion, val1=0, val2=0):
return funcion(val1, val2)
funcion_suma = crear_funcion('+')
resultado = operacion(funcion_suma, 10, 20)
print(resultado)
En esta ocasión definimos la función, crear_funcion. Esta función tiene la capacidad de crear nuevas funciones a partir del valor del parámetro.
Funciones anónimas
Habrá ocasiones en las cuales necesitemos crear funciones de manera rápida, en tiempo de ejecución. Funciones las cuales realizan una tarea en concreto, regularmente pequeña. En estos casos haremos uso de funciones lambda.
Una función lambda es una función anónima, una función que no posee un nombre. En Python la estructura de una función lambda es la siguiente.
lambda argumentos : cuerpo de la función
Aquí un par de ejemplos.
suma = lambda val1=0, val2=0: val1 + val2
operacion = lambda operacion, val1=0, val2=0 : operacion(val1, val2)
resultado = operacion(suma, 10, 20)
print(resultado)
En esta ocasión replicamos el comportamiento de nuestro primer ejemplo. Creamos dos funciones lambda. La primera nos permite sumar dos números, esta función la almacenamos en la variable suma. Por otro lado nuestra segunda función se encarga de ejecutar y retornar el resultado de la operación. Esta función la almacenamos en la variable operacion.
Aquí un par de formas en las cuales podemos crear funciones lambda más complejas.
sin_parametros = lambda : True
con_valores = lambda val, val1=10, val2=10 : val + val1 + val2
con_asterisco = lambda *args : args[0]
con_doble_asterisco = lambda **kwargs : args[0]
con_asteriscos = lambda *args , **kwargs : kwargs.get('key', False)
Función Map
Bien, una vez hecho este rápido repaso, ya podemos poner manos a la obra con nuestras principales funciones. Comencemos explicando la función map.
La función map nos permite aplicar una función sobre cada uno de los elementos de un colección (Listas, tuplas, etc...).
Haremos uso de esta función siempre que tengamos la necesidad de transformar el valor de un elemento en otro.
La estructura de la función es la siguiente.
map(función a aplicar, objeto iterable)
La función a aplicar debe de retornar un nuevo valor. Es apartir de estos nuevos valores que obtendremos una nueva colección.
Veamos un ejemplo.
#Obtener el cuadrado de todos los elementos en la lista.
def cuadrado(elemento=0):
return elemento * elemento
lista = [1,2,3,4,5,6,7,8,9,10]
resultado = list( map(cuadrado, lista) )
print(resultado)
A Partir de la versión 3, la función map retorna un objeto map object. Objeto que fácilmente podemos convertir a una lista.
En este caso como la función que aplicamos sobre los elementos, es una función sencilla, podemos reemplazarla por una función lambda. El código pudiese quedar de la siguiente manera.
resultado = list( map( lambda elemento : elemento * elemento , lista) )
El código se reduce y obtenemos el mismo resultado.
Función Filter
La función filter, es quizás, una de las funciones más utilizadas al momento de trabajar con colecciones. Cómo su nombre lo indica, esta función nos permite realizar un filtro sobre los elementos de la colección.
La estructura de la función es la siguiente.
filter(función a aplicar, objeto iterable)
La función a aplicar será aplicada a cada uno de los elementos de la colección. Esta función siempre deberá retornar un valor booleano. Todos aquellos elementos que tengan como resultado True después de aplicar dicha función, serán los elementos que pasen el filtro. Apartir de estos elementos se creará una nueva colección.
Veamos un ejemplo.
#Obtener la cantidad de elementos mayores a 5 en la tupla.
def mayor_a_cinco(elemento):
return elemento > 5
tupla = (5,2,6,7,8,10,77,55,2,1,30,4,2,3)
resultado = tuple(filter( mayor_a_cinco, tupla))
resultado = len(resultado)
print(resultado)
A Partir de la versión 3, la función filter retorna un objeto filter object. Objeto que fácilmente podemos convertir a una tupla.
De igual forma, si nuestra función a aplicar realizar una tarea sencilla, podemos reemplazarla por una función lambda.
resultado = tuple(filter( lambda elemento: elemento > 5, tupla))
Función reduce
Usaremos la función reduce cuando poseamos una colección de elementos y necesitemos generar un único resultado. reduce nos permitirá reducir los elementos de la colección. Podemos ver a esta función como un acumulador.
La estructura de la función es la siguiente.
reduce(función a aplicar, objeto iterable)
Aquí lo importante es detallar la función a aplicar. Esta función debe de poseer, obligatoriamente, dos parámetros. El primer parámetro hará referencia al acumulador, un variable que irá modificando su valor por cada uno de los elementos en la colección. Por otro lado, el segundo parámetro hará referencia a cada elemento de la colección. La función debe de retornar un nuevo valor, será este nuevo valor el que será asignado al acumulador.
Todo esto suena algo confuzo 🤔, pero no te preocupes, veamos un par de ejemplos.
Comencemos con un enfoque imperativo.
#Obtener la suma de todos los elementos en la lista
lista = [1,2,3,4]
acumulador = 0;
for elemento in lista:
acumulador += elemento
print(acumulador)
Como observamos, para resolver el problema tuvimos que declarar una variable (acumulador). Variable que comienza con el valor de 0. Al recorrer la lista, el valor de nuestra variable incrementa. Su nuevo valor es el valor actual más el valor del elemento en la lista. Hasta aquí no creo que exista alguna duda.
Ahora veamos el mismo ejemplo utilizando la función reduce.
from functools import reduce
lista = [1,2,3,4]
def funcion_acumulador(acumulador=0, elemento=0):
return acumulador + elemento
resultado = reduce(funcion_acumulador, lista)
print(resultado)
Para nosotros poder hacer uso de la función reduce será necesario importarla.
Por cada elemento de la colección se ejecuta la función, funcion_acumulador. La función retorna la suma de los parámetros, este valor es almacenado en nuestro acumulador. Al finalizar la iteración de todos los elementos, reduce retornará el valor del acumulador.
Podemos utilizar reduce mediante una función lambda 😃.
resultado = reduce(lambda acumulador=0, elemento=0: acumulador + elemento, lista)
En este caso el resultado será de tipo entero, ya que así lo he especificado al momento de asignar un valor default al acumulador. Sin embargo, no estamos limitados únicamente a trabajar con valores de tipo entero.
Veamos otro ejemplo.
#Concatenar todos los elementos de la lista
from functools import reduce
lista = ['Python', 'Java', 'Ruby', 'Elixir']
resultado = reduce(lambda acumulador='', elemento='': acumulador + " - " + elemento, lista)
print(resultado)