Javascript reduzir () no objeto

182

Existe um bom método Array reduce()para obter um valor a partir da matriz. Exemplo:

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

Qual é a melhor maneira de conseguir o mesmo com objetos? Eu gostaria de fazer isso:

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

No entanto, o objeto não parece ter nenhum reduce()método implementado.

Pavel S.
fonte
1
Você está usando Underscore.js?
Sethen
Não. O Underscore fornece redução para objetos?
Pavel S.
Não me lembro. Eu sei que tem um reducemétodo. Eu verificaria lá. Porém, a solução não parece tão difícil.
Sethen
1
@ Sethen Maleno, @ Pavel: sim _, tem uma redução para objetos. Não tenho certeza se funciona acidentalmente ou se o suporte a objetos foi intencional, mas, de fato, você pode passar um objeto como no exemplo desta pergunta, e será (conceitualmente) for..in, chamando sua função de iterador com os valores encontrados em cada chave.
Roatin Marth

Respostas:

55

O que você realmente deseja neste caso é o Object.values. Aqui está uma implementação concisa do ES6 com isso em 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

ou simplesmente:

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

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

console.log(total) // 6
Daniel Bayley
fonte
Isso é Array.prototype.values (), que é ligada ao - editado agora
Jonathan Madeira
281

Uma opção seria reducea 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);

Com isso, você desejará especificar um valor inicial ou a 1ª rodada será 'a' + 2.

Se você deseja o resultado como um Object ( { value: ... }), precisará inicializar e retornar o objeto sempre:

Object.keys(o).reduce(function (previous, key) {
    previous.value += o[key].value;
    return previous;
}, { value: 0 });
Jonathan Lonowski
fonte
15
Boa resposta, mas é mais legível usar Object.values ​​em vez de Object.keys porque estamos preocupados com os valores aqui, não com as chaves. Deve ser assim: Object.values ​​(o) .reduce ((total, current) => total + current.value, 0);
Mina Luke
3
Object.values tem suporte ao navegador muito pior do que Object.keys, mas que pode não ser um problema se você usar um polyfill ou você transpile com Babel
duhaime
exatamente, eu estava usando isso em chaves recuperadas de beterraba do modelo mongo, e passei um objeto vazio como um valor inicial para o resultado da redução de chaves e, tanto quanto eu estava usando o @babel/plugin-proposal-object-rest-spreadplug-in, espalhei o valor do acumulador em retorno de reduzir e funciona como um encanto, mas eu estava pensando se estou fazendo algo errado, porque eu estava passando o objeto para o valor inicial de reduzir e sua resposta me provou que eu fiz a coisa certa!
a_m_dev 26/03
55

Implementação do 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
fonte
3
Object.entries(o); // returns [['value',1],['value',2],['value',3]]
Faboulaws
1
const [chave, valor] = par; Eu nunca vi isso!
Martin Meeser
9
@ martin-meeser - isso é chamado de desestruturação. Podemos até omitir esta linha mudando function (total, pair)parafunction (total, [key, value])
Jakub Zawiślak
Embora entriesseja uma maneira mais limpa de fazer isso keys, é menos eficiente. hackernoon.com/...
robdonn
@faboulaws Object.entries (o); // retorna [["a", {valor: 1}], ["b", {valor: 2}], ["c", {valor: 3}]]
Thomas
19

Primeiro de tudo, você não entende exatamente o que é o valor anterior da redução .

No seu pseudo-código, você tem return previous.value + current.value, portanto, o previousvalor será um número na próxima chamada, não um objeto.

Segundo, reduceé um método Array, não o de um Objeto, e você não pode confiar na ordem ao iterar as propriedades de um objeto (consulte: https://developer.mozilla.org/en-US/docs/ JavaScript / Reference / Statements / for ... in , isso também é aplicado a Object.keys ); portanto, não tenho certeza se a aplicação reducesobre um objeto faz sentido.

No entanto, se o pedido não for importante, você pode ter:

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

Ou você pode apenas mapear o valor do objeto:

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

PS no ES6 com a sintaxe da função de seta gorda (já no Firefox Nightly), você pode diminuir um pouco:

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

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

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

Não, rapazes !! ;-)

user1247458
fonte
-1, agora você tem uma nova propriedade enumeráveis em todos os objetos futuros: jsfiddle.net/ygonjooh
Johan
1
Por favor, não modifique o protótipo básico como este. Isso pode levar a muitos problemas para futuros desenvolvedores que trabalham na mesma base de código
adam.k
Sim, é um "patch de macaco" Esta solução foi escrita há 6 anos e não é muito relevante agora, lembre-se: por exemplo, será melhor usá-lo Object.entries()em 2021
user1247458
2

Você pode usar uma expressão de gerador (suportada em todos os navegadores há anos e no Nó) para obter os pares de valores-chave em uma lista que você pode reduzir:

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

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

Um objeto pode ser transformado em uma matriz com: Object.entries () , Object.keys () , Object.values ​​() e, em seguida, ser reduzido como matriz. Mas você também pode reduzir um objeto sem criar a matriz intermediária.

Eu criei uma pequena biblioteca auxiliar odict para trabalhar com objetos.

npm install --save odict

Tem uma reducefunção que funciona muito como Array.prototype.reduce () :

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

Você também pode atribuí-lo a:

Object.reduce = reduce;

como este método é muito útil!

Portanto, a resposta para sua pergunta seria:

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

Se você pode usar uma matriz, use uma matriz, o comprimento e a ordem de uma matriz são metade do seu valor.

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 retornado: (Número) 6 * /

Kennebec
fonte
1

Isso não é muito difícil de se implementar:

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

Em ação:

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

Você também pode adicioná-lo ao protótipo 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
fonte
0

Como ainda não foi realmente confirmado em uma resposta, o Underscore reducetambém funciona para isso.

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

Observe _.reduceque retornará o único valor (objeto ou não) se o objeto da lista tiver apenas um item, sem chamar a função de iterador.

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

//returns {value: 1} instead of 1
Chris Dolphin
fonte
"Experimente esta outra biblioteca" não é útil
Grunion Shaftoe 13/12/19
Não é útil para você
Chris Dolphin
0

Experimente esta função de seta única

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

Tente este. Ele classificará números de outras variáveis.

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
fonte