objects - Javascript reduce en una matriz de objetos
ordenar array de objetos javascript (8)
Después de la primera iteración, está devolviendo un número y luego tratando de obtener la propiedad x
de la misma para agregar al siguiente objeto que undefined
está undefined
y las matemáticas que implican resultados undefined
en NaN
.
intente devolver un objeto que contenga una propiedad x
con la suma de las propiedades x de los parámetros:
var arr = [{x:1},{x:2},{x:4}];
arr.reduce(function (a, b) {
return {x: a.x + b.x}; // returns object with property x
})
// ES6
arr.reduce((a, b) => ({x: a.x + b.x}));
// -> {x: 7}
Explicación agregada de los comentarios:
El valor de retorno de cada iteración de [].reduce
utilizado como a
variable en la siguiente iteración.
Iteración 1: a = {x:1}
, b = {x:2}
, {x: 3}
asignado a a
en la iteración 2
Iteración 2: a = {x:3}
, b = {x:4}
.
El problema con tu ejemplo es que estás devolviendo un número literal.
function (a, b) {
return a.x + b.x; // returns number literal
}
Iteración 1: a = {x:1}
, b = {x:2}
, // returns 3
como a
en la siguiente iteración
Iteración 2: a = 3
, b = {x:2}
devuelve NaN
Un número literal 3
no tiene (típicamente) una propiedad llamada x
por lo que no está undefined
y undefined + bx
devuelve NaN
y NaN + <anything>
es siempre NaN
Aclaración : prefiero mi método sobre la otra respuesta superior en este hilo ya que estoy en desacuerdo con la idea de que pasar un parámetro opcional para reducir con un número mágico para obtener un número primitivo es más claro. Puede dar como resultado menos líneas escritas, pero es menos legible.
Digamos que quiero sumar un ax
para cada elemento en arr
.
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a.x + b.x})
>> NaN
Tengo motivos para creer que ax no está definido en algún momento.
El siguiente funciona bien
arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7
¿Qué estoy haciendo mal en el primer ejemplo?
En cada paso de su reducción, no está devolviendo un nuevo objeto {x:???}
. Entonces debes hacer:
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})
o tienes que hacer
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; })
En el primer paso, funcionará bien ya que el valor de a
será 1 y el de b
será 2 pero se devolverá 2 + 1 y en el siguiente paso el valor de b
será el valor de retorno from step 1 ie 3
y así bx
no estará definido ... e indefinido + anyNumber será NaN y es por eso que está obteniendo ese resultado.
En su lugar, puede probar esto dando un valor inicial como cero, es decir,
arr.reduce(function(a,b){return a + b.x},0);
Otros han respondido a esta pregunta, pero pensé que podría lanzar otro enfoque. En lugar de ir directamente a la suma del hacha, puede combinar un mapa (de ax a x) y reducir (para agregar las x):
arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
.reduce(function(a,b) {return a + b;});
Es cierto que es probable que sea un poco más lento, pero pensé que valía la pena mencionarlo como una opción.
Para formalizar lo que se ha señalado, un reductor es un catamorfismo que toma dos argumentos que pueden ser del mismo tipo por coincidencia, y devuelve un tipo que coincide con el primer argumento.
function reducer (accumulator: X, currentValue: Y): X { }
Eso significa que el cuerpo del reductor necesita convertir el valor actual y el valor actual del accumulator
en el valor del nuevo accumulator
.
Esto funciona de manera directa, cuando se agrega, porque el acumulador y los valores del elemento son del mismo tipo (pero tienen diferentes propósitos).
[1, 2, 3].reduce((x, y) => x + y);
Esto solo funciona porque todos son números.
[{ age: 5 }, { age: 2 }, { age: 8 }]
.reduce((total, thing) => total + thing.age, 0);
Ahora le estamos dando un valor inicial al agregador. El valor inicial debe ser del tipo que espera que sea el agregador (el tipo que espera obtener como el valor final), en la gran mayoría de los casos. Si bien no estás obligado a hacer esto (y no deberías hacerlo), es importante tenerlo en cuenta.
Una vez que lo sepa, puede escribir reducciones significativas para otros problemas de relación n: 1.
Eliminar palabras repetidas:
const skipIfAlreadyFound = (words, word) => words.includes(word)
? words
: words.concat(word);
const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);
Proporcionando un recuento de todas las palabras encontradas:
const incrementWordCount = (counts, word) => {
counts[word] = (counts[word] || 0) + 1;
return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });
Reducir una matriz de matrices a una única matriz plana:
const concat = (a, b) => a.concat(b);
const numbers = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
].reduce(concat, []);
Cada vez que esté buscando pasar de una variedad de cosas a un único valor que no concuerde con un 1: 1, reducir es algo que podría considerar.
De hecho, el mapa y el filtro se pueden implementar como reducciones:
const map = (transform, array) =>
array.reduce((list, el) => list.concat(transform(el)), []);
const filter = (predicate, array) => array.reduce(
(list, el) => predicate(el) ? list.concat(el) : list,
[]
);
Espero que esto proporcione un contexto adicional sobre cómo usar reduce
.
La única adición a esto, en la que aún no me he involucrado, es cuando existe la expectativa de que los tipos de entrada y salida estén específicamente destinados a ser dinámicos, porque los elementos de la matriz son funciones:
const compose = (...fns) => x =>
fns.reduceRight((x, f) => f(x), x);
const hgfx = h(g(f(x)));
const hgf = compose(h, g, f);
const hgfy = hgf(y);
const hgfz = hgf(z);
Una forma más limpia de lograr esto es proporcionar un valor inicial:
var arr = [{x:1}, {x:2}, {x:4}];
arr.reduce(function (acc, obj) { return acc + obj.x; }, 0); // 7
La primera vez que se llama a la función anónima, se llama con (0, {x: 1})
y devuelve 0 + 1 = 1
. La próxima vez, se llama con (1, {x: 2})
y devuelve 1 + 2 = 3
. Luego se llama con (3, {x: 4})
y finalmente devuelve 7
.
función de reducción itera sobre una colección
arr = [{x:1},{x:2},{x:4}] // is a collection
arr.reduce(function(a,b){return a.x + b.x})
se traduce a:
arr.reduce(
//for each index in the collection, this callback function is called
function (
a, //a = accumulator ,during each callback , value of accumulator is
passed inside the variable "a"
b, //currentValue , for ex currentValue is {x:1} in 1st callback
currentIndex,
array
) {
return a.x + b.x;
},
accumulator // this is returned at the end of arr.reduce call
//accumulator = returned value i.e return a.x + b.x in each callback.
);
durante cada devolución de llamada de índice, el valor de la variable "acumulador" se pasa al parámetro "a" en la función de devolución de llamada. Si no inicializamos "acumulador", su valor no estará definido. Llamar undefined.x te daría un error.
Para resolver esto, inicialice "acumulador" con valor 0 como la respuesta de Casey mostrada arriba.
Para comprender las entradas y salidas de la función "reducir", le sugiero que mire el código fuente de esta función. La biblioteca Lodash tiene una función de reducción que funciona exactamente igual que la función "reducir" en ES6.
Aquí está el enlace: reducir el código fuente
TL; DR, establecer el valor inicial
arr.reduce( (sum,cur) => sum+cur.x , 0)
La clave para esto es establecer el valor inicial. El valor de retorno se convierte en el primer parámetro de la siguiente iteración.
Primer pase, sum
es cero. cur
es el primer valor en la matriz, un objeto. Entonces accedemos a la propiedad x
. Agregamos ese número a la sum
y lo devolvemos.
Siguiente iteración, lo mismo. sum
es ahora el último valor devuelto y agregamos el siguiente artículo, etc.
Si no configuramos ese 0
al final, la primera sum
iteración sería un objeto y escribiríamos lógica para manejar un objeto. También tendríamos que devolver un objeto porque la siguiente iteración está configurada para manejar un objeto.
Un poco más de fondo
El método de reduce
toma dos parámetros,
Array.prototype.reduce( callback, initialItem )
La función de callback
toma los siguientes parámetros
(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }
Cuando se proporciona initialItem
, se transfiere como accumulator
a la función de callback
el primer paso y itemInArray
es el primer elemento de la matriz.
Si initialItem
no está establecido, reduce
toma el primer elemento de la matriz como initialItem
y pasa el segundo elemento como itemInArray
. Esto puede ser un comportamiento confuso.
Enseño y recomiendo siempre establecer el valor inicial de reducir. Además, a medida que crece como desarrollador y comienza a buscar transductores u otras bibliotecas mejoradas, descubrirá que establecer el valor inicial es imprescindible.
Para obtener un artículo completo sobre Array.prototype.reduce
consulte:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
¡Espero que esto ayude!