Javascript ridurre () su oggetto


182

Esiste un metodo Array reduce()per ottenere un valore dall'Array. Esempio:

[0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){
  return previousValue + currentValue;
});

Qual è il modo migliore per ottenere lo stesso con gli oggetti? Mi piacerebbe fare questo:

{ 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
}.reduce(function(previous, current, index, array){
  return previous.value + current.value;
});

Tuttavia, Object non sembra avere alcun reduce()metodo implementato.


1
Stai usando Underscore.js?
Sethen,

No. Underscore offre una riduzione per gli oggetti?
Pavel S.

Non ricordo. So che ha un reducemetodo. Vorrei controllare lì. Tuttavia, la soluzione non sembra così difficile.
Sethen,

1
@Sethen Maleno, @Pavel: sì _, ha una riduzione per gli oggetti. Non sei sicuro che funzioni per caso o che il supporto agli oggetti fosse intenzionale, ma in effetti puoi passare un oggetto come nell'esempio di questa domanda, e lo farà (concettualmente) for..in, chiamando la tua funzione iteratore con i valori trovati in ogni chiave.
Roatin Marth

Risposte:


55

Quello che veramente vuoi in questo caso sono i Object.values. Ecco una concisa implementazione ES6 con questo in mente:

const add = {
  a: {value:1},
  b: {value:2},
  c: {value:3}
}

const total = Object.values(add).reduce((t, {value}) => t + value, 0)

console.log(total) // 6

o semplicemente:

const add = {
  a: 1,
  b: 2,
  c: 3
}

const total = Object.values(add).reduce((t, n) => t + n)

console.log(total) // 6

Quello è Array.prototype.values ​​() a cui ti sei collegato - modificato ora
Jonathan Wood

281

Una possibilità potrebbe essere quella reducedel keys():

var o = { 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
};

Object.keys(o).reduce(function (previous, key) {
    return previous + o[key].value;
}, 0);

Con questo, ti consigliamo di specificare un valore iniziale o sarà il 1 ° round 'a' + 2.

Se vuoi il risultato come un oggetto ( { value: ... }), dovrai inizializzare e restituire l'oggetto ogni volta:

Object.keys(o).reduce(function (previous, key) {
    previous.value += o[key].value;
    return previous;
}, { value: 0 });

15
Bella risposta ma è più leggibile usare Object.values ​​invece di Object.keys perché siamo preoccupati per i valori qui non per le chiavi. Dovrebbe essere così: Object.values ​​(o) .reduce ((total, current) => total + current.value, 0);
Mina Luke,

3
Object.values ​​ha un supporto per browser molto peggio di Object.keys, ma potrebbe non essere un problema se usi un polyfill o traspali con Babel
duhaime,

esattamente, lo stavo usando nelle chiavi che la barbabietola ha recuperato dal modello mongo e ho passato un oggetto vuoto come valore iniziale al risultato della riduzione delle chiavi, e per quanto stavo usando il @babel/plugin-proposal-object-rest-spreadplugin, mi è stato diffuso il valore dell'accumulatore in ritorno di ridurre e funziona come un incantesimo, ma mi chiedevo se sto facendo qualcosa di sbagliato, perché stavo passando l'obbiettivo al valore iniziale di ridurre e la tua risposta mi ha dimostrato che ho fatto la cosa giusta!
a_m_dev il

55

Implementazione ES6: Object.entries ()

const o = {
  a: {value: 1},
  b: {value: 2},
  c: {value: 3}
};

const total = Object.entries(o).reduce(function (total, pair) {
  const [key, value] = pair;
  return total + value;
}, 0);

3
Object.entries(o); // returns [['value',1],['value',2],['value',3]]
faboulaws,

1
const [chiave, valore] = coppia; Non l'ho mai visto!
Martin Meeser,

9
@ martin-meeser - questo si chiama destrutturazione. Possiamo anche omettere questa linea cambiando function (total, pair)infunction (total, [key, value])
Jakub Zawiślak

Mentre entriesè un modo più pulito di farlo keys, è meno performante. hackernoon.com/…
robdonn,

@faboulaws Object.entries (o); // restituisce [["a", {valore: 1}], ["b", {valore: 2}], ["c", {valore: 3}]]
Thomas

19

Prima di tutto, non si ottiene del tutto il valore precedente di riduzione .

Nel tuo pseudo codice che hai return previous.value + current.value, quindi il previousvalore sarà un numero alla prossima chiamata, non un oggetto.

Secondo, reduceè un metodo Array, non un Oggetto, e non puoi fare affidamento sull'ordine quando stai iterando le proprietà di un oggetto (vedi: https://developer.mozilla.org/en-US/docs/ JavaScript / Riferimenti / Dichiarazioni / per ... in , questo vale anche per Object.keys ); quindi non sono sicuro che l'applicazione reducesu un oggetto abbia senso.

Tuttavia, se l'ordine non è importante, puoi avere:

Object.keys(obj).reduce(function(sum, key) {
    return sum + obj[key].value;
}, 0);

Oppure puoi semplicemente mappare il valore dell'oggetto:

Object.keys(obj).map(function(key) { return this[key].value }, obj).reduce(function (previous, current) {
    return previous + current;
});

PS in ES6 con la sintassi della funzione fat fat arrow (già in Firefox Nightly), potresti restringere un po ':

Object.keys(obj).map(key => obj[key].value).reduce((previous, current) => previous + current);

3

1:

[{value:5}, {value:10}].reduce((previousValue, currentValue) => { return {value: previousValue.value + currentValue.value}})

>> Object {value: 15}

2:

[{value:5}, {value:10}].map(item => item.value).reduce((previousValue, currentValue) => {return previousValue + currentValue })

>> 15

3:

[{value:5}, {value:10}].reduce(function (previousValue, currentValue) {
      return {value: previousValue.value + currentValue.value};
})

>> Object {value: 15}

2

Estendi Object.prototype.

Object.prototype.reduce = function( reduceCallback, initialValue ) {
    var obj = this, keys = Object.keys( obj );

    return keys.reduce( function( prevVal, item, idx, arr ) {
        return reduceCallback( prevVal, item, obj[item], obj );
    }, initialValue );
};

Esempio di utilizzo.

var dataset = {
    key1 : 'value1',
    key2 : 'value2',
    key3 : 'value3'
};

function reduceFn( prevVal, key, val, obj ) {
    return prevVal + key + ' : ' + val + '; ';
}

console.log( dataset.reduce( reduceFn, 'initialValue' ) );
'Output' == 'initialValue; key1 : value1; key2 : value2; key3 : value3; '.

ragazzi, ragazzi !! ;-)


-1, ora hai una nuova proprietà enumerabile su tutti gli oggetti futuri: jsfiddle.net/ygonjooh
Johan


1
Si prega di non modificare il prototipo di base in questo modo. Ciò può comportare molti problemi per i futuri sviluppatori che lavorano nella stessa base di codice
adam.k,

Sì, si tratta di un "patching di scimmie" Questa soluzione è stata scritta 6 anni fa e non è troppo rilevante ora, tieni solo a mente E ad esempio, sarà meglio usare Object.entries()nel 2021
user1247458

2

È possibile utilizzare un'espressione del generatore (supportata da anni in tutti i browser e in Nodo) per ottenere le coppie chiave-valore in un elenco su cui è possibile ridurre:

>>> a = {"b": 3}
Object { b=3}

>>> [[i, a[i]] for (i in a) if (a.hasOwnProperty(i))]
[["b", 3]]

2

Un oggetto può essere trasformato in un array con: Object.entries () , Object.keys () , Object.values ​​() e quindi essere ridotto come array. Ma puoi anche ridurre un oggetto senza creare l'array intermedio.

Ho creato una piccola libreria helper per lavorare con gli oggetti.

npm install --save odict

Ha una reducefunzione che funziona in modo molto simile a Array.prototype.reduce () :

export const reduce = (dict, reducer, accumulator) => {
  for (const key in dict)
    accumulator = reducer(accumulator, dict[key], key, dict);
  return accumulator;
};

Puoi anche assegnarlo a:

Object.reduce = reduce;

poiché questo metodo è molto utile!

Quindi la risposta alla tua domanda sarebbe:

const result = Object.reduce(
  {
    a: {value:1},
    b: {value:2},
    c: {value:3},
  },
  (accumulator, current) => (accumulator.value += current.value, accumulator), // reducer function must return accumulator
  {value: 0} // initial accumulator value
);

1

Se puoi usare un array, usa un array, la lunghezza e l'ordine di un array valgono la metà.

function reducer(obj, fun, temp){
    if(typeof fun=== 'function'){
        if(temp== undefined) temp= '';
        for(var p in obj){
            if(obj.hasOwnProperty(p)){
                temp= fun(obj[p], temp, p, obj);
            }
        }
    }
    return temp;
}
var O={a:{value:1},b:{value:2},c:{value:3}}

reducer(O, function(a, b){return a.value+b;},0);

/ * valore restituito: (Numero) 6 * /


1

Non è molto difficile implementare te stesso:

function reduceObj(obj, callback, initial) {
    "use strict";
    var key, lastvalue, firstIteration = true;
    if (typeof callback !== 'function') {
        throw new TypeError(callback + 'is not a function');
    }   
    if (arguments.length > 2) {
        // initial value set
        firstIteration = false;
        lastvalue = initial;
    }
    for (key in obj) {
        if (!obj.hasOwnProperty(key)) continue;
        if (firstIteration)
            firstIteration = false;
            lastvalue = obj[key];
            continue;
        }
        lastvalue = callback(lastvalue, obj[key], key, obj);
    }
    if (firstIteration) {
        throw new TypeError('Reduce of empty object with no initial value');
    }
    return lastvalue;
}

In azione:

var o = {a: {value:1}, b: {value:2}, c: {value:3}};
reduceObj(o, function(prev, curr) { prev.value += cur.value; return prev;}, {value:0});
reduceObj(o, function(prev, curr) { return {value: prev.value + curr.value};});
// both == { value: 6 };

reduceObj(o, function(prev, curr) { return prev + curr.value; }, 0);
// == 6

Puoi anche aggiungerlo al prototipo Object:

if (typeof Object.prototype.reduce !== 'function') {
    Object.prototype.reduce = function(callback, initial) {
        "use strict";
        var args = Array.prototype.slice(arguments);
        args.unshift(this);
        return reduceObj.apply(null, args);
    }
}

0

Dal momento che non è stato ancora confermato in una risposta, Underscore reducefunziona anche per questo.

_.reduce({ 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
}, function(prev, current){
    //prev is either first object or total value
    var total = prev.value || prev

    return total + current.value
})

Nota, _.reducerestituirà l'unico valore (oggetto o altro) se l'oggetto elenco ha solo un elemento, senza chiamare la funzione iteratore.

_.reduce({ 
    a: {value:1} 
}, function(prev, current){
    //not called
})

//returns {value: 1} instead of 1

"Prova quest'altra libreria" non è utile
Grunion Shaftoe,

Non ti è utile
Chris Dolphin il

0

Prova questa funzione con una freccia di rivestimento

Object.values(o).map(a => a.value, o).reduce((ac, key, index, arr) => ac+=key)

0

Prova questo. Ordinerà i numeri da altre variabili.

const obj = {
   a: 1,
   b: 2,
   c: 3
};
const result = Object.keys(obj)
.reduce((acc, rec) => typeof obj[rec] === "number" ? acc.concat([obj[rec]]) : acc, [])
.reduce((acc, rec) => acc + rec)
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.