Transformar Arrays
Clase 6 de 88 min de lectura
Ya sabes crear arrays, modificarlos y buscar en ellos. Falta lo que más vas a usar en el día a día: crear arrays nuevos a partir de los que ya tienes. Quedarte solo con algunos elementos, convertir cada uno en otra cosa o resumirlos todos en un único valor.
Para eso hay tres métodos que conviene conocer muy bien: filter, map y reduce.
filter: Un nuevo Array con los elementos que cumplan una condición#
filter funciona como un colador. Le pasas una función, la ejecuta con cada elemento y crea un array nuevo solo con los elementos para los que devuelve true.
Por ejemplo, para quedarte con los números pares:
const numbers = [1, 2, 3, 4, 5, 6, 7]const evenNumbers = numbers.filter(function (number) { return number % 2 === 0})console.log(evenNumbers) // -> [2, 4, 6]Los impares devuelven false y se quedan fuera. El array numbers sigue intacto.
Sirve con cualquier condición. Aquí nos quedamos con las cadenas de texto que tienen la letra a:
const strings = ['hola', 'adiós', 'casa', 'coche', 'perro', 'gato']// en las cadenas de texto podemos usar el método `includes`// para saber si una cadena contiene otra:const stringsWithA = strings.filter(string => string.includes('a'))console.log(stringsWithA) // -> ['hola', 'adiós', 'casa', 'gato']map: Un nuevo Array con los elementos transformados#
Si filter decide quién se queda, map cambia a todos. Crea un array de la misma longitud que el original, donde cada elemento es el resultado de pasar el original por tu función.
La función recibe cada elemento y tiene que devolver el elemento ya transformado. Por ejemplo, el doble de cada número:
const numbers = [1, 2, 3]const doubleNumbers = numbers.map((number) => { return number * 2})console.log(doubleNumbers) // -> [2, 4, 6]Entran tres números y salen tres números. Lo que devuelves no tiene por qué ser del mismo tipo que lo que recibes: aquí entran cadenas de texto y sale la longitud de cada una.
const strings = ['hola', 'javascript', 'midu', 'programación']const stringsLength = strings.map((string) => string.length)console.log(stringsLength) // -> [4, 10, 4, 12]map + filter: Un nuevo Array con los elementos transformados y filtrados#
¿Y si quieres transformar y filtrar a la vez? Como map devuelve un array, puedes llamar a filter directamente sobre ese resultado, sin guardarlo antes en una variable. A esto se le llama encadenar métodos.
Imagina que quieres el doble de cada número y quedarte solo con los resultados mayores que 5. Lee los comentarios para ver qué sale de cada paso:
const numbers = [1, 2, 3, 4, 5, 6, 7]const numsGreaterThanFive = numbers .map(number => number * 2) // [2, 4, 6, 8, 10, 12, 14] .filter(number => number > 5) // [6, 8, 10, 12, 14]console.log(numsGreaterThanFive) // -> [6, 8, 10, 12, 14]El orden lo decides tú. También puedes filtrar primero y transformar después, por ejemplo quedándote con los pares para luego multiplicarlos por 2:
const numbers = [1, 2, 3, 4, 5, 6, 7]const doubleEvenNumbers = numbers .filter(number => number % 2 === 0) // [2, 4, 6] .map(number => number * 2) // [4, 8, 12]console.log(doubleEvenNumbers) // -> [4, 8, 12]reduce: Un único valor a partir de un array#
reduce es el más complejo de los tres, así que vamos despacio. Su trabajo es crear un único valor a partir de un array: una suma, un total, lo que necesites.
Recibe dos parámetros: una función que se ejecuta por cada elemento y un valor inicial, opcional, que es el punto de partida sobre el que se va acumulando.
Esa función, a su vez, recibe estos parámetros:
- El acumulador: el valor que se va acumulando en cada iteración.
- El elemento actual: el elemento del array que se está iterando en ese momento.
- El índice: la posición del elemento actual en el array.
Y debe devolver el valor que quieres acumular para la siguiente iteración.
El caso de uso más típico es sumar todos los elementos de un array:
const numbers = [1, 2, 3]const sum = numbers.reduce((accumulator, currentNumber) => { return accumulator + currentNumber}, 0) // <- el 0 es el valor inicialconsole.log(sum) // -> 6Vale, pero ¿qué pasa por dentro? Vamos vuelta a vuelta:
- El acumulador vale 0, porque es el valor inicial, y el elemento actual vale 1. Devolvemos 0 + 1 = 1.
- El acumulador vale 1, lo que devolvimos en la vuelta anterior, y el elemento actual vale 2. Devolvemos 1 + 2 = 3.
- El acumulador vale 3 y el elemento actual vale 3. Devolvemos 3 + 3 = 6.
No quedan más elementos, así que el valor final de sum es 6.
Y así se combinan los tres métodos, uno detrás de otro, para pasar de un array a un único resultado:
const resultado = [1, 2, 3, 4] .map(n => n * 2) .filter(n => n > 4) .reduce((acc, n) => acc + n, 0)6 + 8 = 14. No quedan elementos, así que el resultado final es 14.¿Recuerdas el encadenamiento de map y filter? Cada método crea un array intermedio. Con reduce puedes ahorrártelo y hacerlo todo en una sola pasada. Por ejemplo, para doblar los números pares y quedarte solo con los mayores de 5:
const numbers = [1, 2, 3, 4, 5, 6, 7]const doubleEvenNumbers = numbers.reduce((accumulator, currentNumber) => { const isEven = currentNumber % 2 === 0 const doubled = currentNumber * 2 const isGreaterThanFive = doubled > 5 // si es par y mayor que 5, lo añadimos al array if (isEven && isGreaterThanFive) { // para ello devolvemos un nuevo array con el valor actual return accumulator.concat(doubled) } else { // si no, devolvemos lo que ya teníamos return accumulator }}, []) // <- el array vacío es el valor inicialconsole.log(doubleEvenNumbers) // -> [8, 12]Esta vez el valor inicial es un array vacío. En cada vuelta, si el número nos interesa lo añadimos al acumulador; si no, lo descartamos devolviendo el acumulador tal cual estaba.
En resumen
filtercrea un array nuevo solo con los elementos que cumplen una condición.mapcrea un array nuevo de la misma longitud con cada elemento transformado.- Como ambos devuelven un array, se pueden encadenar:
.map(...).filter(...). reduceconvierte un array en un único valor, acumulando el resultado vuelta a vuelta.- Ninguno de los tres modifica el array original.