web-dev-qa-db-fra.com

Javascript réduit sur un tableau d'objets

Disons que je veux additionner a.x pour chaque élément dans arr.

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a.x + b.x})
>> NaN

J'ai des raisons de croire que a.x n'est pas défini à un moment donné.

Ce qui suit fonctionne bien

arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7

Qu'est-ce que je fais mal dans le premier exemple?

150
YXD

Après la première itération, vous retournez un nombre et essayez ensuite d'obtenir la propriété x à ajouter à l'objet suivant qui est undefined et les calculs impliquant undefined ont pour résultat NaN

essayez de retourner un objet contenant une propriété x avec la somme des propriétés x des paramètres:

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}

Explication ajoutée à partir des commentaires:

La valeur de retour de chaque itération de [].reduce utilisée comme variable a à la prochaine itération. 

Itération 1: a = {x:1}, b = {x:2}, {x: 3} affecté à a dans l'Itération 2

Itération 2: a = {x:3}, b = {x:4}

Le problème avec votre exemple est que vous retournez un nombre littéral.

function (a, b) {
  return a.x + b.x; // returns number literal
}

Itération 1: a = {x:1}, b = {x:2}, // returns 3 en tant que a à l'itération suivante

Itération 2: a = 3, b = {x:2} renvoie NaN

Un nombre littéral 3 n'a pas (généralement) de propriété nommée x; c'est donc undefined et undefined + b.x renvoie NaN et NaN + <anything> est toujours NaN

Clarification : Je préfère ma méthode à l’autre première réponse de ce fil de discussion car je ne partage pas l’idée que passer un paramètre facultatif à réduire avec un nombre magique pour obtenir un nombre primitif est plus propre. Cela peut entraîner moins de lignes écrites, mais je pense qu’il est moins lisible.

201
JaredMcAteer

Une façon plus simple d’y parvenir est de fournir une valeur initiale:

var arr = [{x:1}, {x:2}, {x:4}];
arr.reduce(function (acc, obj) { return acc + obj.x; }, 0); // 7
console.log(arr);

La première fois que la fonction anonyme est appelée, elle est appelée avec (0, {x: 1}) et renvoie 0 + 1 = 1. La prochaine fois, il sera appelé avec (1, {x: 2}) et retournera 1 + 2 = 3. Il est ensuite appelé avec (3, {x: 4}) et renvoie finalement 7.

192
Casey Chu

D'autres ont répondu à cette question, mais je pensais avoir recours à une autre approche. Plutôt que d'aller directement à sommer a.x, vous pouvez combiner une carte (de a.x à x) et réduire (pour ajouter les x):

arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
   .reduce(function(a,b) {return a + b;});

Certes, cela va probablement être un peu plus lent, mais j’ai pensé que cela valait la peine de le mentionner comme option.

22
RHSeeger

Pour formaliser ce qui a été souligné, un réducteur est un catamorphisme qui prend deux arguments qui peuvent être du même type par coïncidence et renvoie un type qui correspond au premier argument.

function reducer (accumulator: X, currentValue: Y): X { }

Cela signifie que le corps du réducteur doit être sur le point de convertir currentValue et la valeur actuelle de accumulator à la valeur de la nouvelle accumulator.

Cela fonctionne de manière simple, lors de l'ajout, car les valeurs de l'accumulateur et de l'élément sont toutes deux du même type (mais servent des objectifs différents).

[1, 2, 3].reduce((x, y) => x + y);

Cela fonctionne simplement parce que ce sont tous des chiffres.

[{ age: 5 }, { age: 2 }, { age: 8 }]
  .reduce((total, thing) => total + thing.age, 0);

Nous donnons maintenant une valeur de départ à l'agrégateur. La valeur de départ doit être le type que vous attendez de l'agrégateur (le type que vous attendez comme valeur finale), dans la grande majorité des cas . Si vous n'êtes pas obligé de le faire (et ne pas être), il est important de garder à l’esprit.

Une fois que vous savez cela, vous pouvez écrire des réductions significatives pour d’autres problèmes relationnels n: 1.

Enlever les mots répétés:

const skipIfAlreadyFound = (words, Word) => words.includes(Word)
    ? words
    : words.concat(Word);

const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);

Fournir un nombre de tous les mots trouvés:

const incrementWordCount = (counts, Word) => {
  counts[Word] = (counts[Word] || 0) + 1;
  return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });

Réduire un tableau de tableaux en un seul tableau plat:

const concat = (a, b) => a.concat(b);

const numbers = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
].reduce(concat, []);

Chaque fois que vous cherchez à passer d'un ensemble d'éléments à une valeur unique qui ne correspond pas à un 1: 1, vous pouvez envisager de réduire.

En fait, la carte et le filtre peuvent tous deux être implémentés sous forme de réductions:

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,
  []
);

J'espère que cela fournit un contexte supplémentaire pour l'utilisation de reduce.

L'addition à cela, que je n'ai pas encore expliquée, c'est quand on s'attend à ce que les types input et output soient spécifiquement destinés à être dynamiques, car les éléments du tableau sont des fonctions:

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);
7
Norguard

À chaque étape de votre réduction, vous ne retournez pas de nouvel objet {x:???}. Donc, soit vous devez faire:

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})

ou vous avez besoin de faire

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 
4
Jamie Wong

Pour la première itération, 'a' sera le premier objet du tableau, donc ax + bx renverra 1 + 2, c'est-à-dire 3. Maintenant, dans la prochaine itération, le 3 renvoyé est attribué à a, de sorte que a est un nombre qui appelle maintenant ax va donner NaN.

La solution simple consiste d’abord à mapper les nombres dans un tableau, puis à les réduire comme suit:

arr.map(a=>a.x).reduce(function(a,b){return a+b})

ici arr.map(a=>a.x) fournira un tableau de nombres [1,2,4] qui utilise maintenant .reduce(function(a,b){return a+b}) ajoutera simplement ces nombres sans aucun hassel

Une autre solution simple consiste simplement à fournir une somme initiale égale à zéro en affectant 0 à "a" comme ci-dessous:

arr.reduce(function(a,b){return a + b.x},0)
2
fatimasajjad

Dans la première étape, cela fonctionnera correctement car la valeur de a sera 1 et celle de b sera 2, mais comme 2 + 1 sera renvoyé et dans la prochaine étape la valeur de b sera la valeur de retour from step 1 i.e 3 et ainsi b.x sera indéfini ... et indéfini + anyNumber sera NaN et c'est pourquoi vous obtenez ce résultat.

Au lieu de cela, vous pouvez essayer ceci en donnant la valeur initiale égale à zéro, i.e

arr.reduce(function(a,b){return a + b.x},0);

2
Nitesh Ranjan

réduire la fonction itère sur une collection

arr = [{x:1},{x:2},{x:4}] // is a collection

arr.reduce(function(a,b){return a.x + b.x})

se traduit par:

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. 
);

lors de chaque rappel d'index, la valeur de la variable "accumulator" est passé dans "un" paramètre dans la fonction de rappel. Si nous n'initialisons pas "accumulateur", sa valeur sera indéfinie. Appeler undefined.x vous donnerait une erreur. 

Pour résoudre ce problème, initialisez "accumulateur" avec la valeur 0 comme le montre la réponse de Casey ci-dessus.

Pour comprendre les entrées-sorties de la fonction "réduire", je vous suggère de consulter le code source de cette fonction ... La bibliothèque Lodash a une fonction de réduction qui fonctionne exactement comme la fonction "réduire" de ES6.

Voici le lien: réduire le code source

0
Deen John
    
  
 //fill creates array with n element
 //reduce requires 2 parameter , 3rd parameter as a length
 var fibonacci = (n) => Array(n).fill().reduce((a, b, c) => {
      return a.concat(c < 2 ? c : a[c - 1] + a[c - 2])
  }, [])
  console.log(fibonacci(8))
0
Rajesh Bose

pour renvoyer une somme de tous les accessoires x:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)
0
Black Jack