Javascript réduire () sur l'objet

191

Il existe une bonne méthode Array reduce()pour obtenir une valeur du Array. Exemple:

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

Quelle est la meilleure façon d'obtenir la même chose avec des objets? J'aimerais faire ceci:

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

Cependant, Object ne semble pas avoir de reduce()méthode implémentée.

Pavel S.
la source
1
Utilisez-vous Underscore.js?
Sethen
Nan. Underscore fournit-il une réduction pour les objets?
Pavel S.
Je ne m'en souviens plus. Je sais qu'il a une reduceméthode. Je vérifierais là-bas. Cependant, la solution ne semble pas si difficile.
Sethen
1
@Sethen Maleno, @Pavel: oui _a une réduction pour les objets. Je ne sais pas si cela fonctionne par accident ou si le support d'objet était intentionnel, mais en effet, vous pouvez passer un objet comme dans l'exemple de cette question, et il le fera (conceptuellement) for..in, en appelant votre fonction d'itérateur avec les valeurs trouvées à chaque clé.
Roatin Marth

Réponses:

62

Ce que vous voulez réellement dans ce cas, ce sont les fichiers Object.values. Voici une implémentation ES6 concise dans cet esprit:

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

ou simplement:

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

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

console.log(total) // 6
Daniel Bayley
la source
C'est Array.prototype.values ​​() que vous avez lié - édité maintenant
Jonathan Wood
288

Une option serait de reducela 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);

Avec cela, vous voudrez spécifier une valeur initiale ou le 1er tour sera 'a' + 2.

Si vous voulez que le résultat soit un Object ( { value: ... }), vous devrez initialiser et renvoyer l'objet à chaque fois:

Object.keys(o).reduce(function (previous, key) {
    previous.value += o[key].value;
    return previous;
}, { value: 0 });
Jonathan Lonowski
la source
16
Bonne réponse mais il est plus lisible d'utiliser Object.values ​​au lieu d'Object.keys car nous sommes préoccupés par les valeurs ici et non par les clés. Ce devrait être comme ceci: Object.values ​​(o) .reduce ((total, current) => total + current.value, 0);
Mina Luke
3
Object.values ​​a un support de navigateur bien pire que Object.keys, mais cela pourrait ne pas être un problème si vous utilisez un polyfill ou que vous transpilez avec Babel
duhaime
exactement, j'utilisais ceci dans des clés qui ont été récupérées à partir du modèle mongo, et j'ai passé un objet vide comme valeur initiale au résultat de la réduction des clés, et autant que j'utilisais le @babel/plugin-proposal-object-rest-spreadplugin, j'ai réparti la valeur de l'accumulateur dans retour de réduire et cela fonctionne comme un charme, mais je me demandais si je faisais quelque chose de mal, parce que je passais l'obejct à la valeur initiale de réduire et votre réponse m'a prouvé que j'avais fait la bonne chose!
a_m_dev
58

Implémentation 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);
faboulaws
la source
3
Object.entries(o); // returns [['value',1],['value',2],['value',3]]
faboulaws
1
const [clé, valeur] = paire; Je n'ai jamais vu ça!
Martin Meeser
9
@ martin-meeser - cela s'appelle la déstructuration. Nous pouvons même omettre cette ligne en changeant function (total, pair)pourfunction (total, [key, value])
Jakub Zawiślak
Bien que ce entriessoit une façon plus propre de faire cela keys, il est moins performant. hackernoon.com
robdonn
@faboulaws Object.entries (o); // renvoie [["a", {value: 1}], ["b", {value: 2}], ["c", {value: 3}]]
Thomas
19

Tout d'abord, vous ne comprenez pas tout à fait la valeur précédente de la réduction .

Dans votre pseudo-code que vous avez return previous.value + current.value, la previousvaleur sera donc un nombre lors du prochain appel, pas un objet.

Deuxièmement, reducec'est une méthode Array, pas celle d'un objet, et vous ne pouvez pas vous fier à l'ordre lorsque vous itérez les propriétés d'un objet (voir: https://developer.mozilla.org/en-US/docs/ JavaScript / Reference / Statements / for ... in , cela s'applique également à Object.keys ); je ne suis donc pas sûr que l'application reducesur un objet ait du sens.

Cependant, si l'ordre n'est pas important, vous pouvez avoir:

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

Ou vous pouvez simplement mapper la valeur de l'objet:

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

PS dans ES6 avec la syntaxe de la fonction grosse flèche (déjà dans Firefox Nightly), vous pourriez réduire un peu:

Object.keys(obj).map(key => obj[key].value).reduce((previous, current) => previous + current);
ZER0
la source
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}
Alair Tavares Jr
la source
3

Un objet peut être transformé en tableau avec: Object.entries () , Object.keys () , Object.values ​​() , puis être réduit en tableau. Mais vous pouvez également réduire un objet sans créer le tableau intermédiaire.

J'ai créé une petite bibliothèque d' aide pour travailler avec des objets.

npm install --save odict

Il a une reducefonction qui fonctionne très bien comme Array.prototype.reduce () :

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

Vous pouvez également l'attribuer à:

Object.reduce = reduce;

car cette méthode est très utile!

La réponse à votre question serait donc:

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
);
Igor Sukharev
la source
2

Étendez 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 );
};

Exemple d'utilisation.

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, les gars !! ;-)

user1247458
la source
-1, maintenant vous avez une nouvelle propriété énumérable sur tous les objets futurs: jsfiddle.net/ygonjooh
Johan
1
Veuillez ne pas modifier le prototype de base de cette manière. Cela peut entraîner de nombreux problèmes pour les futurs développeurs travaillant dans la même base de code
adam.k
Oui, c'est un "monkey patching" Cette solution a été écrite il y a 6 ans et pas trop pertinente maintenant, gardez juste à l'esprit Et par exemple, il vaudra mieux l'utiliser Object.entries()en 2021
user1247458
2

Vous pouvez utiliser une expression de générateur (prise en charge dans tous les navigateurs depuis des années maintenant et dans Node) pour obtenir les paires clé-valeur dans une liste sur laquelle vous pouvez réduire:

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

>>> [[i, a[i]] for (i in a) if (a.hasOwnProperty(i))]
[["b", 3]]
Janus Troelsen
la source
1

Si vous pouvez utiliser un tableau, utilisez un tableau, la longueur et l'ordre d'un tableau valent la moitié de sa valeur.

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

/ * valeur renvoyée: (Number) 6 * /

Kennebec
la source
1

Ce n'est pas très difficile à mettre en œuvre vous-même:

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

En action:

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

Vous pouvez également l'ajouter au prototype 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);
    }
}
Francis Avila
la source
0

Comme cela n'a pas encore été confirmé dans une réponse, Underscore reducefonctionne également pour cela.

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

Remarque, _.reduceretournera la seule valeur (objet ou autre) si l'objet liste n'a qu'un seul élément, sans appeler la fonction d'itérateur.

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

//returns {value: 1} instead of 1
Chris Dolphin
la source
"Essayez cette autre bibliothèque" n'est pas utile
Sean Munson
Pas utile pour vous
Chris Dolphin
0

Essayez cette fonction de flèche à une ligne

Object.values(o).map(a => a.value, o).reduce((ac, key, index, arr) => ac+=key)
M. Thomas
la source
0

Essaye celui-là. Il triera les nombres d'autres variables.

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)
prgv
la source