Entendiendo el Reductor Todopoderoso | Programar Plus

Recientemente estuve asesorando a alguien que tenía problemas con el .reduce() método en JavaScript. Es decir, cómo se obtiene de esto:

const nums = [1, 2, 3]
let value = 0

for (let i = 0; i < nums.length; i++) {
  value += nums[i]
}

…a esto:

const nums = [1, 2, 3]
const value = nums.reduce((ac, next) => ac + next, 0)

Son funcionalmente equivalentes y ambos suman todos los números de la matriz, pero hay un pequeño cambio de paradigma entre ellos. Exploremos los reductores por un momento porque son poderosos y es importante tenerlos en su caja de herramientas de programación. Hay literalmente cientos de otros artículos sobre reductores, y vincularé algunos de mis favoritos al final.

¿Qué es un reductor?

Lo primero y más importante que debe comprender acerca de un reductor es que siempre devolverá solo un valor. El trabajo de un reductor es reducir. Ese valor puede ser un número, una cadena, una matriz o un objeto, pero siempre será solo uno. Los reductores son realmente geniales para muchas cosas, pero son especialmente útiles para aplicar un poco de lógica a un grupo de valores y terminar con otro resultado único.

Ésa es la otra cosa a mencionar: los reductores, por su naturaleza, no mutarán su valor inicial; más bien devuelven algo más. Repasemos ese primer ejemplo para que pueda ver lo que está sucediendo aquí. El video a continuación explica:

Su navegador no soporta la etiqueta de vídeo.

Puede ser útil ver el video para ver cómo ocurre la progresión, pero aquí está el código que estamos viendo:

const nums = [1, 2, 3]
let value = 0

for (let i = 0; i < nums.length; i++) {
  value += nums[i]
}

Tenemos nuestra matriz (1, 2, 3) y el primer valor al que se agregará cada número de la matriz (0). Analizamos la cantidad de la matriz y la agregamos al valor inicial.

Intentemos esto de manera un poco diferente:

const nums = [1, 2, 3]
const initialValue = 0

const reducer = function (acc, item) { 
  return acc + item
}

const total = nums.reduce(reducer, initialValue)

Ahora tenemos la misma matriz, pero esta vez no mutaremos ese primer valor. En cambio, tenemos un initialValue que solo se utilizará al principio. A continuación, podemos hacer una función que tome un acumulador y un artículo. El acumulador es el valor recolectado devuelto en la última invocación que informa a la función a qué se agregará el siguiente valor. En este caso de suma, puede pensar en ella como una bola de nieve que rueda por una montaña que se come cada valor en su camino a medida que crece en tamaño por cada valor ingerido.

bola de nieve acumulando valores

Usaremos .reduce() para aplicar la función y comenzar desde ese valor inicial. Esto se puede acortar con una función de flecha:

const nums = [1, 2, 3]
const initialValue = 0

const reducer = (acc, item) => { 
  return acc + item
}

const total = nums.reduce(reducer, initialValue)

¡Y luego acortado un poco más! ¡Rendimientos implícitos por la victoria!

const nums = [1, 2, 3]
const initialValue = 0

const reducer = (acc, item) => acc + item

const total = nums.reduce(reducer, initialValue)

Ahora podemos aplicar la función justo donde la llamamos, ¡y también podemos colocar ese valor inicial directamente allí!

const nums = [1, 2, 3]

const total = nums.reduce((acc, item) => acc + item,

Un acumulador puede ser un término intimidante, por lo que puede pensar en él como el estado actual de la matriz mientras aplicamos la lógica en las invocaciones de la devolución de llamada.

La pila de llamadas

En caso de que no esté claro qué está sucediendo, desconectemos lo que está sucediendo en cada iteración. La reducción utiliza una función de devolución de llamada que se ejecutará para cada elemento de la matriz. I La siguiente demostración ayudará a aclarar esto. También he usado una matriz diferente ([1, 3, 6]) porque hacer que los números sean los mismos que los del índice puede resultar confuso.

Vea el bolígrafo que muestra acc, artículo, devolución de Sarah Drasner (@sdras) en CodePen.

Cuando ejecutamos esto, veremos esta salida en la consola:

"Acc: 0, Item: 1, Return value: 1"
"Acc: 1, Item: 3, Return value: 4"
"Acc: 4, Item: 6, Return value: 10"

Aquí hay un desglose más visual.:

Su navegador no soporta la etiqueta de vídeo.

  1. Muestra que el acumulador comienza en nuestro valor inicial, 0
  2. Luego tenemos el primer elemento, que es 1, por lo que nuestro valor de retorno es 1 (0 + 1 = 1)
  3. 1 se convierte en el acumulador en la siguiente invocación
  4. Ahora tenemos 1 ya que el acumulador y 3 es el elemento a ya que es el siguiente en la matriz.
  5. El valor devuelto se convierte en 4 (1 + 3 = 4)
  6. Eso, a su vez, se convierte en el acumulador y el siguiente elemento en la invocación es 6
  7. Que resulta en 10 (4 + 6 = 10) y es nuestro valor final ya que 6 es el último número de la matriz

Ejemplos sencillos

Ahora que tenemos eso en nuestro haber, veamos algunas cosas comunes y útiles que pueden hacer los reductores.

¿Cuántos de X tenemos?

Supongamos que tiene una matriz de números y desea devolver un objeto que informe la cantidad de veces que esos números aparecen en la matriz. Tenga en cuenta que esto podría aplicarse fácilmente a las cadenas.

const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]

const result = nums.reduce((tally, amt) => {
  tally[amt] ? tally[amt]++ : tally[amt] = 1
  return tally
}, {})

console.log(result)

Vea la reducción simplificada de Pen de Sarah Drasner (@sdras) en CodePen.

Espera, ¿qué acabamos de hacer?

Inicialmente, tenemos una matriz y el objeto en el que vamos a poner su contenido. En nuestro reductor, preguntamos: ¿existe este artículo? Si es así, incrementémoslo. De lo contrario, agréguelo y configúrelo en 1. Al final, devuelva el recuento de cada artículo. Luego, ejecutamos la función de reducción, pasando tanto el reductor como el valor inicial.

Tome una matriz y conviértala en un objeto que muestre algunas condiciones

Digamos que tenemos una matriz y queremos crear un objeto basado en un conjunto de condiciones. ¡Reducir puede ser genial para esto! Aquí, queremos crear un objeto a partir de cualquier instancia de un número contenido en la matriz y mostrar tanto una versión impar como una versión par de este número. Si el número ya es par o impar, entonces eso es lo que tendremos en el objeto.

const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]

// we're going to make an object from an even and odd
// version of each instance of a number
const result = nums.reduce((acc, item) => {
  acc[item] = {
    odd: item % 2 ? item : item - 1,
    even: item % 2 ? item + 1 : item
  }
  return acc
}, {})

console.log(result)

Vea la reducción simplificada de Pen de Sarah Drasner (@sdras) en CodePen.

Esto disparará la siguiente salida en la consola:

1:{odd: 1, even: 2}
3:{odd: 3, even: 4}
4:{odd: 3, even: 4}
5:{odd: 5, even: 6}
6:{odd: 5, even: 6}
82:{odd: 81, even: 82}

OK, entonces, ¿qué está pasando?

A medida que revisamos cada elemento de la matriz, creamos una propiedad para pares e impares, y según una condición en línea con un operador de módulo, almacenaremos el número o lo incrementaremos en 1. El operador de módulo es realmente bueno para esto porque puede verificar rápidamente si es par o impar: si es divisible por dos, es par, si no, es impar.

Otros recursos

En la parte superior, mencioné otras publicaciones que son recursos útiles para familiarizarse más con el papel de los reductores. Aquí están algunos de mis favoritos:

  • La documentación de MDN es maravillosa para esto. En serio, es una de sus mejores publicaciones, en mi opinión. También describen con un poco más de detalle lo que sucede si no proporciona un valor inicial, que no cubrimos en esta publicación.
  • Daniel Shiffman siempre es asombroso explicando cosas en Coding Train.
  • Un goteo de JavaScript también hace un buen trabajo.
(Visited 8 times, 1 visits today)