Angenommen, ich möchte a.x
für jedes Element in arr
summieren.
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 irgendwann undefiniert ist.
Folgendes funktioniert gut
arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7
Was mache ich im ersten Beispiel falsch?
Nach der ersten Iteration geben Sie eine Zahl zurück und versuchen dann, die Eigenschaft x
davon zu erhalten, um sie dem nächsten Objekt hinzuzufügen, nämlich undefined
, und maths mit undefined
führt zu NaN
.
versuchen Sie, ein Objekt zurückzugeben, das eine x
-Eigenschaft enthält, 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
, der in der nächsten Iteration als Variable a
verwendet wird.
Iteration 1: a = {x:1}
, b = {x:2}
, {x: 3}
, der a
in Iteration 2 zugewiesen ist
Iteration 2: a = {x:3}
, b = {x:4}
.
Das Problem bei 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
als a
in der nächsten Iteration
Iteration 2: a = 3
, b = {x:2}
gibt NaN
zurück
Ein Zahlenliteral 3
hat (normalerweise) keine Eigenschaft namens x
, daher ist undefined
und undefined + b.x
NaN
und NaN + <anything>
ist immer NaN
.
Klarstellung : Ich bevorzuge meine Methode der anderen Top-Antwort in diesem Thread, da ich nicht der Meinung bin, dass das Übergeben eines optionalen Parameters, der mit einer magischen Zahl reduziert wird, um ein Zahlenprimitiv zu erhalten, sauberer ist. Dies kann dazu führen, dass weniger Zeilen geschrieben werden, aber es ist weniger lesbar.
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);
Beim ersten Aufruf der anonymen Funktion wird sie mit (0, {x: 1})
aufgerufen und gibt 0 + 1 = 1
zurück. Das nächste Mal wird es mit (1, {x: 2})
aufgerufen und gibt 1 + 2 = 3
zurück. Es wird dann mit (3, {x: 4})
aufgerufen und schließlich 7
zurückgegeben.
TL; DR, setze den Anfangswert
Mit destructuring
arr.reduce( ( sum, { x } ) => sum + x , 0)
Ohne zu zerstören
arr.reduce( ( sum , cur ) => sum + cur.x , 0)
Mit TypeScript
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 Anfangswerts. Der Rückgabewert wird zum ersten Parameter der nächsten Iteration.
In der akzeptierten Antwort wird vorgeschlagen, den "optionalen" Wert NICHT zu übergeben. Dies ist falsch, da der zweite Parameter idiomatisch immer enthalten sein muss. Warum? Drei Gründe:
1. Gefährlich - Das Nichtübergeben des Anfangswerts ist gefährlich und kann bei fahrlässiger Rückruffunktion zu Nebenwirkungen und Mutationen führen.
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 tempered 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.assign
Auf ein leeres Objekt, es sei denn, Sie beabsichtigen, das ursprüngliche Objekt zu mutieren. So wie folgt: 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 und diese mitteilen 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 Functors - JavaScript scheint am besten, wenn sein inneres funktionierendes Kind entfesselt ist. In der funktionalen Welt gibt es einen Standard dafür, wie Sie ein Array "falten" oder reduce
. Wenn Sie ein Katamorphismus auf das Array falten oder anwenden, verwenden Sie die Werte dieses Arrays, um einen neuen Typ zu erstellen. Sie müssen den resultierenden Typ mitteilen. Dies sollten Sie auch dann tun, wenn der endgültige Typ der Werte im Array, einem anderen Array oder einem anderen Typ ist.
Überlegen wir es uns 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 es eine Nummer zurückgeben? Ein Objekt? Das macht es klarer
[1,2,3].reduce(callback,0)
Lesen Sie hier mehr über die funktionale Programmierspezifikation: https://github.com/fantasyland/fantasy-land#foldable
Die reduce
-Methode akzeptiert zwei Parameter:
Array.prototype.reduce( callback, initialItem )
Die Funktion callback
akzeptiert die folgenden Parameter
(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }
Wenn initialItem
angegeben ist, wird es als accumulator
an die callback
-Funktion übergeben, und itemInArray
ist das erste Element im Array.
Wenn initialItem
nicht festgelegt ist, nimmt der reduce
das erste Element im Array als initialItem
und übergibt das zweite Element als itemInArray
. Dies kann verwirrend sein.
Ich unterrichte und empfehle, immer den Anfangswert von redu zu setzen.
Für einen vollständigen Artikel über Array.prototype.reduce
Siehe:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
Hoffe das hilft!
Andere haben diese Frage beantwortet, aber ich dachte, ich würde einen anderen Ansatz wählen. Anstatt direkt zum Summieren von a.x zu gehen, können Sie eine Karte (von a.x bis x) kombinieren und verkleinern (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;});
Zugegebenermaßen wird es wahrscheinlich etwas langsamer sein, aber ich dachte, es wäre eine Erwähnung wert.
Um das Gesagte zu formalisieren, ist ein Reduktionsmittel 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 es im Körper des Reduzierers darum gehen muss, currentValue
und den aktuellen Wert der accumulator
in den Wert der neuen accumulator
umzuwandeln.
Dies funktioniert beim Hinzufügen auf unkomplizierte Weise, da der Akkumulator und die Elementwerte beide 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 der Typ sein, von dem der Aggregator erwartet wird (der Typ, den Sie als Endwert erwarten lassen), in den allermeisten Fällen ..__ Sie sind dazu nicht gezwungen (und sollten dies nicht tun) Es ist wichtig, sich daran zu erinnern.
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, []);
Anzahl aller gefundenen Wörter angeben:
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 mit einem 1: 1-Wert übereinstimmt, sollten Sie die Reduzierung in Betracht ziehen.
Tatsächlich können Map und 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, dass dies einen weiteren Kontext für die Verwendung von reduce
bietet.
Der eine Zusatz dazu, den ich noch nicht kennengelernt habe, ist, wenn erwartet wird, dass die Eingabe- und Ausgabetypen speziell als dynamisch gedacht sind, 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);
Bei jedem Schritt Ihrer Reduzierung geben Sie kein neues {x:???}
-Objekt zurück. Sie müssen entweder tun:
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})
oder du musst es tun
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; })
Für die erste Iteration ist 'a' das erste Objekt im Array, daher gibt ax + bx 1 + 2, dh 3 zurück. 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 wird arr.map(a=>a.x)
ein Array von Zahlen [1,2,4] bereitstellen, die jetzt .reduce(function(a,b){return a+b})
verwenden, und diese Zahlen einfach und problemlos hinzufügen
Eine andere einfache Lösung besteht darin, eine anfängliche Summe als Null anzugeben, indem Sie 'a' wie folgt eine 0 zuweisen:
arr.reduce(function(a,b){return a + b.x},0)
Im ersten Schritt funktioniert es einwandfrei, da der Wert von a
1 und der Wert von b
2 ist. Da jedoch 2 + 1 zurückgegeben wird, wird im nächsten Schritt der Wert von b
der Rückgabewert from step 1 i.e 3
und somit b.x
. wird undefined sein und undefined + anyNumber wird NaN sein und deswegen erhalten Sie dieses Ergebnis.
arr.reduce(function(a,b){return a + b.x},0);
funktion verkleinern 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 Index-Callbacks lautet der Wert der Variablen "Accumulator" wird in der Callback-Funktion an einen Parameter "a" übergeben. Wenn wir "akkumulator" nicht initialisieren, ist sein Wert undefiniert. Der Aufruf von undefined.x würde zu einem Fehler führen.
Um dies zu lösen, initialisieren Sie "Accumulator" mit dem Wert 0, wie Caseys Antwort oben gezeigt hat.
Um die In-Outs der "Reduzieren" -Funktion zu verstehen, würde ich vorschlagen, dass Sie sich den Quellcode dieser Funktion ansehen. Die Lodash-Bibliothek hat eine Reduktionsfunktion, die genau wie die "Reduzieren" -Funktion in ES6 funktioniert.
Hier ist der Link: Quellcode reduzieren
//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))
um eine Summe aller x
Requisiten zurückzugeben:
arr.reduce(
(a,b) => (a.x || a) + b.x
)