382 Stimmen

Wie ruft man reduce auf ein Array von Objekten auf, um deren Eigenschaften zu summieren?

Sagen wir, ich möchte summieren a.x für jedes Element in 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 a.x es undefined zu einem bestimmten Zeitpunkt.

Das Folgende funktioniert gut

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

Was mache ich im ersten Beispiel falsch?

476voto

Casey Chu Punkte 23975

Eine sauberere Methode, dies zu erreichen, ist die Angabe eines Anfangswerts als zweites Argument für reduce :

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

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

Dies behandelt auch den Fall, dass das Array leer ist und gibt 0 .

412voto

JaredMcAteer Punkte 19595

Nach der ersten Iteration geben Sie eine Zahl zurück und versuchen dann, die Eigenschaft x davon zum nächsten Objekt hinzuzufügen, das undefined und Mathe mit undefined führt zu NaN .

versuchen, ein Objekt zurückzugeben, das eine x Eigenschaft mit der Summe der x-Eigenschaften der Parameter:

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}

Erläuterung aus Kommentaren hinzugefügt:

Der Rückgabewert jeder Iteration von [].reduce verwendet als a Variable in der nächsten Iteration.

Iteration 1: a = {x:1} , b = {x:2} , {x: 3} zugewiesen a in 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 3 comme a bei der nächsten Iteration

Iteration 2: a = 3 , b = {x:2} gibt zurück. NaN

Eine wörtliche Zahl 3 hat (normalerweise) keine Eigenschaft namens x Es ist also undefined y undefined + b.x gibt zurück. NaN et NaN + <anything> ist immer NaN

Klärung : Ich bevorzuge meine Methode gegenüber der anderen Top-Antwort in diesem Thread, da ich nicht mit der Idee einverstanden bin, dass die Übergabe eines optionalen Parameters an reduce mit einer magischen Zahl, um ein Zahlenprimitiv zu erhalten, sauberer ist. Es mag dazu führen, dass weniger Zeilen geschrieben werden, aber imo ist es weniger lesbar.

159voto

Babakness Punkte 2714

TL;DR, den Anfangswert festlegen

Verwendung von Destrukturierung

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

Ohne Destrukturierung

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

Mit Typoskript

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

Versuchen wir es mit der Methode der Destrukturierung:

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 die Festlegung des Anfangswerts. Der Rückgabewert wird zum ersten Parameter der nächsten Iteration.

Die in der oberen Antwort verwendete Technik ist nicht idiomatisch

Die akzeptierte Antwort schlägt vor, den "optionalen" Wert NICHT zu übergeben. Dies ist falsch, da der idiomatische Weg ist, dass der zweite Parameter immer einbezogen werden. Warum? Aus drei Gründen:

1. Gefährlich -- Die Nichtübergabe des Anfangswerts ist gefährlich und kann zu Nebenwirkungen und Mutationen führen, wenn die Callback-Funktion unvorsichtig ist.

Siehe

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 } ]

Hätten wir es aber so gemacht, mit dem Ausgangswert:

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

Für das Protokoll, es sei denn, Sie beabsichtigen, das ursprüngliche Objekt zu verändern, setzen Sie den ersten Parameter von Object.assign auf ein leeres Objekt. Zum Beispiel so: Object.assign({}, a, b, c) .

2 - Bessere Typeninferenz -Wenn Sie ein Tool wie Typescript oder einen Editor wie VS Code verwenden, haben Sie den Vorteil, dass Sie dem Compiler den Anfangswert mitteilen und er Fehler erkennen kann, wenn Sie es falsch machen. Wenn Sie den Anfangswert nicht angeben, kann er in vielen Situationen nicht erraten, was zu gruseligen Laufzeitfehlern führen kann.

3 - Respektieren Sie die Funktionsträger -- JavaScript glänzt am besten, wenn sein inneres funktionales Kind entfesselt wird. In der funktionalen Welt gibt es einen Standard, wie Sie "falten" oder reduce ein Array. Wenn Sie ein Array falten oder anwenden Katamorphismus zum Array hinzufügen, nehmen Sie die Werte dieses Arrays, um einen neuen Typ zu konstruieren. Sie müssen den resultierenden Typ mitteilen - das sollten Sie auch dann tun, wenn der endgültige Typ der der Werte in dem Array, einem anderen Array oder einem anderen Typ entspricht.

Betrachten wir es einmal anders. 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 sie eine Zahl zurückgeben? Ein Objekt? Das macht es klarer

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

Lesen Sie hier mehr über die Spezifikation der funktionalen Programmierung: https://github.com/fantasyland/fantasy-land#foldable

Einige weitere Hintergrundinformationen

Les reduce Methode benötigt zwei Parameter,

Array.prototype.reduce( callback, initialItem )

Les callback Funktion nimmt folgende Parameter entgegen

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

Für die erste Iteration,

  • Si initialItem vorgesehen ist, wird die reduce Funktion übergibt die initialItem als die accumulator und das erste Element des Arrays als itemInArray .

  • Si initialItem es no vorausgesetzt, die reduce Funktion übergibt das erste Element im Array als initialItem und das zweite Element im Array als itemInArray was ein verwirrendes Verhalten sein kann.

Ich lehre und empfehle, immer den Anfangswert von reduce einzustellen.

Sie können die Dokumentation unter folgender Adresse einsehen:

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

Ich hoffe, das hilft!

39voto

RHSeeger Punkte 15604

Andere haben diese Frage bereits beantwortet, aber ich wollte noch einen weiteren Ansatz hinzufügen. Anstatt direkt zur Summierung von a.x zu gehen, können Sie eine Abbildung (von a.x nach x) und eine Reduktion (zum Addieren der x's) kombinieren:

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 wäre eine Erwähnung der Option wert.

9voto

Norguard Punkte 25344

Um das Gesagte zu formalisieren, ist ein Reduzierer ein Katamorphismus, der zwei Argumente annimmt, 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 der Körper des Reduzierstücks etwa die Umwandlung von currentValue und der aktuelle Wert des accumulator auf den Wert des neuen accumulator .

Dies funktioniert beim Addieren auf einfache Weise, da der Akkumulator und die Elementwerte beide vom gleichen 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 für den Aggregator erwarten (der Typ, den Sie als Endwert erwarten). Sie sind zwar nicht gezwungen, dies zu tun (und sollten es auch nicht), aber es ist wichtig, daran zu denken.

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

Entfernen von Wortwiederholungen:

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

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

Zählung aller gefundenen Wörter:

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

Reduktion eines Arrays von Arrays auf ein einziges 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 übergehen wollen, der nicht 1:1 übereinstimmt, ist reduzieren etwas, das Sie in Betracht ziehen könnten.

Map und Filter können beide als Reduktionen 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, dass dies weitere Informationen über die Verwendung von reduce .

Die einzige Ergänzung dazu, die ich noch nicht untersucht habe, ist, wenn erwartet wird, dass die Eingabe- und Ausgabetypen speziell dynamisch sein sollen, weil 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);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X