JavaScript map, filter y reduce para dummies

En este artículo estudiaremos tres de los métodos más “de moda” a la hora de trabajar con Arrays en JavaScript, map(), filter() y reduce().

 

map-filter-reduce

 

Si bien es cierto que existe mucha información sobre estos tres métodos en la web, en la mayoría de los casos se vuelve demasiado técnica y esto la hace poco comprensible. En este artículo intentaré profundizar en el tema evitando caer en descripciones demasiado técnicas para que cualquiera pueda entender como funcionan.

!Al lío!

 

Array.prototype.map()

Si buscamos entre la numerosa documentación que existe en la web, encontraremos que map() es una función que crea un nuevo Array con los resultados de la llamada a la función indicada como parámetro, aplicada a cada uno de los elementos del Array sobre el que itera. Y como bonus nos ponen el súper ejemplo de código.

arr.map(callback[, thisArg])

Genial, ¿pero esto que cojones significa? Podemos pensar en map() como en un bucle forEach que sirve específicamente para transformar los valores de los elementos sobre los que itera. Recibe como parámetro una función, la cual a su vez recibe tres parámetros: el elemento actual del Array, el índice del elemento actual y el propio Array sobre el que se llama la función map(). Expresado en código quedaría algo así.

[1, 2, 3, 4].map( function (currentValue, index, array) { ... })

Para no volvernos locos apliquemos lo visto a un ejemplo sencillo. Tenemos un Array de números y necesitamos convertir esos números a sus cuadrados. Es una tarea simple pero lo suficientemente ilustrativa para lo que queremos ver.

Primero veremos como lo haríamos por el método tradicional, iterando el Array con un bucle for y transformando los valores en cada iteración para “endosárselos” a un nuevo Array. Seguro que os sentís familiarizados con este código.

let numbers = [1, 2, 3, 4, 5, 6]
let numSqrt = []

for (let i = 0; i < numbers.length; i++) {
  numSqrt[i] = numbers[i] * numbers[i]
}
// [1, 4, 9, 16, 25, 36]

Ok, hasta aquí todo funciona perfectamente pero el código no resulta muy amigable, además es demasiado complicado para una tarea tan simple. Con el método map() podemos simplificar nuestro código, hacerlo mucho más comprensible y agradable y por supuesto, mucho más funcional.

let numbers = [1, 2, 3, 4, 5, 6]
let numSqrt = numbers.map( function (number) {
  return number * number
})
// [1, 4, 9, 16, 25, 36]

Este código ya es otra historia. Pero aún lo podemos hacer un poco más bonito con las funciones de flecha o Arrow Functions disponibles en la nueva sintaxis de ECMAScript 6.

let numbers = [1, 2, 3, 4, 5, 6]
let numSqrt = numbers.map( number => number * number )
// [1, 4, 9, 16, 25, 36]

Precioso, ¿verdad? Con dos simples líneas de código hemos conseguido nuestro propósito, además se ha convertido en un código mucho más legible y funcional. Ha desaparecido el tedioso bucle for y no tenemos que ir añadiendo manualmente los nuevos valores a la matriz numSqrt. Nosotros solo tenemos que definir la transformación que queremos aplicar y dejar el trabajo sucio a map().

 

Array.prototype.filter()

Este método es bastante similar en cuanto a estructura al método map() pero con algunas diferencias significativas. Si miramos su sintaxis no encontraremos grandes cambios.

arr.filter(callback[, thisArg])

Podemos pensar que filter() al igual que map(), es como un forEach pero en este caso sirve específicamente para filtrar. Es decir, como su propio nombre indica, filtrará los elementos de un Array y creará un nuevo Array con los items que pasen el filtro. Este filtro será el resultado de la función que pasemos como parámetro, que siempre devolverá true o false.

Veamos como conseguir esto por el método tradicional. En este caso crearemos un nuevo Array con los items cuyo valor sea mayor que 3.

let numbers = [1, 2, 3, 4, 5, 6]
let numFiltered = []

for (let i = 0; i < numbers.length; i++) {
  if(numbers[i] > 3) {
    numFiltered[i] = numbers[i]
  }
}
// [4, 5, 6]

Ok, al igual que ocurría con el ejemplo que vimos con map(), este código funciona perfectamente pero no es demasiado funcional. Veamos como hacerlo con el método filter().

let numbers = [1, 2, 3, 4, 5, 6]
let numFiltered = numbers.filter(function (number) {
  return number > 3
})
// [4, 5, 6]

Y con un poco de Arrow Magic.

let numbers = [1, 2, 3, 4, 5, 6]
let numFiltered = numbers.filter(number => number > 3)
// [4, 5, 6]

En este caso podemos ver que además del bucle for nos hemos ahorrado la condición. El resultado de evaluar la expresión number > 3 hace de condición, o mejor dicho de filtro para este caso concreto.

 

Array.prototype.reduce()

Lo que hace este método es bastante simple pero a menudo es de los que mas cuesta entender. Creo que si vemos un ejemplo sencillo entenderemos claramente lo que hace.

let numbers = [1, 2, 3, 4, 5, 6]
let total = 0

for (let i = 0; i < numbers.length; i++) {
    total += numbers[i]
}
// 21

Si nos paramos a analizar lo que ocurre en el ejemplo anterior, veremos claramente que vamos sumando y acumulando en la variable total el valor de los items por los que iteramos. Es decir, en el primer ciclo del bucle total vale 0 y el item del Array sobre el que iteramos vale 1, por lo que total ahora valdrá 0 + 1 = 1. En el siguiente ciclo total vale 1 y el item del Array 2, por lo que total valdrá 3, en el siguiente ciclo valdrá 6, luego 10 y así hasta 21.

Sencillo, ¿verdad?. Pues esto es lo que en esencia hace reduce(), convierte o reduce una matriz o Array hasta un único valor por medio de la función pasada como callback.

Este método tiene algunas diferencias significativas respecto a los vistos anteriormente. También recibe una función, pero además recibe un segundo parámetro que será el valor inicial que se usará como primer argumento en la función. De ahí que su sintaxis varíe un poco respecto a map() y filter().

arr.reduce(callback[, initialValue])

Ya es hora de ver como resolver el ejemplo propuesto anteriormente con el método reduce().

let numbers = [1, 2, 3, 4, 5, 6]
let total = numbers.reduce((prev, cur) => prev + cur, 0)
// 21

 

 

BONUS! Encadenando funciones

Lo que oyes, estas funciones de primer nivel se pueden encadenar unas a otras, para entender mejor como va esto veamos un ejemplo.

Esta vez lo haremos más entretenido y partiremos de un Array que contiene objetos. Algo así nos puede valer.

var developers = [
  { name: 'Tano', type: 'mobile', age: 4 },
  { name: 'Inma', type: 'mobile', age: 31 },
  { name: 'Edgar', type: 'web', age: 35 },
  { name: 'Fernando', type: 'mobile', age: 33 }
]

Ahora pongamos como caso que necesitamos sacar el total de las edades de los desarrolladores mobile. Antes de seguir, ¿Se os ocurre como hacerlo?

let sumYearsMobileDev = developers
  .filter(developer => developer.type === 'mobile')
  .map(developer => developer.age)
  .reduce((prev, cur) => prev + cur, 0)
// 68

Si habéis pensado en algo parecido a esto genial, significa que el post os ha servido para algo y yo no he tirado el tiempo a la basura escribiendo. En cualquier caso recomiendo que le lechéis un vistazo a la docu de Mozilla para conocer más a fondo estos métodos.

Hasta la próxima!

 

  • Alejandro Garcia Romero

    Muy bueno que se declare map e invoques el metodo, te intere y te devuelva el resultado justo en donde declaras la variable numberSqrt, esto si es eficiente 😉

  • Pingback: JavaScript map filter y reduce para dummies()

  • Favio Velez

    Excelente artículo! De lo mejor que he leído para entender JavaScrip con lenguaje claro y sin tanto tecnicismo!

  • Jose Alberto Martinez Bianchi

    Me sacaste de dudas sin mucha escribir mucho, de verdad que excelente. Muchas gracias!