Reduce array of objects on key and sum value into

2019-04-08 18:04发布

问题:

I have the following object:

data = [
  { name: 'foo', type: 'fizz', val: 9 },
  { name: 'foo', type: 'buzz', val: 3 },
  { name: 'bar', type: 'fizz', val: 4 },
  { name: 'bar', type: 'buzz', val: 7 },
];

And used lodash map:

result = _.map(data, function item, idx){
  return {
    key: item[key],
    values: item.value,
  }
}

Which results in:

[
  { key: foo, val: 9 },
  { key: foo, val: 3 },
  { key: bar, val: 4 },
  { key: bar, val: 7 },
]

but now I'm trying to return:

[
  { key: 'foo', val: 12 },
  { key: 'bar', val: 11 },
]

I tried using reduce which seems to only output to a single object, which I could then convert back into an array, but I feel like there must be an elegant way to use lodash to go from my source data straight to my desired result without all of the intermediate steps.

I thought this was addressing my exact problem, but it seems like quite a bit of work just to have to convert the object into the desired array of objects outlined above.

Cheers.

回答1:

Interestingly not straight forward, because of wanting to accumulate the value by key, but then wanting the key as a value of the property key. So somewhat like an inverse map reduce:

var result = 
    _.chain(data)
        .reduce(function(memo, obj) {
            if(typeof memo[obj.name] === 'undefined') {
                memo[obj.name] = 0;
            } 
            memo[obj.name] += obj.val;
            return memo;
        }, {})
        .map(function (val, key) {
            return {key: key, val: val};
        })
        .value();

For the sake of brevity in es6:

_.chain(data)
    .reduce((memo, obj) => {
        memo[obj.name = obj.val] += obj.val;
        return memo; 
    }, {})
    .map((val, key) => ({key, val}))   
    .value();


回答2:

A twist to the accepted answer that uses groupBy instead of reduce to do the initial grouping:

var result = _.chain(data)
    .groupBy('name')
    .map((group, key) => ({ key, val : _.sumBy(group, 'val') }))
    .value();


回答3:

You can get all the unique names using map() and uniq(), and then map() each name to get their respective sums using sumBy().

var result = _(data)
  .map('name')
  .uniq()
  .map(key => ({ 
    key, 
    val: _(data).filter({ name: key }).sumBy('val')
  }))
  .value();

var data = [
  { name: 'foo', type: 'fizz', val: 9 },
  { name: 'foo', type: 'buzz', val: 3 },
  { name: 'bar', type: 'fizz', val: 4 },
  { name: 'bar', type: 'buzz', val: 7 }
];

var result = _(data)
  .map('name')
  .uniq()
  .map(key => ({ 
    key, 
    val: _(data).filter({ name: key }).sumBy('val')
  }))
  .value();

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>

Here is a non es6 version:

var result = _(data)
  .map('name')
  .uniq()
  .map(function(key) { 
    return {
      key: key, 
      val: _(data).filter({ name: key }).sumBy('val')
    };
  })
  .value();