Gleb Svechnikov
Gleb Svechnikov

Reputation: 383

How can I reduce array with non-unique elements by summing their second value with lodash/underscore?

How to get from this array

[
    {name: "a", weight: 1}, 
    {name: "b", weight: 3}, 
    {name: "a", weight: 5},
    {name: "b", weight: 7}
]

get this array(weight of duplicates summed)

[
    {name: "a", weight: 6}, 
    {name: "b", weight: 10}
]

Upvotes: 1

Views: 560

Answers (2)

mu is too short
mu is too short

Reputation: 434685

You don't need any fancy Underscore stuff for this, you can do it all with a single reduce call:

var result = a.reduce(function(m, e) {
    if(!m.by_name[e.name]) {
        m.by_name[e.name] = { name: e.name, weight: 0 };
        m.a.push(m.by_name[e.name]);
    }
    m.by_name[e.name].weight += e.weight;
    return m;
}, { a: [ ], by_name: { } });

Then your array will be in result.a.

Demo: http://jsfiddle.net/ambiguous/6UaXr/

If you must, you can use _.reduce instead of the native reduce.

There are a couple tricks here:

  1. The reduce memo caches the results by name in an object and in an array at the same time. Indexing by in m.by_name gives you quick lookups but you want an array as the final result so we build that along the way too.
  2. The by-name object and the array share the same references so incrementing through m.by_name[e.name] also updates the array.
  3. The by_name entries are created (and the references carefully shared) the first time we need them. We also initialize the cached weights with zero so that all the summarizing is done in a single m.by_name[e.name].weight += e.weight.

Upvotes: 1

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276306

Something like this should work using groupBy reduce and pluck:

var sum = function(a,b){ return a+b; };

_.chain([
    {name: "a", weight: 1}, 
    {name: "b", weight: 3}, 
    {name: "a", weight: 5},
    {name: "b", weight: 7}
]).groupBy('name') // we first group them by name
.map(function(v){  // them we map group to the name, and the sum of weights
      return {name:v[0].name,weight:_.pluck(v,"weight").reduce(sum)};
}).value(); // finally, we get the value

Note that we are reduceing using native JavaScript reduce, if you want Underscore's reduce you need to chain inside before the _.pluck, call .reduce and then call .value.

Upvotes: 1

Related Questions