wake-up-neo.com

Javascript auf Array von Objekten reduzieren

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?

150
YXD

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.xNaN 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.

201
JaredMcAteer

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.

192
Casey Chu

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.

Die in der Antwort verwendete Technik ist nicht idiomatisch

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

Noch etwas Hintergrund

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!

43
Babakness

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.

22
RHSeeger

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

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}; }) 
4
Jamie Wong

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)
2
fatimasajjad

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.

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

2
Nitesh Ranjan

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

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

um eine Summe aller x Requisiten zurückzugeben:

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