Sum JavaScript object propertyA values with the same object propertyB in an array of objects
Asked Answered
C

13

23

How would one take a JavaScript array of objects, such as

objArr = [
    {key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:42},
    {key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
    {key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23},
    {key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:54} // <- duplicate key
]

and merge duplicate keys by summing the values?

In order to get something like this:

reducedObjArr = [
    {key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:96},
    {key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
    {key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23}
]

I have tried iterating and adding to a new array, but this didn't work:

var reducedObjArr = [];
var item = null, key = null;
for(var i=0; i<objArr.length; i++) {
    item = objArr[i];
    key = Object.keys(item)[0];
    item = item[key];

    if(!result[key]) {
        result[key] = item;
    } else {
       result[key] += item;
    }
}a
Crim answered 7/10, 2013 at 19:45 Comment(5)
Why are you doing key = Object.keys(item)[0]; item=item[key]; ? You already know the name is key, so just do item.key or objArr[i].key. Also, using the [0] index won't necessarily always give you the same property.Contemptible
Thanks lookinig for same ,,Leer
Will it work without quoting the strings, e.g. Mon Sep 23 2013 00:00:00 GMT-0400?Dottie
A TypeScript alternative is discussed in Group by and sum on multiple keys maintaining type safetyDurango
Does this answer your question? Sum similar keys in an array of objectsMagnetohydrodynamics
C
15

You should be assigning each object not found to the result with its .key property.

If it is found, then you need to add its .val.

var temp = {};
var obj = null;
for(var i=0; i < objArr.length; i++) {
   obj=objArr[i];

   if(!temp[obj.key]) {
       temp[obj.key] = obj;
   } else {
       temp[obj.key].val += obj.val;
   }
}
var result = [];
for (var prop in temp)
    result.push(temp[prop]);

Also, part of the problem was that you were reusing the item variable to reference the value of .key, so you lost reference to the object.

Contemptible answered 7/10, 2013 at 19:53 Comment(0)
P
17

Rather than using a for loop and pushing values, you can directly use map and reduce:

let objArr = [
  {key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42},
  {key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78},
  {key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23},
  {key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54}
];

// first, convert data into a Map with reduce
let counts = objArr.reduce((prev, curr) => {
  let count = prev.get(curr.key) || 0;
  prev.set(curr.key, curr.val + count);
  return prev;
}, new Map());

// then, map your counts object back to an array
let reducedObjArr = [...counts].map(([key, value]) => {
  return {key, value}
})

console.log(reducedObjArr);
Perchance answered 3/6, 2016 at 21:15 Comment(2)
Can a seperate count be also added as well using map/reduce. Example : 23rd sep, count will be 2, for 24th & 25 count will be 1.Tourniquet
Yep! You'd just do something like count + 1 instead of curr.val + count in the prev.set callPerchance
C
15

You should be assigning each object not found to the result with its .key property.

If it is found, then you need to add its .val.

var temp = {};
var obj = null;
for(var i=0; i < objArr.length; i++) {
   obj=objArr[i];

   if(!temp[obj.key]) {
       temp[obj.key] = obj;
   } else {
       temp[obj.key].val += obj.val;
   }
}
var result = [];
for (var prop in temp)
    result.push(temp[prop]);

Also, part of the problem was that you were reusing the item variable to reference the value of .key, so you lost reference to the object.

Contemptible answered 7/10, 2013 at 19:53 Comment(0)
H
11

Simpler reduce than posted elsewhere since it does not use a Map element

const objArr = [
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:42},
{key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
{key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23},
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:54}];

const output = objArr.reduce((accumulator, cur) => {
  let date = cur.key;
  let found = accumulator.find(elem => elem.key === date)
  if (found) found.val += cur.val;
  else accumulator.push(cur);
  return accumulator;
}, []);

console.log(output)
Hooray answered 20/11, 2019 at 10:12 Comment(2)
Still, an explanation would be in order. E.g., in what way is it simpler? What is the idea/gist? From the Help Center: "...always explain why the solution you're presenting is appropriate and how it works". Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Dottie
It’s not using a map. Updated. It is just simpler code.Hooray
H
5

You could use a hash table for the grouping by key.

var array = [{ key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42 }, { key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78 }, { key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23 }, { key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54}],
    grouped = [];

array.forEach(function (o) {
    if (!this[o.key]) {
        this[o.key] = { key: o.key, val: 0 };
        grouped.push(this[o.key]);
    }
    this[o.key].val += o.val;
}, Object.create(null));

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

Another approach is to collect all key/value pairs in a Map and format the final array with Array.from and a callback for the objects.

var array = [{ key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42 }, { key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78 }, { key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23 }, { key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54 }],
    grouped = Array.from(
        array.reduce((m, { key, val }) => m.set(key, (m.get(key) || 0) + val), new Map),
        ([key, val]) => ({ key, val })
    );

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Heronry answered 13/1, 2017 at 19:39 Comment(11)
how to add multiple keys?Rattigan
@klent, replace val in this[o.key].val += o.val; with your property to add.Heronry
Sorry, I've meant how to add another condition before it adds for example if their's another property called 'category'. I want to check key and category.Rattigan
take an array for the keys to group, like groups = ['foo', 'bar'], inside the forEach use var key = groups.map(k => o[k]).join('|'); and then use this[key] instead of this[o.key]. maybe omit this at all and use a global variable for the object for grouping.Heronry
how to sum same values in object if my obj something like this obj = [ { menu: "apple", amount: 3}, { menu: "apple", amount: 1}, {menu: "melon", amount: 10}, {menu: "stawberry", amount: 7} ]; i want the ouput is [ {menu: apple, total: 4}, {menu: melon, total: 10}, and.......... ] @NinaScholzDeutsch
@ZumDummi, you need to reduce the array by finding same menu properties and update amount.Heronry
can we just use loops, because reduce and some magic functions not allow at my school for now:D we learn old style method:DDeutsch
in this case, you better take an object as hash table for the object for accumulation and check against it, if not exists.Heronry
The second approach doesn't seem to work. Values are NaN.Priorate
Can a seperate count be also added as well using map/reduce. Example : 23rd sep, count will be 2, for 24th & 25 count will be 1.Tourniquet
How can we use a property of the key if the key is an object itself instead of being a string inside the array data. For example instead of accessing the 'key' in "var array" accessing 'key.username'?Chiquia
A
3

var objArr = [
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:42},
{key:"Mon Sep 24 2013 00:00:00 GMT-0400", val:78},
{key:"Mon Sep 25 2013 00:00:00 GMT-0400", val:23},
{key:"Mon Sep 23 2013 00:00:00 GMT-0400", val:54}]

var targetObj = {};
for (var i = 0; i < objArr.length; i++) {
  if (!targetObj.hasOwnProperty(objArr[i].key)) {
    targetObj[objArr[i].key] = 0;
  }
  targetObj[objArr[i].key] += objArr[i].val;
}

console.log(targetObj);
Actiniform answered 7/10, 2013 at 19:53 Comment(1)
OP wants the same key/value pairs, but with the val consolidated.Contemptible
L
2

One can use Array#reduce with an object to store the values for each key.

let arr = [{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:42},{key:'Mon Sep 24 2013 00:00:00 GMT-0400', val:78},{key:'Mon Sep 25 2013 00:00:00 GMT-0400', val:23},{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:54}];
let res = Object.values(arr.reduce((acc, curr)=>{
  (acc[curr.key] = acc[curr.key] || {key: curr.key, val: 0}).val += curr.val;
  return acc;
}, {}));
console.log(res);

In newer browsers, logical nullish assignment can be used.

let arr = [{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:42},{key:'Mon Sep 24 2013 00:00:00 GMT-0400', val:78},{key:'Mon Sep 25 2013 00:00:00 GMT-0400', val:23},{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:54}];
let res = Object.values(arr.reduce((acc, curr)=>{
  (acc[curr.key] ??= {key: curr.key, val: 0}).val += curr.val;
  return acc;
}, {}));
console.log(res);
Lazarolazaruk answered 13/4, 2021 at 17:5 Comment(1)
Ah, finally a non-"try this" answer!Dottie
D
1

Try this. It should help.

var arr1 = [
  { name: 'besart', value: 12 },
  { name: 'astrit', value: 10 },
  { name: 'astrit', value: 10 },
  { name: 'besar', value: 18 },
  { name: 'besar', value: 3 },
  { name: 'astrit', value: 3 },
  { name: 'besart', value: 3 },
  { name: 'besart', value: 10 },
  { name: 'besar', value: 0 },
];
var arr2 = [];
var emri = "";
var value = 0;
for (var i = 0; i < arr1.length; i++) {
  emri = arr1[0].name;
  value += arr1[0].value;
  for (var j = 1; j < arr1.length; j++) {
    if (emri == arr1[j].name) {
      value += arr1[j].value;
      arr1.splice(j, 1);
      j--;
    }
  }
  arr1.splice(0, 1);
  arr2[i] = {
    name: emri,
    value: value
  };
  value = 0;
}
console.log(arr2);

Below is another solution that uses only one loop (a while loop):

var arr1 = [
  { name: 'besart', value: 12 },
  { name: 'astrit', value: 10 },
  { name: 'astrit', value: 10 },
  { name: 'besar', value: 18 },
  { name: 'besar', value: 3 },
  { name: 'astrit', value: 3 },
  { name: 'besart', value: 3 },
  { name: 'besart', value: 10 },
  { name: 'besar', value: 0 },
];

var arr2 = [];
var emri = "";
var value = 0;
var i = 1;
var j = 0;

while (arr1.length != 0) {
  emri = arr1[0].name;
  if (emri == arr1[i].name) {
    value += arr1[i].value;
    arr1.splice(i, 1);
    i--;
  }
  i++;
  if (i == arr1.length) {
    value += arr1[0].value;
    i = 1;
    arr2[j] = {
      name: emri,
      value: value
    };
    j++;
    value = 0;
    arr1.splice(0, 1);
  }
}
console.log(arr2)
Downpour answered 6/8, 2019 at 8:57 Comment(3)
Where did you copy this from? The input data is from Astrit Spanca's answer. Does it even answer the question? What is the relation between some of the input data and your user name?Dottie
In any case, an explanation would be in order. E.g., what is the idea/gist? From the Help Center: "...always explain why the solution you're presenting is appropriate and how it works". Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Dottie
OK, the OP has left the building ("Last seen more than 2 years ago"). There is not much value in this answer.Dottie
W
0

Here is an alternative for you, but similar to that of Explosion Pills. It reuses the original array rather than creating a new one or a different object. The sort may not be necessary and will slow things down a little, but it could be removed.

JavaScript

function reduceMyObjArr(arr) {
    var temp = {},
        index;

    for (index = arr.length - 1; index >= 0; index -= 1) {
        key = arr[index].key;
        if (temp.hasOwnProperty(key)) {
            arr[temp[key]].val += arr[index].val;
            arr.splice(index, 1);
        } else {
            temp[key] = index;
        }
    }

    arr.sort(function (a, b) {
        if (a.key === b.key) {
            return 0;
        }

        if (a.key < b.key) {
            return -1;
        }

        return 1;
    });

    return arr;
}

var myObjArr = [{
    key: "Mon Sep 23 2013 00: 00: 00 GMT - 0400",
    val: 42
}, {
    key: "Mon Sep 24 2013 00: 00: 00 GMT - 0400",
    val: 78
}, {
    key: "Mon Sep 25 2013 00: 00: 00 GMT - 0400",
    val: 23
}, {
    key: "Mon Sep 23 2013 00: 00: 00 GMT - 0400",
    val: 54
}];

reduceMyObjArr(myObjArr);

console.log(myObjArr);

jsFiddle

And a jsperf that compares this (with and without the sort) against the accepted answer. You can improve the performance test by extending the data set.

Wallie answered 7/10, 2013 at 21:0 Comment(0)
S
0

You can also try using the JavaScript linq framework which is exactly same as an SQL statement which is given desired output with less written code and effective and found at linq.js.

var objArr =
[
{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:42},
{key:'Mon Sep 24 2013 00:00:00 GMT-0400', val:78},
{key:'Mon Sep 25 2013 00:00:00 GMT-0400', val:23},
{key:'Mon Sep 23 2013 00:00:00 GMT-0400', val:54}
];


var aggregatedObject = Enumerable.From(objArr)
        .GroupBy("$.key", null,
                 function (key, g) {
                     return {
                       key: key,
                       contributions: g.Sum("$.val")
                     }
        })
        .ToArray();

console.log(aggregatedObject);
<script src="http://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.min.js"></script>

Which is pretty easy as compare to looping.

Sales answered 10/7, 2016 at 18:32 Comment(1)
This is confusing. There is also Microsoft's LINQ (.NET/C#). Can you add a (non-naked) link (URL) to "the JavaScript linq framework"? Please respond by editing (changing) your answer, not here in comments. (But without "Edit:", "Update:", or similar - the answer should appear as if it was written today.)Dottie
T
0

Recently I needed a similar implementation and I used a similar solution offered by some guy that used the reduce function.

A few days later I wanted to implement something similar by myself and here is the result.

const users = [
        { id: 1, name: 'ernest', spent: 40 },
        { id: 2, name: 'ernest', spent: 40 },
        { id: 3, name: 'astrit', spent: 22 },
        { id: 4, name: 'astrit', spent: 2956 },
        { id: 5, name: 'astrit', spent: 22 },
        { id: 6, name: 'besart', spent: 40 },
        { id: 7, name: 'besart', spent: 100},
        { id: 8, name: 'besart', spent: 4000 }
    ];

const sum = [];

users.forEach(el => {
    if(sum.length === 0) {
        delete el.id;
        sum.push(el);
    }
    else
    {
        const get = () => {
            for(let i = 0; i < sum.length; i++) {
                if(sum[i].name === el.name) {
                    return { stat: true, id: i };
                }
            }
        }

        let i = get();
        if(i) {
            sum[i.id].spent += el.spent;
        }
        else
        {
            delete el.id;
            sum.push(el);
        }
    }
});

console.log(sum);

Output:

[ { name: 'ernest', spent: 80 }, { name: 'astrit', spent: 3000 }, { name: 'besart', spent: 4140 } ]
Tachygraphy answered 5/8, 2019 at 19:31 Comment(0)
B
0
function sumGroupBy(array, groupColumn, valueColumn) {
    var res = []
    array.forEach((item) => {
         if(res.map((el) => el[groupColumn]).includes(item[groupColumn])) {
             res.filter((el) => el[groupColumn] == item[groupColumn])[0][valueColumn] += item[valueColumn]
         } else {
             eval(`res.push({${groupColumn}: "${item[groupColumn]}", ${valueColumn}: ${item[valueColumn]} })`)
         }
    });
    return res;
}

const pets = [
    {type:"Dog", age:12},
    {type:"Cat", age:13},
    {type:"Dog", age:6}, 
    {type:"Cat", age:18}
];
    
console.log(sumGroupBy(pets, 'type', 'age' ))
// [ { type: 'Dog', age: 18 }, { type: 'Cat', age: 31 } ]
Barham answered 6/8, 2022 at 19:28 Comment(0)
M
0

Here is a more general function that you can use to group an array of objects based on a specified key groupKey and calculate the sum of a specified property sumKey:

function groupAndSum(arr, groupKey, sumKey) {
  return Object.values(
    arr.reduce((acc, curr) => {
      const group = curr[groupKey];
      acc[group] = acc[group] || {
        [groupKey]: group,
        [sumKey]: 0
      };
      acc[group][sumKey] += curr[sumKey];
      return acc;
    }, {})
  );
}

Demo:

let objArr = [{ key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 42 }, { key: 'Mon Sep 24 2013 00:00:00 GMT-0400', val: 78 }, { key: 'Mon Sep 25 2013 00:00:00 GMT-0400', val: 23 }, { key: 'Mon Sep 23 2013 00:00:00 GMT-0400', val: 54 } ];

function groupAndSum(arr, groupKey, sumKey) {
  return Object.values(
    arr.reduce((acc, curr) => {
      const group = curr[groupKey];
      acc[group] = acc[group] || {
        [groupKey]: group,
        [sumKey]: 0
      };
      acc[group][sumKey] += curr[sumKey];
      return acc;
    }, {})
  );
}
console.log(groupAndSum(objArr, 'key', 'val'))
Machicolate answered 4/7, 2023 at 22:21 Comment(0)
B
-2
function mergeDuplicatesBy(array, getKey, mergeWith) {
  const buff = {}
  array.forEach(function (arrayElement) {
    const key = getKey(arrayElement)
    const alreadyExistingVal = buff[key]
    if (alreadyExistingVal) {
      buff[key] = mergeWith(alreadyExistingVal, arrayElement)
    } else {
      buff[key] = arrayElement
    }
  })
  return Object.values(buff)
}

mergeDuplicatesBy(
  arr,
  x => x.name,
  (x, y) => ({ name: x.name, foo: x.foo + y.foo })
)
Burkley answered 5/3, 2021 at 11:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.