Reduce array by its items key in ES6

I've got array of fruits:

const fruits = [
  { name: 'apple', count: 2 },
  { name: 'apple', count: 4 },
  { name: 'banana', count: 5 },
  { name: 'apple', count: 3 },
  { name: 'apple', count: 4 },
  { name: 'apple', count: 1 },
  { name: 'banana', count: 3 },
  { name: 'apple', count: 1 },
  { name: 'orange', count: 2 }
];

What I try to achieve is to reduce this array so it look like this:

let fruitsReduces = [
  { name: 'apple', count: 15 },
  { name: 'banana', count: 8 },
  { name: 'orange', count: 2 }
];

I've done this using very ugly for loop:

for (let i = 0; i < fruits.length; i++) {
  fruitItem = {};
  fruitItem.name = fruits[i].name;
  fruitItem.count = fruits[i].count;

  const fruitAlreadyInTheArray = fruitsReduced.find(fruit => fruit.name === fruitItem.name);
  if (!fruitAlreadyInTheArray) {
    fruitsReduced.push(fruitItem);
  } else {
    fruitAlreadyInTheArray.count += fruitItem.count;
  }
}

But I'm sure that same thing can be done somehow by using ES6 array.reduce. Can you help me? I looked at JS (ES6): Reduce array based on object attribute but I can't figure it out.

javascriptarraysobjectecmascript-6reduce

Answers

answered 3 months ago Nina Scholz #1

You could use an array as accumulator and seach for an inserted object with a wanted name for adding the actual count. If not found add a new object.

const
    fruits = [{ name: 'apple', count: 2 }, { name: 'apple', count: 4 }, { name: 'banana', count: 5 }, { name: 'apple', count: 3 }, { name: 'apple', count: 4 }, { name: 'apple', count: 1 }, { name: 'banana', count: 3 }, { name: 'apple', count: 1 }, { name: 'orange', count: 2 }],
    result = fruits.reduce((r, { name, count }) => {
        var item = r.find(o => o.name === name);
        if (!item) {
            item = { name, count: 0 };
            r.push(item);
        }
        item.count += count;
        return r;
    }, []);
    
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

A different approach by collecting all counts first with a Map and render the wanted array with Array.from and a mapping function which builds new objects.

const
    fruits = [{ name: 'apple', count: 2 }, { name: 'apple', count: 4 }, { name: 'banana', count: 5 }, { name: 'apple', count: 3 }, { name: 'apple', count: 4 }, { name: 'apple', count: 1 }, { name: 'banana', count: 3 }, { name: 'apple', count: 1 }, { name: 'orange', count: 2 }],
    result = Array.from(
        fruits.reduce(
            (m, { name, count }) => m.set(name, (m.get(name) || 0) + count),
            new Map
        ),
        ([name, count]) => ({ name, count })
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

answered 3 months ago Eddie #2

You can us reduce() the array into an object. Use Object.values() to convert the object into an array.

const fruits = [{"name":"apple","count":2},{"name":"apple","count":4},{"name":"banana","count":5},{"name":"apple","count":3},{"name":"apple","count":4},{"name":"apple","count":1},{"name":"banana","count":3},{"name":"apple","count":1},{"name":"orange","count":2}];

var result = Object.values(fruits.reduce((c, {name,count}) => {
  c[name] = c[name] || {name,count: 0};
  c[name].count += count;
  return c;
}, {}));

console.log(result);

answered 3 months ago Jonas Wilms #3

You might use a hashtable to build up the resultig array:

 const result = [], hash = {};

for(const {name, count} of fruits) {
  if(hash[name]) {
     hash[name].count += count;
   } else {
     result.push(hash[name] = {name, count});
  }
}

answered 3 months ago Rohit.007 #4

You can try to reduce() like

const fruits = [
  { name: 'apple', count: 2 },
  { name: 'apple', count: 4 },
  { name: 'banana', count: 5 },
  { name: 'apple', count: 3 },
  { name: 'apple', count: 4 },
  { name: 'apple', count: 1 },
  { name: 'banana', count: 3 },
  { name: 'apple', count: 1 },
  { name: 'orange', count: 2 }
];

function getReduced(total, {name,count}) {
    total[name] = total[name] || {name,count: 0};
    total[name].count += count;
    return total;
}

let fruitsReduces = Object.values(fruits.reduce(getReduced,{}));
console.log(fruitsReduces);

answered 3 months ago KooiInc #5

Yet another (slightly different) solution

const fruits = [ 
  { name: 'apple', count: 2 }, 
  { name: 'apple', count: 4 }, 
  { name: 'banana', count: 5 }, 
  { name: 'apple', count: 3 }, 
  { name: 'apple', count: 4 }, 
  { name: 'apple', count: 1 }, 
  { name: 'banana', count: 3 },
  { name: 'apple', count: 1 }, 
  { name: 'orange', count: 2 } ];

const reduced = Object.entries ( 
  fruits.reduce( (p, o) => 
    p[o.name] ? (p[o.name] += o.count, p) : {...p, ...{[o.name]: o.count}}, {} ) )
  .map(([key, value]) => ({name: key, count: value}));

console.log(reduced);

answered 3 months ago Alex G #6

You can use a simple forEach method like this:

const res = {};

fruits.forEach((e) => res[e.name] = (res[e.name] || 0) + e.count);

res will contain your expected result.

comments powered by Disqus