4 minute read

Map, filter y reduce

En un mundo tan caótico en donde todo parece ser mutable estar seguro de cómo se comportará nuestro código es realmente una ardua tarea, pero no tiene porqué ser así. Recientemente el paradigma funcional se ha convertido en un virus que comienza a afectar los lenguajes mainstream, los cambios mas notables de Java en su versión 8 están directamente relacionados con la programación funcional: funciones de orden superior, lambda funciones, evaluación perezosa, entre otros. Algo tendrá entonces que ofrecer lo que antes se veía como “meramente académico” por eso en esta ocasión aprenderemos algunos conceptos que harán de tu vida como programador mucho mas fácil.

Map

Map nos permite tomar elementos que pertenecen a un espacio y transformarlos a otro espacio, eso mismo, matemáticamente tenemos una secuencia de datos E y queremos aplicarles una transformación de modo que pertenezcan a una secuencia F, (E → F) × Seq<‍E> → Seq<‍F>. Aunque parezca marciano es algo a lo que nos enfrentamos comunmente cuando trabajamos con una secuencia de datos ya sea de un archivo en disco, una base de datos o desde un servicio web.

Para comprender mejor, Map toma como parámetros una función unaria, es decir que recibe un solo parámetro, y como segundo argumento recibe una colección de datos (una lista, un set), en Java la condición es que dicha colección proveea un Iterator. Ejemplo en pseudocódigo:

map(sqrt, [1, 4, 9, 16])        # ==> [1.0, 2.0, 3.0, 4.0]

Estamos usando como parámetro la función sqrt que devuelve la raíz cuadrada del número que le pasemos, y una lista de datos que tienen raíz exacta. Internamente Map itera sobre cada valor de la lista y le aplica la función devolviendo una lista del mismo tamaño y con el mismo orden en el que le fueron entregados los datos.

Veámos como sería en Java aplicarle la raíz cuadrada a una lista de enteros.

public List<Double> raizCuadrada (List<Integer> entrada) {
  List<Double> resultado = new ArrayList<Double>();
  for( Integer valor : entrada) {
    resultado.add(Math.sqrt(valor));
  }

  return resultado;
}

¿Qué pasa si también requiero crear una nueva lista con la raíz cúbica? ¿ o si ahora lo que quiero es elevar toda a lista al cuadrado? Estaríamos tentados entonces a crear una función particular para cada caso, significa más código que depurar, testear y mantener, violariamos el patrón DRY (Don’t repeat yourself).

Gracias a que ahora en Java podemos usar funciones de orden superior y además podemos obtener un Stream a partir de las colecciones sobre el cuál podemos operar con Map.

double [] miRaiz = Arrays.stream(entrada)
  .map(Math::sqrt)
  .toArray();

¡Mira mamá, sin for! Ahora es solo una línea de código, más fácil de entender, menos código que mantener y menos líneas que testear.

Filter

Filter nos permite obtener de una lista los datos que cumplan con cierto criterio, dicho criterio es una función que devuelve un booleano. Ejemplo:

String[] mifiltro = Arrays.stream(myArray)
  .filter(s -> s.length() > 4)
  .toArray(String[]::new);

En este caso estamos aplicando una función lambda como criterio, un lambda es una función anónima que solo la usaremos como parámetro de entrada a la función filter, s -> s.length() > 4 queremos solo las cadenas que tienen 4 o mas letras.

Reduce

Reduce consiste en reducir una colección de datos a un solo valor, por ejemplo, si queremos tomar una lista de números y calcularles la suma, el promedio o funciones más complejas:

int myArray[] = { 1, 5, 8 };
int sum = Arrays.stream(myArray).sum();

Los parámetros que toma reduce difieren de los de map y filter, un identificador y una función lambda. El identificador se puede pensar como un elemento que pertenece al conjunto de datos pero que no altera el resultado final de la operación, por ejemplo, si estamos calculando una productoria el identificador será 1, si estamos concatenando caracteres '', etc.

String[] myArray = { "this", "is", "a", "sentence" };
String result = Arrays.stream(myArray)
                .reduce("", (a,b) -> a + b);

En este caso la función lambda (a,b) -> a + b recibe el identificador "" y lo concatena con el primer elemento de la lista "this", luego toma el resultado y lo concatena con el siguiente elemento hasta que no hayan más elementos por evaluar.

Hemos visto como reducir y mejorar la calidad de nuestro código con estas 3 nuevas funciones, es posible hacer composiciones de estas 3 funciones para crear criterios mas complejos, en próximas entregas entraremos mas a fondo.

comments powered by Disqus