Javascript réduire () sur l'objet


182

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.


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:


55

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

C'est Array.prototype.values ​​() que vous avez lié - édité maintenant
Jonathan Wood

281

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

15
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. Cela devrait ressembler à 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 les 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 fais quelque chose de mal, parce que je passais l'objet à la valeur initiale de réduire et votre réponse m'a prouvé que j'avais fait la bonne chose!
a_m_dev

55

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

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 - c'est ce qu'on 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 manière 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 ce qu'est 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, reduceest 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 ); donc je ne suis pas sûr de postulerreduce sur 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);

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}

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

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


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

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 * /


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

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

"Essayez cette autre bibliothèque" n'est pas utile
Grunion Shaftoe

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)

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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.