pengz
pengz

Reputation: 2471

Javascript ES6 calculate the sum of an array of arrays of objects

I have multiple arrays of objects with a string property "CountType" and a number property "ItemCount".

The objects must be added up to calculate the total of all items by "CountType" property.

For example, I need to know the total number of Volumes, Sheets, Photos, etc.

Attempt #1: This code I attempted returns 0. Possibly that is because I need to loop through the outer array to do the comparison across the nested arrays.

function sumByProperty(items, prop) {
  if (items == null) {
      return 0;
  }
  return items.reduce(function (a, b) {
      return b[prop] == null ? a : a + b[prop];
  }, 0);
}

INPUT

Item count

[
    [
      {"CountType":"Volumes","ItemCount":3},
      {"CountType":"Sheets","ItemCount":6},
      {"CountType":"Photos","ItemCount":3},
      {"CountType":"Boxes","ItemCount":1},
      {"CountType":"Other","ItemCount":2}
    ],
    [
      {"CountType":"Volumes","ItemCount":1},
      {"CountType":"Sheets","ItemCount":1},
      {"CountType":"Photos","ItemCount":3},
      {"CountType":"Boxes","ItemCount":0},
      {"CountType":"Other","ItemCount":1}
    ],
    [
      {"CountType":"Volumes","ItemCount":1},
      {"CountType":"Sheets","ItemCount":0},
      {"CountType":"Photos","ItemCount":3},
      {"CountType":"Boxes","ItemCount":4},
      {"CountType":"Other","ItemCount":5}
    ]
]

DEISRED OUTPUT

Total count

[
  {"CountType":"Volumes","ItemCount":5},
  {"CountType":"Sheets","ItemCount":7},
  {"CountType":"Photos","ItemCount":9},
  {"CountType":"Boxes","ItemCount":5},
  {"CountType":"Other","ItemCount":8}
]

UPDATE: Here is how I am trying to run the function:

https://jsfiddle.net/yd051o76/2/

Upvotes: 3

Views: 1085

Answers (3)

mwilson
mwilson

Reputation: 12980

Here's an example using 2 reduce functions with a for ... in ... to merge the results back into the parent reducer.

The trick is to make sure you initialize your accumulator as null so you can define your own. Then, it's just a matter of reducing the array of arrays and having another reducer to run the array of objects. Once done, you just need to merge the result of the inner array back into the parent. (Might be a better way to do this part)

Fiddle: https://jsfiddle.net/mswilson4040/jcp9x6ba/26/

const json = [
  [
    {"CountType":"Volumes","ItemCount":3},
    {"CountType":"Sheets","ItemCount":6},
    {"CountType":"Photos","ItemCount":3},
    {"CountType":"Boxes","ItemCount":1},
    {"CountType":"Other","ItemCount":2}
  ],
  [
    {"CountType":"Volumes","ItemCount":1},
    {"CountType":"Sheets","ItemCount":1},
    {"CountType":"Photos","ItemCount":3},
    {"CountType":"Boxes","ItemCount":0},
    {"CountType":"Other","ItemCount":1}
  ],
  [
    {"CountType":"Volumes","ItemCount":1},
    {"CountType":"Sheets","ItemCount":0},
    {"CountType":"Photos","ItemCount":3},
    {"CountType":"Boxes","ItemCount":4},
    {"CountType":"Other","ItemCount":5}
  ]
];

const counts = json.reduce( (acc, currentArray) => {
	acc = acc ? acc : {};
  const arrCounts = currentArray.reduce( (_acc, _item) => {
  	_acc = _acc ? _acc : {};
    _acc[_item.CountType] = _acc[_item.CountType] ? _acc[_item.CountType] + _item.ItemCount : _item.ItemCount;
    return _acc;
  }, null);
  for (const item in arrCounts) {
  	acc[item] = acc[item] ? acc[item] + arrCounts[item] : arrCounts[item];
  }
  return acc;
}, null);


console.log(counts);

Upvotes: 1

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92735

Try (data is input, h = {}, the r is result);

data.map(a=> a.map(x=> h[x.CountType]=x.ItemCount+(h[x.CountType]||0) ))

r= Object.keys(h).map(k=> ({CountType:k, ItemCount:h[k] }) );

let data = [[
  {"CountType":"Volumes","ItemCount":3},
  {"CountType":"Sheets","ItemCount":6},
  {"CountType":"Photos","ItemCount":3},
  {"CountType":"Boxes","ItemCount":1},
  {"CountType":"Other","ItemCount":2}
],
[
  {"CountType":"Volumes","ItemCount":1},
  {"CountType":"Sheets","ItemCount":1},
  {"CountType":"Photos","ItemCount":3},
  {"CountType":"Boxes","ItemCount":0},
  {"CountType":"Other","ItemCount":1}
],
[
  {"CountType":"Volumes","ItemCount":1},
  {"CountType":"Sheets","ItemCount":0},
  {"CountType":"Photos","ItemCount":3},
  {"CountType":"Boxes","ItemCount":4},
  {"CountType":"Other","ItemCount":5}
]];

let r,h = {};
data.map(a=> a.map(x=> h[x.CountType]=x.ItemCount+(h[x.CountType]||0) ))
r= Object.keys(h).map(k=> ({CountType:k, ItemCount:h[k] }) );

console.log(r);

Upvotes: 0

Jonas Wilms
Jonas Wilms

Reputation: 138557

Possibly that is because I need to loop through the outer array to do the comparison across the nested arrays.

Indeed, you could flatten the array:

 function sumByProperty(items, prop) {
   return items.flat().reduce(function (a, b) {
     return b[prop] == null ? a : a + b[prop];
   }, 0);
 }

 const result = [
   { CountType: "Volumes", ItemCount: sumByProperty(input, "Volumes"), },
   //...
 ];

But I'd rather group dynamically using a hashtable, that way you only have to iterate once and you don't need to name all properties:

  const hash = new Map();

   for(const { CountType, ItemCount } of input.flat())
      hash.set(CountType, (hash.get(CountType) || 0) + ItemCount);

  const result = [...hash.entries()].map(([CountType, ItemCount]) => ({ CountType, ItemCount }));

Upvotes: 4

Related Questions