Javascript si riduce sull'array di oggetti


226

Di 'che voglio sommare a.xper ogni elemento in arr.

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

Ho motivo di credere che l'ascia sia indefinita ad un certo punto.

Di seguito funziona bene

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

Cosa sto sbagliando nel primo esempio?


1
Inoltre, penso che intendi arr.reduce(function(a,b){return a + b})nel secondo esempio.
Jamie Wong,

1
Grazie per la correzione. Mi sono imbattuto qui: developer.mozilla.org/it/JavaScript/Reference/Global_Objects/…
YXD

6
@Jamie Wong è in realtà una parte di JavaScript 1.8
JaredMcAteer

@OriginalSyn sì - l'ho appena visto. Interessante, ma poiché non ha il supporto nativo completo, l'implementazione è ancora importante quando si risponde a domande come questa.
Jamie Wong,

3
Le versioni JavaScript sono solo versioni dell'interprete di Firefox, è confuso fare riferimento a loro. Esistono solo ES3 ed ES5.
Raynos,

Risposte:


279

Dopo la prima iterazione stai restituendo un numero e poi provando a ottenerne la proprietà xda aggiungere all'oggetto successivo che è undefinede la matematica che comporta i undefinedrisultati inNaN .

prova a restituire un oggetto contenente una xproprietà con la somma delle proprietà x dei parametri:

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}

Spiegazione aggiunta dai commenti:

Il valore restituito di ogni iterazione di [].reduceusato comea variabile nella successiva iterazione.

Iterazione 1: a = {x:1}, b = {x:2}, {x: 3}assegnato aa in Iterazione 2

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

Il problema con il tuo esempio è che stai restituendo un numero letterale.

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

Iterazione 1: a = {x:1}, b = {x:2}, // returns 3comea in prossima iterazione

Iterazione 2: a = 3,b = {x:2} i rendimentiNaN

Un numero letterale 3non ha (in genere) una proprietà chiamata, xquindi è undefinede undefined + b.xrestituisce NaNed NaN + <anything>è sempreNaN

Chiarimento : preferisco il mio metodo rispetto all'altra risposta migliore in questo thread in quanto non sono d'accordo con l'idea che passare un parametro opzionale per ridurre con un numero magico per ottenere un numero primitivo sia più pulito. Può comportare un minor numero di righe scritte, ma è meno leggibile.


7
Sicuramente ridurre accetta una funzione per eseguire operazioni su ciascuno degli elementi in un array. Ogni volta che restituisce un valore che viene utilizzato come variabile 'a' successiva nell'operazione. Quindi prima iterazione a = {x: 1}, b = {x: 2} quindi seconda iterazione a = {x: 3} (valore combinato della prima iterazione), b = {x: 4}. Il problema con il tuo esempio nella seconda iterazione stava cercando di aggiungere 3.x + bx, 3 non ha una proprietà chiamata x quindi è tornato indefinito e aggiungendo che a bx (4) ha restituito Not a Number
JaredMcAteer

Capisco la spiegazione, ma ancora non vedo come {x: ...} impedisce all'iterazione 2 di chiamare ax? Cosa significa quella x? Ho provato a usare questo approccio con un metodo e sembra non funzionare
mck

in realtà, basta leggere stackoverflow.com/questions/2118123/… e capire come funziona .. ma come lo faresti quando avessi un metodo, piuttosto che semplici attributi?
mck,

278

Un modo più pulito per raggiungere questo obiettivo è fornire un valore iniziale:

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

La prima volta che viene chiamata la funzione anonima, viene chiamata con (0, {x: 1})e ritorna 0 + 1 = 1. La prossima volta, viene chiamato con (1, {x: 2})e ritorna 1 + 2 = 3. Viene quindi chiamato con (3, {x: 4}), finalmente tornando 7.


1
Questa è la soluzione se si dispone di un valore iniziale (2 ° parametro di ridurre)
TJ.

Grazie, stavo cercando il suggerimento con il secondo argomento per reduce.
nilsole,

Questo è meno codice e più diretto della risposta accettata - c'è un modo in cui la risposta accettata è migliore di questa?
jbyrd,

1
Questo mi ha aiutato a prendere il caso quando la lunghezza dell'array è solo 1 a volte.
HussienK,

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

76

TL; DR, imposta il valore iniziale

Usando la destrutturazione

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

Senza destrutturare

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

Con dattiloscritto

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

Proviamo il metodo di destrutturazione:

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

La chiave di ciò è l'impostazione del valore iniziale. Il valore restituito diventa il primo parametro della successiva iterazione.

La tecnica utilizzata nella risposta migliore non è idiomatica

La risposta accettata propone di NON passare il valore "opzionale". Questo è sbagliato, poiché il modo idiomatico è che il secondo parametro sia sempre incluso. Perché? Tre motivi:

1. Pericoloso : non passare il valore iniziale è pericoloso e può creare effetti collaterali e mutazioni se la funzione di richiamata è trascurata.

Ecco

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

Se tuttavia lo avessimo fatto in questo modo, con il valore iniziale:

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

Per la cronaca, a meno che non si intenda mutare l'oggetto originale, impostare il primo parametro su Object.assignsu un oggetto vuoto. Come questo:Object.assign({}, a, b, c) .

2 - Migliore inferenza del tipo quando si utilizza uno strumento come Typescript o un editor come VS Code, si ottiene il vantaggio di dire al compilatore l'iniziale e che può rilevare errori se si sbaglia. Se non imposti il ​​valore iniziale, in molte situazioni potrebbe non essere in grado di indovinare e potresti finire con errori di runtime inquietanti.

3 - Rispetta i comportamenti - JavaScript brilla meglio quando viene liberato il suo bambino funzionale interno. Nel mondo funzionale, esiste uno standard su come "piegare" o reduceun array. Quando pieghi o applichi un catamorfismo all'array, prendi i valori di quell'array per costruire un nuovo tipo. Devi comunicare il tipo risultante: dovresti farlo anche se il tipo finale è quello dei valori nell'array, in un altro array o in qualsiasi altro tipo.

Pensiamo in un altro modo. In JavaScript, le funzioni possono essere passate come dati, ecco come funzionano i callback, qual è il risultato del seguente codice?

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

Restituirà un numero? Un oggetto? Questo lo rende più chiaro

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

Maggiori informazioni sulle specifiche di programmazione funzionale qui: https://github.com/fantasyland/fantasy-land#foldable

Qualche altro sfondo

Il reducemetodo accetta due parametri,

Array.prototype.reduce( callback, initialItem )

La callbackfunzione accetta i seguenti parametri

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

Per la prima iterazione,

  • Se initialItemviene fornita, la reducefunzione passa initialItemcome accumulatore il primo elemento dell'array come itemInArray.

  • Se noninitialItem viene fornito, la funzione passa il primo elemento nell'array come il secondo elemento nell'array in quanto ciò può creare un comportamento confuso.reduceinitialItemitemInArray

Insegno e consiglio di impostare sempre il valore iniziale di ridurre.

Puoi consultare la documentazione su:

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

Spero che questo ti aiuti!


1
La ristrutturazione è proprio quello che volevo. Funziona come un fascino. Grazie!
AleksandrH,

24

Altri hanno risposto a questa domanda, ma ho pensato di lanciarmi in un altro approccio. Invece di andare direttamente all'asse di somma, puoi combinare una mappa (da ax a x) e ridurre (per aggiungere le x):

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

Certo, probabilmente sarà leggermente più lento, ma ho pensato che valesse la pena menzionarlo come opzione.


8

Per formalizzare ciò che è stato sottolineato, un riduttore è un catamorfismo che accetta due argomenti che possono essere dello stesso tipo per coincidenza e restituisce un tipo che corrisponde al primo argomento.

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

Ciò significa che il corpo del riduttore deve riguardare la conversione currentValuee il valore corrente del accumulatornel valore del nuovo accumulator.

Questo funziona in modo semplice, quando si aggiunge, perché l'accumulatore e i valori degli elementi sono entrambi dello stesso tipo (ma servono a scopi diversi).

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

Funziona solo perché sono tutti numeri.

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

Ora stiamo dando un valore iniziale all'aggregatore. Il valore iniziale dovrebbe essere il tipo che prevedi sia l'aggregatore (il tipo che prevedi venga visualizzato come valore finale), nella stragrande maggioranza dei casi. Anche se non sei obbligato a farlo (e non dovresti esserlo), è importante tenerlo a mente.

Una volta che lo sai, puoi scrivere riduzioni significative per altri problemi di relazione n: 1.

Rimozione di parole ripetute:

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

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

Fornendo un conteggio di tutte le parole trovate:

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

Riduzione di una matrice di array, a una singola matrice piatta:

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

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

Ogni volta che stai cercando di passare da una serie di cose a un singolo valore che non corrisponde a 1: 1, la riduzione è qualcosa che potresti prendere in considerazione.

In effetti, sia la mappa che il filtro possono essere implementati come riduzioni:

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

Spero che questo fornisca un ulteriore contesto per come usare reduce.

L'unica aggiunta a questa, che non ho ancora violato, è quando ci si aspetta che i tipi di input e output siano specificamente pensati per essere dinamici, perché gli elementi dell'array sono funzioni:

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 per spiegarlo chiaramente in termini di sistema di tipi. OTOH, penso aprendomi con l'idea che si tratti di un catamorfismo può essere scoraggiante per molti ...
Periata Breatta,

6

Per la prima iterazione 'a' sarà il primo oggetto nella matrice, quindi ax + bx restituirà 1 + 2 cioè 3. Ora nella successiva iterazione il 3 restituito è assegnato a a, quindi a è un numero ora n che chiama ax darà NaN.

La soluzione semplice consiste innanzitutto nel mappare i numeri nell'array e quindi ridurli come di seguito:

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

qui arr.map(a=>a.x)fornirà una serie di numeri [1,2,4] ora usando .reduce(function(a,b){return a+b})sarà semplice aggiungere questi numeri senza hassel

Un'altra semplice soluzione è solo quella di fornire una somma iniziale come zero assegnando 0 a 'a' come di seguito:

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

5

Ad ogni passaggio della riduzione, non stai restituendo un nuovo {x:???}oggetto. Quindi o devi fare:

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

o devi fare

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

3
Il primo esempio richiede un valore predefinito (come 0) altrimenti "a" non è definito nella prima iterazione.
Griffin,

2

Nel primo passaggio, funzionerà bene poiché il valore di asarà 1 e quello di bsarà 2 ma come 2 + 1 verrà restituito e nel passaggio successivo il valore di bsarà il valore restituito from step 1 i.e 3e quindi b.xnon sarà definito. .e non definito + anyNumber sarà NaN ed è per questo che stai ottenendo quel risultato.

Invece puoi provare questo dando il valore iniziale come zero ie

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


Aggiorna questo per mostrare come il tuo approccio è diverso dagli altri.
kwelch,

2

Se si dispone di un oggetto complesso con molti dati, come una matrice di oggetti, è possibile adottare un approccio graduale per risolverlo.

Ad esempio:

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

Innanzitutto, dovresti mappare il tuo array in un nuovo array di tuo interesse, potrebbe essere un nuovo array di valori in questo esempio.

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

Questa funzione di richiamata restituirà un nuovo array contenente solo valori dell'array originale e lo memorizzerà su valori const. Ora i tuoi valori const è un array come questo:

values = [10, 20];

E ora sei pronto per eseguire la riduzione:

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

Come puoi vedere, il metodo di riduzione esegue più volte la funzione di richiamata. Per ogni volta, prende il valore corrente dell'articolo nell'array e somma con l'accumulatore. Quindi per riassumere correttamente è necessario impostare il valore iniziale dell'accumulatore come secondo argomento del metodo di riduzione.

Ora hai la tua nuova const const con il valore di 30.


0

la funzione di riduzione scorre su una raccolta

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

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

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

durante ogni richiamata indice, il valore della variabile "accumulatore" viene passato in un "parametro" nella funzione di richiamata. Se non inizializziamo "accumulatore", il suo valore sarà indefinito. Chiamare undefined.x ti darebbe un errore.

Per risolvere questo, inizializzare "accumulatore" con valore 0 come mostrato nella risposta di Casey.

Per capire gli ingressi della funzione "Riduci", ti suggerisco di guardare il codice sorgente di questa funzione. La libreria Lodash ha una funzione di riduzione che funziona esattamente come la funzione di "riduzione" in ES6.

Ecco il link: ridurre il codice sorgente


0

per restituire una somma di tutti gli xoggetti di scena:

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

3
Fornire un contesto sul perché questa risposta sia migliore di altre accettate o votate può aiutare gli utenti a cercare la soluzione alla loro domanda.
Andrew,

0

È possibile utilizzare la mappa e ridurre le funzioni

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

-1

La funzione di riduzione dell'array accetta tre parametri, ovvero valore iniziale (valore predefinito 0), accumulatore e valore corrente. Per impostazione predefinita, il valore di initialValue sarà "0". che è preso dall'accumulatore

Vediamo questo nel codice.

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

Ora stesso esempio con Valore iniziale:

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

Lo stesso vale anche per gli array di oggetti (l'attuale domanda di StackOverflow):

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

UNA COSA DA TENERE MENTE è InitialValue di default è 0 e può essere dato qualsiasi cosa intendo {}, [] e numero


-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

non dovresti usare ax per accumulatore, invece puoi fare così `arr = [{x: 1}, {x: 2}, {x: 4}]

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.