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.


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

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

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

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

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

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


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

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

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


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

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

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

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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.