Javascript redu () für Objekt


179

Es gibt eine nette Array-Methode reduce(), um einen Wert aus dem Array zu erhalten. Beispiel:

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

Was ist der beste Weg, um dasselbe mit Objekten zu erreichen? Ich würde das gerne machen:

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

In Object scheint jedoch keine reduce()Methode implementiert zu sein.


1
Benutzt du Underscore.js?
Sethen

Nee. Bietet der Unterstrich eine Reduzierung für Objekte?
Pavel S.

Ich kann mich nicht erinnern. Ich weiß, dass es eine reduceMethode gibt. Ich würde dort nachsehen. Die Lösung scheint jedoch nicht so schwierig zu sein.
Sethen

1
@Sethen Maleno, @Pavel: Ja, _es gibt eine Reduzierung für Objekte. Sie sind sich nicht sicher, ob es versehentlich funktioniert oder ob die Objektunterstützung beabsichtigt war, aber Sie können tatsächlich ein Objekt wie im Beispiel dieser Frage übergeben, und es wird (konzeptionell) for..inIhre Iteratorfunktion mit den Werten aufrufen, die bei jedem Schlüssel gefunden werden.
Roatin Marth

Antworten:


53

Was Sie in diesem Fall tatsächlich wollen, sind die Object.values. Hier ist eine kurze ES6- Implementierung in diesem Sinne:

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

oder einfach:

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

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

console.log(total) // 6

Das ist Array.prototype.values ​​(), mit dem Sie verlinkt haben - jetzt bearbeitet
Jonathan Wood

281

Eine Option wäre reducedie 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);

Hiermit möchten Sie einen Anfangswert angeben, oder die erste Runde wird 'a' + 2.

Wenn Sie das Ergebnis als Object ( { value: ... }) möchten , müssen Sie das Objekt jedes Mal initialisieren und zurückgeben:

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

14
Gute Antwort, aber es ist besser lesbar, Object.values ​​anstelle von Object.keys zu verwenden, da wir uns um die Werte hier kümmern, nicht um die Schlüssel. Es sollte so aussehen: Object.values ​​(o) .reduce ((total, current) => total + current.value, 0);
Mina Luke

3
Object.values ​​hat eine viel schlechtere Browserunterstützung als Object.keys, aber das ist möglicherweise kein Problem, wenn Sie eine Polyfüllung verwenden oder mit Babel
duhaime

Genau, ich habe dies in Schlüsseln verwendet, die aus dem Mongo-Modell abgerufen wurden, und ich habe ein leeres Objekt als Anfangswert an das Ergebnis der Schlüsselreduzierung übergeben, und so oft ich das @babel/plugin-proposal-object-rest-spreadPlugin verwendet habe, habe ich den Akkumulatorwert in verteilt Rückkehr von Reduzieren und es funktioniert wie ein Zauber, aber ich war wondere, wenn ich etwas falsch mache, weil ich das Objekt auf den Anfangswert von Reduzieren übergab und Ihre Antwort mir bewies, dass ich das Richtige getan habe!
a_m_dev

55

ES6-Implementierung: 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 [Schlüssel, Wert] = Paar; Ich habe das noch nie gesehen!
Martin Meeser

9
@ martin-meeser - das nennt man destrukturierung. Wir können diese Zeile sogar weglassen, indem wir function (total, pair)zufunction (total, [key, value])
Jakub Zawiślak

Während entriesein sauberer Weg , dies zu tun vorbei ist keys, ist es weniger performant. hackernoon.com/…
robdonn

@faboulaws Object.entries (o); // gibt [["a", {Wert: 1}], ["b", {Wert: 2}], ["c", {Wert: 3}] zurück
Thomas

19

Erstens bekommt man nicht ganz, was reduziert wird ist ‚s vorheriger Wert.

In deinem Pseudocode hast du return previous.value + current.valuealso denprevious Wert beim nächsten Aufruf eine Nummer, kein Objekt.

Zweitens reducehandelt es sich um eine Array-Methode, nicht um eine Objektmethode, und Sie können sich nicht auf die Reihenfolge verlassen, wenn Sie die Eigenschaften eines Objekts iterieren (siehe: https://developer.mozilla.org/en-US/docs/). JavaScript / Reference / Statements / for ... in , dies gilt auch für Object.keys ); Ich bin mir also nicht sicher, ob ich mich bewerben sollreduce auf ein Objekt sinnvoll ist.

Wenn die Reihenfolge jedoch nicht wichtig ist, können Sie Folgendes haben:

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

Oder Sie können einfach Karte den Wert des Objekts:

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

PS in ES6 mit der Syntax der Fat Arrow-Funktion (bereits in Firefox Nightly) könnten Sie etwas schrumpfen:

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

Erweitern Sie 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 );
};

Verwendungsbeispiel.

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; '.

n'Joy it, Leute !! ;-);


-1, jetzt haben Sie eine neue aufzählbare Eigenschaft für alle zukünftigen Objekte: jsfiddle.net/ygonjooh
Johan


1
Bitte ändern Sie den Basisprototyp nicht so. Dies kann zu vielen Problemen für zukünftige Entwickler führen, die in derselben Codebasis arbeiten
adam.k

Ja, es ist ein "Affen-Patching". Diese Lösung wurde vor 6 Jahren geschrieben und ist jetzt nicht allzu relevant. Denken Sie daran. Zum Beispiel ist es besser, sie Object.entries()2021 zu verwenden
user1247458,

2

Sie können einen Generatorausdruck verwenden (der seit Jahren in allen Browsern und in Node unterstützt wird), um die Schlüssel-Wert-Paare in einer Liste abzurufen, die Sie reduzieren können:

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

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

2

Ein Objekt kann in ein Array umgewandelt werden mit: Object.entries () , Object.keys () , Object.values ​​() und dann als Array reduziert werden. Sie können ein Objekt aber auch reduzieren, ohne das Zwischenarray zu erstellen.

Ich habe ein kleines Hilfsbibliothek-Odict für die Arbeit mit Objekten erstellt.

npm install --save odict

Es hat eine reduceFunktion, die sehr ähnlich wie Array.prototype.reduce () funktioniert :

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

Sie können es auch zuweisen:

Object.reduce = reduce;

da diese Methode sehr nützlich ist!

Die Antwort auf Ihre Frage wäre also:

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

Wenn Sie ein Array verwenden können, verwenden Sie ein Array. Die Länge und Reihenfolge eines Arrays sind halb so hoch.

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

/ * zurückgegebener Wert: (Nummer) 6 * /


1

Dies ist nicht sehr schwer selbst umzusetzen:

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 Aktion:

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

Sie können es auch dem Objektprototyp hinzufügen:

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

Da dies in einer Antwort noch nicht wirklich bestätigt wurde, reducefunktioniert Underscore auch dafür.

_.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
})

Hinweis: _.reduceGibt den einzigen Wert (Objekt oder anderweitig) zurück, wenn das Listenobjekt nur ein Element enthält, ohne die Iteratorfunktion aufzurufen.

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

//returns {value: 1} instead of 1

"Versuchen Sie diese andere Bibliothek" ist nicht nützlich
Grunion Shaftoe

Nicht nützlich für Sie
Chris Dolphin

0

Probieren Sie diese Einzeiler-Pfeilfunktion aus

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

0

Probier diese. Es werden Zahlen von anderen Variablen sortiert.

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.