JS: Funciones
Teoría: Agregación (reduce)
La última función de nuestro trío es el método reduce() (llamado "reducción"), que se utiliza para agregar datos. La agregación se refiere a una operación que calcula un valor que depende de todo el conjunto de datos. Algunas operaciones de agregación incluyen encontrar el valor promedio, la suma de elementos, el máximo o el mínimo. Este enfoque se explicó en el curso de matrices.
reduce() es un poco más complicado que map() y filter(), pero en general sigue el mismo enfoque de pasar una función. Implementemos un código que encuentre la cantidad total de dinero de un grupo de personas. Aquí se puede ver claramente la agregación, ya que necesitamos reducir la cantidad de dinero de todos los usuarios a un solo valor:
La principal diferencia entre la agregación y el mapeo y filtrado es que el resultado de la agregación puede ser de cualquier tipo de datos, ya sea primitivo o compuesto, como un arreglo. Además, la agregación a menudo implica la inicialización con un valor inicial, que se suele llamar acumulador. En el ejemplo anterior, esto se realiza en la línea let sum = 0. Aquí, la variable sum "acumula" el resultado dentro de sí misma.
Veamos otro ejemplo de agregación: agrupar los nombres de los usuarios por edad:
En este ejemplo, el resultado de la agregación es un objeto con arreglos como propiedades. Este resultado se inicializa como un objeto vacío al principio, y luego se "llena" gradualmente con los datos necesarios en cada iteración. El valor que acumula el resultado de la agregación se suele llamar acumulador. En los ejemplos anteriores, son sum y usersByAge.
Implementemos el primer ejemplo utilizando reduce():
El método reduce() toma dos parámetros: una función de procesamiento y un valor inicial para el acumulador. Este acumulador se devuelve como resultado de toda la operación.
La función pasada a reduce() es la parte más importante y clave para comprender cómo funciona todo el mecanismo de agregación. Toma dos valores como entrada: el valor actual del acumulador y el elemento actual que se está procesando. La tarea de la función es devolver un nuevo valor para el acumulador. reduce() no analiza el contenido del acumulador. Todo lo que hace es pasarlo en cada nueva llamada hasta que se haya procesado toda la colección y, finalmente, lo devuelve. Es importante destacar que siempre se debe devolver el acumulador, incluso si no ha cambiado.
El segundo ejemplo utilizando reduce() se ve así:
El código prácticamente no ha cambiado, excepto que se ha eliminado el bucle y se ha agregado un retorno del acumulador desde la función anónima.
Veamos paso a paso cómo funciona la función reduce(). La función recibe un acumulador y un usuario en cada iteración. Para comprender mejor cómo funcionan estos valores, veamos qué valores tienen en cada iteración:
- En la primera iteración, el acumulador es un objeto vacío, que es el valor inicial del acumulador especificado como segundo parámetro,
users.reduce(cb, {})- aquí se pasa un objeto vacío como segundo parámetro. El parámetrouseres igual al primer elemento del arreglo, es decir,{ name: 'Pedro', age: 4 }. Se crea un arreglo en el objeto vacío con la claveuser.agey se agrega el nombre actual a ese arreglo. Como resultado, el acumulador se convierte en el objeto{ 4: ['Pedro'] }. Este objeto se devuelve desde la función. - En la segunda iteración, el acumulador es el valor devuelto de la iteración anterior, que es el objeto
{ 4: ['Pedro'] }. El parámetrouseres igual al segundo elemento del arreglo{ name: 'Sergio', age: 19 }. El acumulador no tiene una clave con la edad del usuario actual, por lo que se crea una nueva clave y un nuevo arreglo. Después de llenar el acumulador, se convierte en{ 4: ['Pedro'], 19: ['Sergio'] }, y este objeto se devuelve desde la función. - En esta iteración, el acumulador es el objeto devuelto de la iteración anterior
{ 4: ['Pedro'], 19: ['Sergio'] }. El parámetrouseres{ name: 'Isabela', age: 4 }. La claveuser.agetiene el valor 4, que ya existe en el acumulador, por lo que no se crea una nueva clave, sino que se agrega el usuario actual al arreglo existente. Como resultado, el acumulador se convierte en{ 4: ['Pedro', 'Isabela'], 19: ['Sergio'] }, y se devuelve desde la función. - Última iteración. El parámetro
acces igual al objeto devuelto de la iteración anterior{ 4: ['Pedro', 'Isabela'], 19: ['Sergio'] }, yuseres igual a{ name: 'Miguel', age: 16 }. No hay una clave16en el acumulador, por lo que se crea un nuevo arreglo en la clave16y se agrega el usuario actual a ese arreglo. Como resultado, el acumulador será{ 4: ['Pedro', 'Isabela'], 16: ['Miguel'], 19: ['Sergio'] }, y este objeto se devuelve al final y será el resultado de todo el reduce, ya que es la última iteración.
reduce() es un método muy poderoso. Formalmente, se puede trabajar solo con él, ya que puede reemplazar tanto el mapeo como el filtrado. Sin embargo, no se recomienda hacerlo de esta manera. La agregación controla el estado (acumulador) de manera explícita. Este tipo de código siempre es más complicado y requiere más acciones. Por lo tanto, si es posible resolver una tarea utilizando el mapeo o el filtrado, es mejor hacerlo de esa manera.
Cómo pensar en reduce
Desarrollemos un algoritmo que nos ayude a abordar correctamente las tareas que requieren el uso de reduce(). Imagina que tienes una lista de cursos con lecciones dentro de ellos y necesitas contar la cantidad total de lecciones. Por ejemplo, esto puede ser necesario para calcular la duración de un programa de estudio. En Códica, este tipo de tareas se encuentran regularmente.
Aquí vemos dos cursos que tienen un total de 5 lecciones. Ahora intentemos calcular esto programáticamente. La primera pregunta a la que debemos responder es si esta operación es una agregación. La respuesta es sí, ya que estamos reduciendo los datos de origen a un resultado calculado. Luego, miramos qué tipo de resultado es esta operación. En nuestro caso, es un número que se calcula como la suma de las lecciones en cada curso. Por lo tanto, el valor inicial del acumulador será 0 (aquí puedes refrescar). Ahora tenemos un algoritmo aproximado:
- Inicializamos el resultado acumulado en cero
- Recorremos la colección de cursos uno por uno
- Agregamos al acumulador la cantidad de lecciones en el curso actual
Este algoritmo será idéntico en cualquier variante de solución, ya sea a través de un bucle o a través de reduce():
Implementación
Escribamos nuestra propia función myReduce(), que funcione de manera similar al método reduce() de los arreglos:
Completado
0 / 16