Javascript reduziert das Array von Objekten


224

Angenommen, ich möchte a.xfür jedes Element in summieren arr.

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

Ich habe Grund zu der Annahme, dass die Axt irgendwann undefiniert ist.

Das Folgende funktioniert gut

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

Was mache ich im ersten Beispiel falsch?


1
Ich denke auch, Sie meinen arr.reduce(function(a,b){return a + b})im zweiten Beispiel.
Jamie Wong

1
Danke für die Korrektur. Ich bin hier auf Reduzieren
gestoßen

6
@ Jamie Wong es ist eigentlich ein Teil von JavaScript 1.8
JaredMcAteer

@OriginalSyn ja - habe das gerade gesehen. Interessant, aber da es keine vollständige native Unterstützung bietet, ist die Implementierung bei der Beantwortung solcher Fragen immer noch wichtig.
Jamie Wong

3
JavaScript-Versionen sind nur Versionen des Firefox-Interpreters. Es ist verwirrend, auf sie zu verweisen. Es gibt nur ES3 und ES5.
Raynos

Antworten:


279

Nach der ersten Iteration geben Sie eine Zahl zurück und versuchen dann, die Eigenschaft xdavon zu erhalten, um sie dem nächsten Objekt hinzuzufügen, undefinedund Mathematik mit undefinedErgebnissen in NaN.

Versuchen Sie, ein Objekt zurückzugeben, das eine xEigenschaft mit der Summe der x-Eigenschaften der Parameter enthält:

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}

Erklärung aus Kommentaren hinzugefügt:

Der Rückgabewert jeder Iteration [].reducewird als aVariable in der nächsten Iteration verwendet.

Iteration 1: a = {x:1}, b = {x:2}, {x: 3}zugewiesen ain Iteration 2

Iteration 2: a = {x:3}, b = {x:4}.

Das Problem mit Ihrem Beispiel ist, dass Sie ein Zahlenliteral zurückgeben.

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

Iteration 1: a = {x:1}, b = {x:2}, // returns 3wie ain der nächsten Iteration

Iteration 2 : a = 3, b = {x:2}kehrt zurückNaN

Ein Zahlenliteral 3hat (normalerweise) keine Eigenschaft, die aufgerufen wird, xalso ist es undefinedund gibt undefined + b.xzurück NaNund NaN + <anything>ist immerNaN

Klarstellung : Ich bevorzuge meine Methode gegenüber der anderen Top-Antwort in diesem Thread, da ich nicht der Meinung bin, dass das Übergeben eines optionalen Parameters zum Reduzieren mit einer magischen Zahl zum Herausholen eines Zahlenprimitivs sauberer ist. Es kann dazu führen, dass weniger Zeilen geschrieben werden, aber imo ist es weniger lesbar.


7
Sicherlich reduziert Reduce eine Funktion, um Operationen an jedem der Elemente in einem Array auszuführen. Jedes Mal, wenn ein Wert zurückgegeben wird, der als nächste 'a'-Variable in der Operation verwendet wird. Also erste Iteration a = {x: 1}, b = {x: 2}, dann zweite Iteration a = {x: 3} (kombinierter Wert der ersten Iteration), b = {x: 4}. Das Problem mit Ihrem Beispiel in der zweiten Iteration, bei der versucht wurde, 3.x + bx, 3 hinzuzufügen, hat keine Eigenschaft namens x, daher wurde undefiniert zurückgegeben und das Hinzufügen zu bx (4)
ergab

Ich verstehe die Erklärung, sehe aber immer noch nicht, wie {x: ...} verhindert, dass Iteration 2 ax aufruft? Was bedeutet das x? Ich habe versucht, diesen Ansatz mit einer Methode zu verwenden, und scheint nicht zu funktionieren
mck

Lesen Sie einfach stackoverflow.com/questions/2118123/… und verstehen Sie, wie es funktioniert. Aber wie würden Sie es tun, wenn Sie eine Methode haben und nicht nur reine Attribute?
mck

277

Ein sauberer Weg, dies zu erreichen, ist die Angabe eines Anfangswertes:

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

Wenn die anonyme Funktion zum ersten Mal aufgerufen wird, wird sie mit aufgerufen (0, {x: 1})und kehrt zurück 0 + 1 = 1. Das nächste Mal wird es mit aufgerufen (1, {x: 2})und kehrt zurück 1 + 2 = 3. Es wird dann mit gerufen (3, {x: 4})und kehrt schließlich zurück 7.


1
Dies ist die Lösung, wenn Sie einen Anfangswert (2. Reduktionsparameter)
TJ haben.

Danke, ich habe nach dem Hinweis mit dem zweiten Argument für gesucht reduce.
Nilsole

Dies ist weniger Code und direkter als die akzeptierte Antwort. Gibt es eine Möglichkeit, wie die akzeptierte Antwort besser ist als diese?
22.

1
Dies hat mir geholfen, den Fall zu lösen, in dem die Array-Länge manchmal nur 1 beträgt.
HussienK

1
es6 way var arr = [{x: 1}, {x: 2}, {x: 4}]; sei a = arr.reduce ((acc, obj) => acc + obj.x, 0); console.log (a);
Amar Ghodke

76

TL; DR, stellen Sie den Anfangswert ein

Mit Destrukturierung

arr.reduce( ( sum, { x } ) => sum + x , 0)

Ohne zu zerstören

arr.reduce( ( sum , cur ) => sum + cur.x , 0)

Mit Typoskript

arr.reduce( ( sum, { x } : { x: number } ) => sum + x , 0)

Versuchen wir die Destrukturierungsmethode:

const arr = [ { x: 1 }, { x: 2 }, { x: 4 } ]
const result = arr.reduce( ( sum, { x } ) => sum + x , 0)
console.log( result ) // 7

Der Schlüssel dazu ist das Einstellen des Anfangswertes. Der Rückgabewert wird zum ersten Parameter der nächsten Iteration.

Die in der Top-Antwort verwendete Technik ist nicht idiomatisch

Die akzeptierte Antwort schlägt vor, den "optionalen" Wert NICHT zu übergeben. Dies ist falsch, da idiomatisch immer der zweite Parameter enthalten sein muss. Warum? Drei Gründe:

1. Gefährlich - Die Nichtübergabe des Anfangswertes ist gefährlich und kann zu Nebenwirkungen und Mutationen führen, wenn die Rückruffunktion nachlässig ist.

Erblicken

const badCallback = (a,i) => Object.assign(a,i) 

const foo = [ { a: 1 }, { b: 2 }, { c: 3 } ]
const bar = foo.reduce( badCallback )  // bad use of Object.assign
// Look, we've tampered with the original array
foo //  [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]

Wenn wir es jedoch so gemacht hätten, mit dem Anfangswert:

const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // { a: 1, b: 2, c: 3 }

Setzen Sie für den Datensatz den ersten Parameter von Object.assignauf ein leeres Objekt , es sei denn, Sie möchten das ursprüngliche Objekt mutieren . So : Object.assign({}, a, b, c).

2 - Bessere Typinferenz - Wenn Sie ein Tool wie Typescript oder einen Editor wie VS Code verwenden, können Sie dem Compiler die Initiale mitteilen, und es kann Fehler abfangen, wenn Sie es falsch machen. Wenn Sie den Anfangswert nicht festlegen, kann er in vielen Situationen möglicherweise nicht erraten werden, und es kann zu gruseligen Laufzeitfehlern kommen.

3 - Respektiere die Funktoren - JavaScript leuchtet am besten, wenn sein inneres funktionales Kind freigesetzt wird. In der Funktionswelt gibt es einen Standard für das "Falten" oder reduceein Array. Wenn Sie das Array falten oder einen Katamorphismus anwenden , verwenden Sie die Werte dieses Arrays, um einen neuen Typ zu erstellen . Sie müssen den resultierenden Typ kommunizieren - Sie sollten dies auch dann tun, wenn der endgültige Typ der der Werte im Array, einem anderen Array oder einem anderen Typ ist.

Lassen Sie uns anders darüber nachdenken. In JavaScript können Funktionen wie Daten weitergegeben werden. So funktionieren Rückrufe. Was ist das Ergebnis des folgenden Codes?

[1,2,3].reduce(callback)

Wird es eine Nummer zurückgeben? Ein Objekt? Das macht es klarer

[1,2,3].reduce(callback,0)

Weitere Informationen zur funktionalen Programmierspezifikation finden Sie hier: https://github.com/fantasyland/fantasy-land#foldable

Noch etwas Hintergrund

Die reduceMethode akzeptiert zwei Parameter:

Array.prototype.reduce( callback, initialItem )

Die callbackFunktion akzeptiert die folgenden Parameter

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }

Für die erste Iteration

  • Wenn angegeben initialItem, reduceübergibt die Funktion das initialItemals accumulatorund das erste Element des Arrays als itemInArray.

  • Falls initialItemist nicht vorgesehen, die reducevergeht Funktion auf das erste Element in dem Array als initialItemund dem zweiten Element im Array wie itemInArraydem Verhalten verwirrend sein kann.

Ich unterrichte und empfehle, immer den Anfangswert von Reduzieren einzustellen.

Sie können die Dokumentation unter folgender Adresse einsehen:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

Hoffe das hilft!


1
Destrukturierung ist genau das, was ich wollte. Klappt wunderbar. Danke dir!
AleksandrH

24

Andere haben diese Frage beantwortet, aber ich dachte, ich würde einen anderen Ansatz wählen. Anstatt direkt zur Summierungsaxt zu wechseln, können Sie eine Karte (von Axt zu x) kombinieren und reduzieren (um die x hinzuzufügen):

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

Zugegeben, es wird wahrscheinlich etwas langsamer sein, aber ich dachte, es lohnt sich, es als Option zu erwähnen.


8

Um zu formalisieren, worauf hingewiesen wurde, ist ein Reduzierer ein Katamorphismus, der zwei Argumente verwendet, die zufällig vom gleichen Typ sein können, und einen Typ zurückgibt, der dem ersten Argument entspricht.

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

Das bedeutet, dass es beim Körper des Reduzierers darum gehen muss, currentValueden aktuellen Wert von accumulatorin den Wert des neuen umzuwandeln accumulator.

Dies funktioniert beim Hinzufügen auf einfache Weise, da sowohl der Akkumulator- als auch der Elementwert vom selben Typ sind (aber unterschiedlichen Zwecken dienen).

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

Das funktioniert nur, weil sie alle Zahlen sind.

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

Jetzt geben wir dem Aggregator einen Startwert. Der Startwert sollte in den allermeisten Fällen der Typ sein, den Sie vom Aggregator erwarten (der Typ, von dem Sie erwarten, dass er als Endwert herauskommt). Sie sind zwar nicht dazu gezwungen (und sollten es auch nicht sein), aber es ist wichtig, dies zu berücksichtigen.

Sobald Sie das wissen, können Sie sinnvolle Reduzierungen für andere n: 1-Beziehungsprobleme schreiben.

Wiederholte Wörter entfernen:

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

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

Angabe einer Anzahl aller gefundenen Wörter:

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

Reduzieren eines Arrays von Arrays auf ein einzelnes flaches Array:

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

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

Jedes Mal, wenn Sie von einer Reihe von Dingen zu einem einzelnen Wert wechseln möchten, der nicht 1: 1 entspricht, sollten Sie eine Reduzierung in Betracht ziehen.

Tatsächlich können sowohl Map als auch Filter als Reduzierungen implementiert werden:

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

Ich hoffe, dies bietet einen weiteren Kontext für die Verwendung reduce.

Die einzige Ergänzung, auf die ich noch nicht eingegangen bin, ist die Erwartung, dass die Eingabe- und Ausgabetypen speziell dynamisch sein sollen, da die Array-Elemente Funktionen sind:

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

1
+1 für eine klare Erklärung in Bezug auf das Typsystem. OTOH, ich denke, wenn ich mit der Idee beginne, dass es ein Katamorphismus ist, kann dies für viele
abstoßend sein

6

Für die erste Iteration ist 'a' das erste Objekt im Array, daher gibt ax + bx 1 + 2 zurück, dh 3. In der nächsten Iteration wird die zurückgegebene 3 a zugewiesen, also ist a eine Zahl, die jetzt n ax nennt wird NaN geben.

Eine einfache Lösung besteht darin, zuerst die Zahlen im Array abzubilden und sie dann wie folgt zu reduzieren:

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

Hier arr.map(a=>a.x)finden Sie eine Reihe von Zahlen [1,2,4], mit denen .reduce(function(a,b){return a+b})Sie diese Zahlen jetzt problemlos hinzufügen können

Eine andere einfache Lösung besteht darin, eine Anfangssumme als Null bereitzustellen, indem 'a' wie folgt 0 zugewiesen wird:

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

5

Bei jedem Schritt Ihrer Reduzierung geben Sie kein neues {x:???}Objekt zurück. Sie müssen also entweder Folgendes tun:

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

oder du musst tun

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

3
Das erste Beispiel benötigt einen Standardwert (z. B. 0), andernfalls ist "a" in der ersten Iteration undefiniert.
Griffin

2

Im ersten Schritt funktioniert es einwandfrei, da der Wert von a1 und der von b2 ist, aber 2 + 1 zurückgegeben wird und im nächsten Schritt der Wert von bder Rückgabewert ist from step 1 i.e 3und somit b.xundefiniert ist. .und undefiniert + anyNumber ist NaN und deshalb erhalten Sie dieses Ergebnis.

Stattdessen können Sie dies versuchen, indem Sie den Anfangswert als Null angeben, d. H.

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


Bitte aktualisieren Sie dies, um zu zeigen, wie sich Ihr Ansatz von den anderen unterscheidet.
Kwelch

2

Wenn Sie ein komplexes Objekt mit vielen Daten haben, wie z. B. ein Array von Objekten, können Sie dies schrittweise lösen.

Zum Beispiel:

const myArray = [{ id: 1, value: 10}, { id: 2, value: 20}];

Zunächst sollten Sie Ihr Array einem neuen Array von Interesse zuordnen. In diesem Beispiel kann es sich um ein neues Array von Werten handeln.

const values = myArray.map(obj => obj.value);

Diese Rückruffunktion gibt ein neues Array zurück, das nur Werte aus dem ursprünglichen Array enthält, und speichert es in den Werten const. Jetzt ist Ihre Werte const ein Array wie folgt:

values = [10, 20];

Und jetzt sind Sie bereit, Ihre Reduzierung durchzuführen:

const sum = values.reduce((accumulator, currentValue) => { return accumulator + currentValue; } , 0);

Wie Sie sehen können, führt die Reduktionsmethode die Rückruffunktion mehrmals aus. Für jedes Mal wird der aktuelle Wert des Elements im Array genommen und mit dem Akkumulator summiert. Um es richtig zu summieren, müssen Sie den Anfangswert Ihres Akkumulators als zweites Argument der Reduktionsmethode festlegen.

Jetzt haben Sie Ihre neue konstante Summe mit dem Wert 30.


0

Reduzierungsfunktion iteriert über eine Sammlung

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

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

wird übersetzt in:

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

Während jedes Indexrückrufs wird der Wert der Variablen "Akkumulator" in der Rückruffunktion an den Parameter "a" übergeben. Wenn wir "Akkumulator" nicht initialisieren, ist sein Wert undefiniert. Wenn Sie undefined.x aufrufen, erhalten Sie einen Fehler.

Um dies zu lösen, initialisieren Sie "Akkumulator" mit dem Wert 0, wie Caseys Antwort oben gezeigt hat.

Um die Vor- und Nachteile der Funktion "Reduzieren" zu verstehen, sollten Sie sich den Quellcode dieser Funktion ansehen. Die Lodash-Bibliothek verfügt über eine Reduktionsfunktion, die genauso funktioniert wie die "Reduktions" -Funktion in ES6.

Hier ist der Link: Quellcode reduzieren


0

um eine Summe aller xRequisiten zurückzugeben:

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

3
Wenn Sie einen Kontext angeben, warum diese Antwort besser ist als die anderen akzeptierten oder positiv bewerteten, können Sie den Benutzern bei der Suche nach einer Lösung für ihre Frage helfen.
Andrew

0

Sie können die Karte verwenden und Funktionen reduzieren

const arr = [{x:1},{x:2},{x:4}];
const sum = arr.map(n => n.x).reduce((a, b) => a + b, 0);

-1

Die Array-Reduktionsfunktion verwendet drei Parameter, nämlich initialValue (Standard ist 0), Akkumulator und aktueller Wert. Standardmäßig ist der Wert von initialValue "0". welches vom Akku genommen wird

Lassen Sie uns dies im Code sehen.

var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal ) ; 
// (remember Initialvalue is 0 by default )

//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks .
// solution = 7

Jetzt gleiches Beispiel mit Anfangswert:

var initialValue = 10;
var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal,initialValue ) ; 
/
// (remember Initialvalue is 0 by default but now it's 10 )

//first iteration** : 10 +1 => Now accumulator =11;
//second iteration** : 11 +2 => Now accumulator =13;
//third iteration** : 13 + 4 => Now accumulator = 17;
No more array properties now the loop breaks .
//solution=17

Gleiches gilt auch für die Objekt-Arrays (die aktuelle Stackoverflow-Frage):

var arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(acc,currVal){return acc + currVal.x}) 
// destructing {x:1} = currVal;
Now currVal is object which have all the object properties .So now 
currVal.x=>1 
//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks 
//solution=7

Eine Sache, die man im Kopf behalten sollte, ist InitialValue. Standardmäßig ist 0 und kann alles gegeben werden, was ich meine {}, [] und Nummer


-1

    
  
 //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))


-2

Sie sollten keine Axt als Akkumulator verwenden. Stattdessen können Sie Folgendes tun: arr = [{x: 1}, {x: 2}, {x: 4}]

arr.reduce (Funktion (a, b) {a + bx}, 0) `

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.