Javascript reduce () en Object

182

Hay un buen método de matriz reduce()para obtener un valor de la matriz. Ejemplo:

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

¿Cuál es la mejor manera de lograr lo mismo con los objetos? Me gustaría hacer esto:

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

Sin embargo, Object no parece tener ningún reduce()método implementado.

Pavel S.
fuente
1
Estas usando Underscore.js?
Sethen 01 de
No ¿Underscore proporciona reducción para los objetos?
Pavel S.
No me acuerdo Sé que tiene un reducemétodo. Lo comprobaría allí. Sin embargo, la solución no parece tan difícil.
Sethen 01 de
1
@Sethen Maleno, @Pavel: sí _tiene una reducción para los objetos. No estoy seguro si funciona por accidente o si el soporte de objetos fue intencional, pero de hecho puede pasar un objeto como en el ejemplo de esta pregunta, y lo hará (conceptualmente) for..in, llamando a su función de iterador con los valores encontrados en cada tecla.
Roatin Marth

Respuestas:

55

Lo que realmente quieres en este caso son los Object.values. Aquí hay una implementación concisa de ES6 con eso en 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 simplemente:

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

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

console.log(total) // 6
Daniel Bayley
fuente
Es Array.prototype.values ​​() con el que se vinculó, editó ahora
Jonathan Wood el
281

Una opción sería la 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);

Con esto, querrá especificar un valor inicial o la primera ronda será 'a' + 2.

Si desea el resultado como un Objeto ( { value: ... }), deberá inicializar y devolver el objeto cada vez:

Object.keys(o).reduce(function (previous, key) {
    previous.value += o[key].value;
    return previous;
}, { value: 0 });
Jonathan Lonowski
fuente
15
Buena respuesta, pero es más legible usar Object.values ​​en lugar de Object.keys porque nos preocupan los valores aquí, no las claves. Debería ser así: Object.values ​​(o) .reduce ((total, current) => total + current.value, 0);
Mina Luke
3
Object.values ​​tiene un soporte de navegador mucho peor que Object.keys, pero eso podría no ser un problema si usa un polyfill o transpila con Babel
duhaime
exactamente, estaba usando esto en claves que se han recuperado de remolacha del modelo mongo, y pasé un objeto vacío como valor inicial al resultado de la reducción de claves, y tanto como estaba usando el @babel/plugin-proposal-object-rest-spreadcomplemento, extendí el valor del acumulador en Regreso de reducción y funciona a las mil maravillas, pero me pregunto si estoy haciendo algo mal, porque estaba pasando el objeto al valor inicial de reducción y su respuesta me demostró que hice lo correcto.
a_m_dev
55

Implementación de 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
fuente
3
Object.entries(o); // returns [['value',1],['value',2],['value',3]]
faboulaws
1
const [clave, valor] = par; ¡Nunca he visto esto!
Martin Meeser
9
@ martin-meeser: esto se llama desestructuración. Incluso podemos omitir esta línea cambiando function (total, pair)afunction (total, [key, value])
Jakub Zawiślak
Si bien entrieses una forma más limpia de hacerlo keys, es menos eficiente. hackernoon.com/…
robdonn
@faboulaws Object.entries (o); // devuelve [["a", {valor: 1}], ["b", {valor: 2}], ["c", {valor: 3}]]
Thomas
19

En primer lugar, no entiendes cuál es el valor anterior de reducir .

En su pseudocódigo que tiene return previous.value + current.value, por lo tanto, el previousvalor será un número en la próxima llamada, no un objeto.

En segundo lugar, reducees un método de matriz, no un objeto, y no puede confiar en el orden cuando está iterando las propiedades de un objeto (consulte: https://developer.mozilla.org/en-US/docs/ JavaScript / Referencia / Declaraciones / para ... en , esto también se aplica a Object.keys ); así que no estoy seguro de si reducetiene sentido aplicar sobre un objeto.

Sin embargo, si el pedido no es importante, puede tener:

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

O simplemente puede mapear el valor del objeto:

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

PS en ES6 con la sintaxis de la función de flecha gruesa (ya en Firefox Nightly), puede reducir un poco:

Object.keys(obj).map(key => obj[key].value).reduce((previous, current) => previous + current);
ZER0
fuente
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
fuente
2

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

Muestra de uso.

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

¡No, chicos! ;-)

usuario1247458
fuente
-1, ahora tiene una nueva propiedad enumerable en todos los objetos futuros: jsfiddle.net/ygonjooh
Johan
1
No modifique el prototipo base de esta manera. Esto puede generar muchos problemas para futuros desarrolladores que trabajen en la misma base de código
adam.k
Sí, es un "parche de mono". Esta solución fue escrita hace 6 años y ahora no es muy relevante, solo tenga en cuenta y, por ejemplo, será mejor usarla Object.entries()en 2021
user1247458
2

Puede usar una expresión generadora (admitida en todos los navegadores durante años y en Nodo) para obtener los pares clave-valor en una lista que puede reducir en:

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

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

Un objeto puede convertirse en una matriz con: Object.entries () , Object.keys () , Object.values ​​() , y luego reducirse como matriz. Pero también puede reducir un objeto sin crear la matriz intermedia.

He creado una pequeña biblioteca auxiliar odict para trabajar con objetos.

npm install --save odict

Tiene una reducefunción que funciona de manera muy similar a Array.prototype.reduce () :

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

También puede asignarlo a:

Object.reduce = reduce;

¡ya que este método es muy útil!

Entonces la respuesta a su pregunta sería:

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
fuente
1

Si puede usar una matriz, use una matriz, la longitud y el orden de una matriz valen la mitad.

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

/ * valor devuelto: (Número) 6 * /

Kennebec
fuente
1

Esto no es muy difícil de implementar usted mismo:

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 acción:

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

También puede agregarlo al prototipo de objeto:

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
fuente
0

Como todavía no se ha confirmado realmente en una respuesta, Underscore reducetambién funciona para esto.

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

Tenga en cuenta _.reduceque devolverá el único valor (objeto u otro) si el objeto de lista solo tiene un elemento, sin llamar a la función de iterador.

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

//returns {value: 1} instead of 1
Chris Dolphin
fuente
"Probar esta otra biblioteca" no es útil
Grunion Shaftoe
No es útil para ti
Chris Dolphin
0

Prueba esta función de flecha de línea

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

Prueba este. Se ordenarán los números de otras 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
fuente