How can I reduce array with non-unique elements by

2019-08-14 23:27发布

问题:

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

回答1:

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.



回答2:

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.