Related
I have an array of objects like the following:
[
{
'name': 'P1',
'value': 150
},
{
'name': 'P1',
'value': 150
},
{
'name': 'P2',
'value': 200
},
{
'name': 'P3',
'value': 450
}
]
I need to add up all the values for objects with the same name. (Probably also other mathematical operations like calculate average.) For the example above the result would be:
[
{
'name': 'P1',
'value': 300
},
{
'name': 'P2',
'value': 200
},
{
'name': 'P3',
'value': 450
}
]
First iterate through the array and push the 'name' into another object's property. If the property exists add the 'value' to the value of the property otherwise initialize the property to the 'value'. Once you build this object, iterate through the properties and push them to another array.
Here is some code:
var obj = [
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P2', 'value': 200 },
{ 'name': 'P3', 'value': 450 }
];
var holder = {};
obj.forEach(function(d) {
if (holder.hasOwnProperty(d.name)) {
holder[d.name] = holder[d.name] + d.value;
} else {
holder[d.name] = d.value;
}
});
var obj2 = [];
for (var prop in holder) {
obj2.push({ name: prop, value: holder[prop] });
}
console.log(obj2);
Hope this helps.
An ES6 approach to group by name:
You can convert your array of objects to a Map by using .reduce(). The Map has key-value pairs, where each key is the name, and each value is the accumulated sum of values for that particular name key. You can then easily convert the Map back into an array using Array.from(), where you can provide a mapping function that will take the keys/values of the Map and convert them into objects:
const arr = [ { 'name': 'P1', 'value': 150 }, { 'name': 'P1', 'value': 150 }, { 'name': 'P2', 'value': 200 }, { 'name': 'P3', 'value': 450 } ];
const res = Array.from(arr.reduce(
(m, {name, value}) => m.set(name, (m.get(name) || 0) + value), new Map
), ([name, value]) => ({name, value}));
console.log(res);
The above is quite compact and not necessarily the easiest to read. I would suggest putting it into a function so it's clearer what it's doing. If you're after more self-documenting code, using for...of can make the above easier to understand. The below function is also useful if you want to group or sum on keys with spaces in them:
const arr = [ { 'name': 'P1', 'value': 150 }, { 'name': 'P1', 'value': 150 }, { 'name': 'P2', 'value': 200 }, { 'name': 'P3', 'value': 450 } ];
const sumByKey = (arr, key, value) => {
const map = new Map();
for(const obj of arr) {
const currSum = map.get(obj[key]) || 0;
map.set(obj[key], currSum + obj[value]);
}
const res = Array.from(map, ([k, v]) => ({[key]: k, [value]: v}));
return res;
}
console.log(sumByKey(arr, 'name', 'value')); // 'name' = value to group by, 'value' = value to sum
Grouping by more than just name:
Here's an approach that should work if you have other overlapping properties other than just name (the keys/values need to be the same in both objects for them to "group"). It involves iterating through your array and reducing it to Map which holds key-value pairs. Each key of the new Map is a string of all the property values you want to group by, and so, if your object key already exists then you know it is a duplicate, which means you can add the object's current value to the stored object. Finally, you can use Array.from() to transform your Map of key-value pairs, to an array of just values:
const arr = [{'name':'P1','value':150},{'name':'P1','value':150},{'name':'P2','value':200},{'name':'P3','value':450}];
const res = Array.from(arr.reduce((acc, {value, ...r}) => {
const key = JSON.stringify(r);
const current = acc.get(key) || {...r, value: 0};
return acc.set(key, {...current, value: current.value + value});
}, new Map).values());
console.log(res);
You can use Array.reduce() to accumulate results during each iteration.
var arr = [{'name':'P1','value':150},{'name':'P1','value':150},{'name':'P2','value':200},{'name':'P3','value':450}];
var result = arr.reduce(function(acc, val){
var o = acc.filter(function(obj){
return obj.name==val.name;
}).pop() || {name:val.name, value:0};
o.value += val.value;
acc.push(o);
return acc;
},[]);
console.log(result);
I see these complicated reduce with Array from and Map and Set - this is far simpler
const summed = arr.reduce((acc, cur, i) => {
const item = i > 0 && acc.find(({name}) => name === cur.name)
if (item) item.value += cur.value;
else acc.push({ name: cur.name, value: cur.value }); // don't push cur here
return acc;
}, [])
console.log(arr); // not modified
console.log(summed);
<script>
const arr = [{
'name': 'P1',
'value': 150
},
{
'name': 'P1',
'value': 150
},
{
'name': 'P2',
'value': 200
},
{
'name': 'P3',
'value': 450
}
]
</script>
For some reason when I ran the code by #Vignesh Raja, I obtained the "sum" but also items duplicated. So, I had to remove the duplicates as I described below.
Original array:
arr=[{name: "LINCE-01", y: 70},
{name: "LINCE-01", y: 155},
{name: "LINCE-01", y: 210},
{name: "MIRAFLORES-03", y: 232},
{name: "MIRAFLORES-03", y: 267}]
Using #VigneshRaja's code:
var result = arr.reduce(function(acc, val){
var o = acc.filter(function(obj){
return obj.name==val.name;
}).pop() || {name:val.name, y:0};
o.y += val.y;
acc.push(o);
return acc;
},[]);
console.log(result);
First outcome:
result: [{name: "LINCE-01", y: 435},
{name: "LINCE-01", y: 435},
{name: "LINCE-01", y: 435},
{name: "MIRAFLORES-03", y: 499},
{name: "MIRAFLORES-03", y: 499}]
Removing the duplicates:
var finalresult = result.filter(function(itm, i, a) {
return i == a.indexOf(itm);
});
console.log(finalresult);
Finally, I obtained what I whished:
finalresult = [{name: "LINCE-01", y: 435},
{name: "MIRAFLORES-03", y: 657}]
Regards,
I have customized Mr Nick Parsons answer(Thanks for the idea). if you need to sum multiple key values.
var arr = [{'name':'P1','value':150,'apple':10},{'name':'P1','value':150,'apple':20},{'name':'P2','value':200,'apple':30},{'name':'P3','value':450,'apple':40}];
var res = Object.values(arr.reduce((acc, {value,apple , ...r}) => {
var key = Object.entries(r).join('-');
acc[key] = (acc[key] || {...r, apple:0,value: 0});
return (acc[key].apple += apple, acc[key].value += value, acc);
}, {}));
console.log(res);
One more solution which is clean, I guess
var obj = [
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P2', 'value': 200 },
{ 'name': 'P3', 'value': 450 }
];
var result = [];
Array.from(new Set(obj.map(x => x.name))).forEach(x => {
result.push(obj.filter(y => y.name === x).reduce((output,item) => {
let val = output[x] === undefined?0:output[x];
output[x] = (item.value + val);
return output;
},{}));
})
console.log(result);
if you need to keep the object structure same than,
var obj = [
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P2', 'value': 200 },
{ 'name': 'P2', 'value': 1000 },
{ 'name': 'P3', 'value': 450 }
];
let output = [];
let result = {};
let uniqueName = Array.from(new Set(obj.map(x => x.name)));
uniqueName.forEach(n => {
output.push(obj.filter(x => x.name === n).reduce((a, item) => {
let val = a['name'] === undefined? item.value:a['value']+item.value;
return {name:n,value:val};
},{})
);
});
console.log(output);
My data was not so organized as the example data. I faced a few issues such as I wanted to total the number of unique strings in a group and I have extra data that I don't care to tally.
I also found it a bit hard to substitute some of the answers demo data obj values for real world values depending on code readability. I decided I like #NickParsons answer best --> https://stackoverflow.com/a/57477448/5079799 and decided to add to it. Wasn't sure if this should be a new question like "Sum similar specific keys and/or sum strings in arrays" but this post seemed to be close enough that I felt an answer here was best.
I decided that using Arrays for string was best, and I made it an option for my function to DeDup array if desired. But now you can use arr.length for number of hits and as in my case, I will then also have further uses for that array as a collection based on the group.
I will likely be adding extra iterations over my Arr_Of_Objs but I like having this master function and then making smaller logical groups of Objs. I have a lot of cross-referencing/tallying and I tried making one large iteration where I did all the logic, and it became a total mess quickly.
I also added the ability to total strings if your object values happen to be cast as string vs int.
As well, the ability to pass toFixed to trim decimal places.
And Criteria_Objs for further restricting sums. Since you can't pass an operator, I made a single if then to check for "==" and use == as an operator. If you want more, you'd have to code them in, but should be easy to say add >
Here is some code:
var arr = [
{
'group': 'A',
"Type": "A",
"batch": "FOO_A",
"name": "Unique_ID_1",
'value': "150",
},
{
'group': 'A',
"Type": "A",
"batch": "FOO_A",
"name": "Unique_ID_11",
'value': 150,
},
{
'group': 'A',
"Type": "A",
"batch": "FOO_B",
"name": "Unique_ID_2",
'value': 150,
},
{
'group': 'A',
"Type": "A",
"batch": "FOO_B",
"name": "Unique_ID_22",
'value': 150,
},
{
'group': 'B',
"Type": "A",
"batch": "BAR_A",
"name": "Unique_ID_A1",
'value': 150,
},
{
'group': 'B',
"Type": "B",
"batch": "BAR_A",
"name": "Unique_ID_A11",
'value': 150,
},
{
'group': 'B',
"Type": "A",
"batch": "BAR_B",
"name": "Unique_ID_B2",
'value': 150,
},
{
'group': 'B',
"Type": "A",
"batch": "BAR_B",
"name": "Unique_ID_B22",
'value': "150.016",
},
]
const sumByKey = (arr, key, value, DeDup_Str_Arr, TO_Fixed, Criteria_Arr_Of_Objs) => {
/*
var Criteria_Arr_Of_Objs = [
{
"crit_key": "Type",
"crit_val": "A",
"crit_operator": "==",
},
]
*/
var Is_Int = false
if (!isNaN(TO_Fixed) && TO_Fixed != null && TO_Fixed != false && TO_Fixed != "") {
Is_Int = true
}
const map = new Map();
for (const obj of arr) {
const currSum = map.get(obj[key]) || 0;
var val = obj[value]
var crit_passed = false
if (Array.isArray(Criteria_Arr_Of_Objs) == true) {
for (var ai = 0; ai < Criteria_Arr_Of_Objs.length; ++ai) {
var crit_obj = Criteria_Arr_Of_Objs[ai]
var check_val = obj[crit_obj['crit_key']]
var crit_val = crit_obj['crit_val']
var crit_operator = crit_obj['crit_operator']
if (crit_operator == "==") {
if (check_val == crit_val) {
crit_passed = true
}
}
}
} else {
crit_passed = true
}
if (crit_passed == true) {
if (!isNaN(val)) {
val = Number(val)
}
if (typeof val === 'string' || val instanceof String) {
val = val + ","
}
map.set(obj[key], currSum + val);
}
}
if (Is_Int == true) {
var res = Array.from(map, ([k, v]) => ({ [key]: k, [value]: Number(Number(v).toFixed(2)) })); //
} else {
var res = Array.from(map, ([k, v]) => ({ [key]: k, [value]: v }));
}
var val = res[0][value]
if (typeof val === 'string' || val instanceof String) {
for (var ai = 0; ai < res.length; ++ai) {
var obj = res[ai]
var val = obj[value]
var vals_arr = val.split(",")
vals_arr[0] = vals_arr[0].substring(1) //Removing leading 0
vals_arr.pop() //trailing ","
if (DeDup_Str_Arr == true) {
let unique = [];
for (let element of vals_arr) {
if (Array.isArray(element) == true) {
for (let elm of element) { if (!unique.includes(elm)) { unique.push(elm) } }
} else {
if (!unique.includes(element)) { unique.push(element) }
}
}
obj[value] = unique
} else {
obj[value] = vals_arr
}
}
}
return res;
}
var Criteria_Arr_Of_Objs = [
{
"crit_key": "Type",
"crit_val": "A",
"crit_operator": "==",
},
]
console.log(sumByKey(arr, 'batch', 'value',false,false,Criteria_Arr_Of_Objs))
console.log(sumByKey(arr, 'batch', 'value'))
console.log(sumByKey(arr, 'batch', 'value', null, 2))
console.log(sumByKey(arr, 'batch', 'value', null, "2"))
console.log(sumByKey(arr, 'group', 'batch', null))
console.log(sumByKey(arr, 'group', 'batch', true))
console.log(sumByKey(arr, 'group', 'batch', true, "2"))
console.log(sumByKey(arr, 'group', 'batch', true, 2))
console.log(sumByKey(arr, 'group', 'value', true, 2))
(function () {
var arr = [
{'name': 'P1', 'age': 150},
{'name': 'P1', 'age': 150},
{'name': 'P2', 'age': 200},
{'name': 'P3', 'age': 450}
];
var resMap = new Map();
var result = [];
arr.map((x) => {
if (!resMap.has(x.name))
resMap.set(x.name, x.age);
else
resMap.set(x.name, (x.age + resMap.get(x.name)));
})
resMap.forEach((value, key) => {
result.push({
name: key,
age: value
})
})
console.log(result);
})();
let ary = [
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P2',
'key2': 200
},
{
'key1': 'P3',
'key2': 450
}
]
result array let newAray = []
for (let index = 0; index < ary.length; index++) {
// console.log(ary[index].key1);
for (let index2 = index + 1; index2 < ary.length; index2++) {
if (ary[index2].key1 == ary[index].key1) {
console.log('match');
ary[index].key2 += ary[index2].key2;
newAry = ary.filter( val => val !== ary[index2]);
newAry = ary.filter(function (e) {
return e !== ary[index2];
});
}
}
}
console.log(newAry)
Here provide more generic version for this question
/**
* #param {(item: T) => string} keyFn
* #param {(itemA: T, itemB: T) => T} mergeFn
* #param {number[]} list
*/
function compress(keyFn, mergeFn, list) {
return Array.from(
list
.reduce((map, item) => {
const key = keyFn(item);
return map.has(key) // if key already existed
? map.set(key, mergeFn(map.get(key), item)) // merge two items together
: map.set(key, item); // save item in map
}, new Map())
.values()
);
}
const testcase = [
{
name: "P1",
value: 150,
},
{
name: "P1",
value: 150,
},
{
name: "P2",
value: 200,
},
{
name: "P3",
value: 450,
},
];
console.log(
compress(
/* user define which is unique key */
({ name }) => name,
/* how to merge two item together */
(a, b) => ({ name: a.name, value: a.value + b.value }),
/* array */
testcase
)
)
I like this approach for readability.
const original = [
{ name: 'P1', value: 150 },
{ name: 'P1', value: 150 },
{ name: 'P2', value: 200 },
{ name: 'P3', value: 450 },
];
const aggregate = {};
original.forEach((item) => {
if (aggregate[item.name]) {
aggregate[item.name].value += item.value;
} else {
aggregate[item.name] = item;
}
});
const summary = Object.values(aggregate);
console.log(original)
console.log(aggregate)
console.log(summary);
let arr = [
{'name':'P1','value':150,'apple':10},
{'name':'P1','value':150,'apple':20},
{'name':'P2','value':200,'apple':30},
{'name':'P2','value':600,'apple':30},
{'name':'P3','value':450,'apple':40}
];
let obj = {}
arr.forEach((item)=>{
if(obj[item.name]){
obj[item.name].value = obj[item.name].value + item.value
}else{
obj[item.name] = item
}
})
let valuesArr = Object.values(obj)
console.log(valuesArr);
Output
[
{ name: "P1", value: 300, apple: 10 },
{ name: "P2", value: 800, apple: 30 },
{ name: "P3", value: 450, apple: 40 } ]
The method that Vignesh Raja posted will let you sum various values in an array of objects by the key or other property these method will work better
let data= [
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P2',
'key2': 200
},
{
'key1': 'P3',
'key2': 450
}
]
var holder = []
data.forEach( index => {
const data = holder.find( i => i.key1=== index.key1)
if(!data){
holder.push({key1:index.key1,key2:index.key2})
}else{
data.key2 = parseInt(data.key2) + parseInt(index.key2)
}
});
console.log(holder);
I'm trying to sort an array of objects by a key in a particular position. The only problem is that each object has a different key name.
here is an example of an object i'm trying to sort:
let data = [
{name: "James", x: 3, },
{name: "Thomas", y: 1},
{name: "Zack", z: 2}
];
I'm trying to sort it by the 2nd key so the order should be
[
{name: "James", x: 3, },
{name: "Zack", z: 2},
{name: "Thomas", y: 1}
];
here is how I'm trying to do it:
let data = [
{name: "James", x: 3, },
{name: "Thomas", y: 1},
{name: "Zack", z: 2}
];
data.sort((a, b) => {
let key1 = Object.keys(a)[1];
let key2 = Object.keys(b)[1];
return a[key1] > b[key2]
});
console.log(data)
Here is my jsbin
https://jsbin.com/lihefodoni/edit?html,js,console
Not sure why it's not working. I'm trying to do this in my react Application so I don't know if there's something different I need to do?
The .sort callback expects a number as a return value, not a Boolean (as the < operator will evaluate to). Return the numeric difference instead:
let data = [
{name: "James", x: 3, },
{name: "Thomas", y: 1},
{name: "Zack", z: 2}
];
data.sort((a, b) => {
let key1 = Object.keys(a)[1];
let key2 = Object.keys(b)[1];
return b[key2] - a[key1]
});
console.log(data)
To make sorting more reliable, .find the entry whose key is not name:
let data = [
{name: "James", x: 3, },
{name: "Thomas", y: 1},
{name: "Zack", z: 2}
];
data.sort((a, b) => {
const aEntry = Object.entries(a).find(([key]) => key !== 'name');
const bEntry = Object.entries(b).find(([key]) => key !== 'name');
return bEntry[1] - aEntry[1];
});
console.log(data)
You could exclude the unwanted key and take the values of the object. it works by taking the only item without index.
let data = [{ name: "James", x: 3 }, { name: "Thomas", y: 1 }, { name: "Zack", z: 2 }];
data.sort(({ name, ...a }, { name: _, ...b }) => Object.values(b) - Object.values(a));
console.log(data)
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an array filled with objects. The following example shows the structure of the objects.
let array = [
{
data: [{name:'a', value:20}, {name:'b', value:10}, {name:'c', value:5}]
},
{
data: [{name:'d', value:20}, {name:'a', value:10}, {name:'e', value:40}]
},
{
data: [{name:'b', value:30}, {name:'a', value:5}]
}
];
I'm trying to iterate through all the data values and summarize all the identical letters and sum up there values in a new array. So the new array should look like this:
let array = [{name:'a', value:35}, {name:'b', value:40}, {name:'c', value:5}, {name:'d', value:20}, {name:'e', value:40}];
This is my current approach but I don't get it to work.
let prevData = '';
let summarizedArray = [];
for(let i = 0; i < array.length; i++) {
for(let j = 0; j < array[i].data.length; j++) {
if(prevData === array[i].data[j].name) {
let summarized = {
name: array[i].data[j].name;
value: prevData.value + array[i].data[j].value;
}
summarizedArray.push(summarized);
}
prevData = array[i].data[j];
}
}
// Edited Example:
let array = [
{
data: [{name:'a', value1:20, value2:90, value3:'foo'},
{name:'b', value1:30, value2:20, value3:'boo'}]
},
data: [{name:'c', value1:5, value2:10, value3:'goo'},
{name:'a', value1:30, value2:20, value3:'foo'}]
},
{
];
The values should be bundled by same names. The values of Value1 and Value2 should be added up and Value3 is always the same for each name.
So the result should look like this:
let result = [{name:'a', value1:50, value2:110, value3:'foo'},
{name:'b', value1:30, value2:20, value3:'boo'},
{name:'c', value1:5, value2:10, value3:'goo'}
];
You could take a Map and collect all values. Later get an array of object of the collected values.
let array = [{ data: [{ name: 'a', value: 20 }, { name: 'b', value: 10 }, { name: 'c', value: 5 }] }, { data: [{ name: 'd', value: 20 }, { name: 'a', value: 10 }, { name: 'd', value: 40 }] }, { data: [{ name: 'b', value: 30 }, { name: 'a', value: 5 }] }],
result = Array.from(
array.reduce(
(m, { data }) => data.reduce(
(n, { name, value }) => n.set(name, (n.get(name) || 0) + value),
m
),
new Map
),
([name, value]) => ({ name, value })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For a more convoluted object, you could take single properties to add, after a check for the type.
var array = [{ data: [{ name: 'a', value1: 20, value2: 90, value3: 'foo' }, { name: 'b', value1: 30, value2: 20, value3: 'boo' }] }, { data: [{ name: 'c', value1: 5, value2: 10, value3: 'goo' }, { name: 'a', value1: 30, value2: 20, value3: 'foo' }] }],
result = Array.from(
array.reduce(
(m, { data }) => {
data.forEach(o => {
var temp = m.get(o.name);
if (!temp) {
m.set(o.name, temp = {});
}
Object.entries(o).forEach(([k, v]) => {
if (k === 'name') return;
if (typeof v === 'number') {
temp[k] = (temp[k] || 0) + v;
} else {
temp[k] = v;
}
});
});
return m;
},
new Map
),
([name, value]) => Object.assign({ name }, value)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This question already has answers here:
Most efficient method to groupby on an array of objects
(58 answers)
Closed 4 years ago.
What would be the best way to sort myarr into sortedArr so that all of the values have been added together for each invidual id using javascript?
myarr = [{id: 10, val:100}, {id:10, val: 100}, {id:20, val:200}, {id:20, val:100}, {id:30, val:100}]
sortedArr = [{id: 10, val: 200}, {id:20, val:300}, {id:30, 100}]
First sum the values per ID, then push them to a new array.
let myarr = [{ id: 10, val: 100 }, { id: 10, val: 100 }, { id: 20, val: 200 }, { id: 20, val: 100 }, { id: 30, val: 100 }]
let group = {}
myarr.forEach((value, index) => {
if (group[value.id]) {
group[value.id] += value.val
}
else {
group[value.id] = value.val
}
})
let res = []
Object.keys(group).forEach((key) => {
res.push({ id: key, val: group[key] })
})
console.log(res);
You need to keep a map of keys (hits) and reduce the original array. You can sort it afterwards, because the array will be smaller which speeds up sorting.
var myArr = [{id: 10, val:100}, {id:10, val: 100}, {id:20, val:200}, {id:20, val:100}, {id:30, val:100}];
var sortedArr = accumulateAndSortById(myArr, 'id', 'val');
console.log(sortedArr);
function accumulateAndSortById(arr, field, value) {
var hitMap = arr.reduce((result, item) => {
result[item[field]] = (result[item[field]] || 0) + item[value];
return result;
}, {});
return Object.keys(hitMap).reduce((result, key) => {
var obj = {};
obj[key] = hitMap[key];
result.push(obj);
return result;
}, []).sort((a, b) => a[field] > b[field]);
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
You can do:
const myArr = [{id: 10, val:100}, {id:10, val: 100}, {id:20, val:200}, {id:20, val:100}, {id:30, val:100}]
const temp = myArr.reduce((a, c) => (a[c.id] = (a[c.id] || 0) + c.val, a), {});
const sortedArr = Object.keys(temp).map(k => ({id: +k, val: temp[k]}));
console.log(sortedArr);
Use the following compare function to sort:
var myArr = [{id: 10, val:100}, {id:10, val: 100}, {id:20, val:200}, {id:20, val:100}, {id:30, val:100}];
function compare(a,b) {
if (a.id < b.id)
return -1;
if (a.id > b.id)
return 1;
return 0;
}
var sortedArr = myArr.sort(compare);
I have an array of objects like the following:
[
{
'name': 'P1',
'value': 150
},
{
'name': 'P1',
'value': 150
},
{
'name': 'P2',
'value': 200
},
{
'name': 'P3',
'value': 450
}
]
I need to add up all the values for objects with the same name. (Probably also other mathematical operations like calculate average.) For the example above the result would be:
[
{
'name': 'P1',
'value': 300
},
{
'name': 'P2',
'value': 200
},
{
'name': 'P3',
'value': 450
}
]
First iterate through the array and push the 'name' into another object's property. If the property exists add the 'value' to the value of the property otherwise initialize the property to the 'value'. Once you build this object, iterate through the properties and push them to another array.
Here is some code:
var obj = [
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P2', 'value': 200 },
{ 'name': 'P3', 'value': 450 }
];
var holder = {};
obj.forEach(function(d) {
if (holder.hasOwnProperty(d.name)) {
holder[d.name] = holder[d.name] + d.value;
} else {
holder[d.name] = d.value;
}
});
var obj2 = [];
for (var prop in holder) {
obj2.push({ name: prop, value: holder[prop] });
}
console.log(obj2);
Hope this helps.
An ES6 approach to group by name:
You can convert your array of objects to a Map by using .reduce(). The Map has key-value pairs, where each key is the name, and each value is the accumulated sum of values for that particular name key. You can then easily convert the Map back into an array using Array.from(), where you can provide a mapping function that will take the keys/values of the Map and convert them into objects:
const arr = [ { 'name': 'P1', 'value': 150 }, { 'name': 'P1', 'value': 150 }, { 'name': 'P2', 'value': 200 }, { 'name': 'P3', 'value': 450 } ];
const res = Array.from(arr.reduce(
(m, {name, value}) => m.set(name, (m.get(name) || 0) + value), new Map
), ([name, value]) => ({name, value}));
console.log(res);
The above is quite compact and not necessarily the easiest to read. I would suggest putting it into a function so it's clearer what it's doing. If you're after more self-documenting code, using for...of can make the above easier to understand. The below function is also useful if you want to group or sum on keys with spaces in them:
const arr = [ { 'name': 'P1', 'value': 150 }, { 'name': 'P1', 'value': 150 }, { 'name': 'P2', 'value': 200 }, { 'name': 'P3', 'value': 450 } ];
const sumByKey = (arr, key, value) => {
const map = new Map();
for(const obj of arr) {
const currSum = map.get(obj[key]) || 0;
map.set(obj[key], currSum + obj[value]);
}
const res = Array.from(map, ([k, v]) => ({[key]: k, [value]: v}));
return res;
}
console.log(sumByKey(arr, 'name', 'value')); // 'name' = value to group by, 'value' = value to sum
Grouping by more than just name:
Here's an approach that should work if you have other overlapping properties other than just name (the keys/values need to be the same in both objects for them to "group"). It involves iterating through your array and reducing it to Map which holds key-value pairs. Each key of the new Map is a string of all the property values you want to group by, and so, if your object key already exists then you know it is a duplicate, which means you can add the object's current value to the stored object. Finally, you can use Array.from() to transform your Map of key-value pairs, to an array of just values:
const arr = [{'name':'P1','value':150},{'name':'P1','value':150},{'name':'P2','value':200},{'name':'P3','value':450}];
const res = Array.from(arr.reduce((acc, {value, ...r}) => {
const key = JSON.stringify(r);
const current = acc.get(key) || {...r, value: 0};
return acc.set(key, {...current, value: current.value + value});
}, new Map).values());
console.log(res);
You can use Array.reduce() to accumulate results during each iteration.
var arr = [{'name':'P1','value':150},{'name':'P1','value':150},{'name':'P2','value':200},{'name':'P3','value':450}];
var result = arr.reduce(function(acc, val){
var o = acc.filter(function(obj){
return obj.name==val.name;
}).pop() || {name:val.name, value:0};
o.value += val.value;
acc.push(o);
return acc;
},[]);
console.log(result);
I see these complicated reduce with Array from and Map and Set - this is far simpler
const summed = arr.reduce((acc, cur, i) => {
const item = i > 0 && acc.find(({name}) => name === cur.name)
if (item) item.value += cur.value;
else acc.push({ name: cur.name, value: cur.value }); // don't push cur here
return acc;
}, [])
console.log(arr); // not modified
console.log(summed);
<script>
const arr = [{
'name': 'P1',
'value': 150
},
{
'name': 'P1',
'value': 150
},
{
'name': 'P2',
'value': 200
},
{
'name': 'P3',
'value': 450
}
]
</script>
For some reason when I ran the code by #Vignesh Raja, I obtained the "sum" but also items duplicated. So, I had to remove the duplicates as I described below.
Original array:
arr=[{name: "LINCE-01", y: 70},
{name: "LINCE-01", y: 155},
{name: "LINCE-01", y: 210},
{name: "MIRAFLORES-03", y: 232},
{name: "MIRAFLORES-03", y: 267}]
Using #VigneshRaja's code:
var result = arr.reduce(function(acc, val){
var o = acc.filter(function(obj){
return obj.name==val.name;
}).pop() || {name:val.name, y:0};
o.y += val.y;
acc.push(o);
return acc;
},[]);
console.log(result);
First outcome:
result: [{name: "LINCE-01", y: 435},
{name: "LINCE-01", y: 435},
{name: "LINCE-01", y: 435},
{name: "MIRAFLORES-03", y: 499},
{name: "MIRAFLORES-03", y: 499}]
Removing the duplicates:
var finalresult = result.filter(function(itm, i, a) {
return i == a.indexOf(itm);
});
console.log(finalresult);
Finally, I obtained what I whished:
finalresult = [{name: "LINCE-01", y: 435},
{name: "MIRAFLORES-03", y: 657}]
Regards,
I have customized Mr Nick Parsons answer(Thanks for the idea). if you need to sum multiple key values.
var arr = [{'name':'P1','value':150,'apple':10},{'name':'P1','value':150,'apple':20},{'name':'P2','value':200,'apple':30},{'name':'P3','value':450,'apple':40}];
var res = Object.values(arr.reduce((acc, {value,apple , ...r}) => {
var key = Object.entries(r).join('-');
acc[key] = (acc[key] || {...r, apple:0,value: 0});
return (acc[key].apple += apple, acc[key].value += value, acc);
}, {}));
console.log(res);
One more solution which is clean, I guess
var obj = [
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P2', 'value': 200 },
{ 'name': 'P3', 'value': 450 }
];
var result = [];
Array.from(new Set(obj.map(x => x.name))).forEach(x => {
result.push(obj.filter(y => y.name === x).reduce((output,item) => {
let val = output[x] === undefined?0:output[x];
output[x] = (item.value + val);
return output;
},{}));
})
console.log(result);
if you need to keep the object structure same than,
var obj = [
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P1', 'value': 150 },
{ 'name': 'P2', 'value': 200 },
{ 'name': 'P2', 'value': 1000 },
{ 'name': 'P3', 'value': 450 }
];
let output = [];
let result = {};
let uniqueName = Array.from(new Set(obj.map(x => x.name)));
uniqueName.forEach(n => {
output.push(obj.filter(x => x.name === n).reduce((a, item) => {
let val = a['name'] === undefined? item.value:a['value']+item.value;
return {name:n,value:val};
},{})
);
});
console.log(output);
My data was not so organized as the example data. I faced a few issues such as I wanted to total the number of unique strings in a group and I have extra data that I don't care to tally.
I also found it a bit hard to substitute some of the answers demo data obj values for real world values depending on code readability. I decided I like #NickParsons answer best --> https://stackoverflow.com/a/57477448/5079799 and decided to add to it. Wasn't sure if this should be a new question like "Sum similar specific keys and/or sum strings in arrays" but this post seemed to be close enough that I felt an answer here was best.
I decided that using Arrays for string was best, and I made it an option for my function to DeDup array if desired. But now you can use arr.length for number of hits and as in my case, I will then also have further uses for that array as a collection based on the group.
I will likely be adding extra iterations over my Arr_Of_Objs but I like having this master function and then making smaller logical groups of Objs. I have a lot of cross-referencing/tallying and I tried making one large iteration where I did all the logic, and it became a total mess quickly.
I also added the ability to total strings if your object values happen to be cast as string vs int.
As well, the ability to pass toFixed to trim decimal places.
And Criteria_Objs for further restricting sums. Since you can't pass an operator, I made a single if then to check for "==" and use == as an operator. If you want more, you'd have to code them in, but should be easy to say add >
Here is some code:
var arr = [
{
'group': 'A',
"Type": "A",
"batch": "FOO_A",
"name": "Unique_ID_1",
'value': "150",
},
{
'group': 'A',
"Type": "A",
"batch": "FOO_A",
"name": "Unique_ID_11",
'value': 150,
},
{
'group': 'A',
"Type": "A",
"batch": "FOO_B",
"name": "Unique_ID_2",
'value': 150,
},
{
'group': 'A',
"Type": "A",
"batch": "FOO_B",
"name": "Unique_ID_22",
'value': 150,
},
{
'group': 'B',
"Type": "A",
"batch": "BAR_A",
"name": "Unique_ID_A1",
'value': 150,
},
{
'group': 'B',
"Type": "B",
"batch": "BAR_A",
"name": "Unique_ID_A11",
'value': 150,
},
{
'group': 'B',
"Type": "A",
"batch": "BAR_B",
"name": "Unique_ID_B2",
'value': 150,
},
{
'group': 'B',
"Type": "A",
"batch": "BAR_B",
"name": "Unique_ID_B22",
'value': "150.016",
},
]
const sumByKey = (arr, key, value, DeDup_Str_Arr, TO_Fixed, Criteria_Arr_Of_Objs) => {
/*
var Criteria_Arr_Of_Objs = [
{
"crit_key": "Type",
"crit_val": "A",
"crit_operator": "==",
},
]
*/
var Is_Int = false
if (!isNaN(TO_Fixed) && TO_Fixed != null && TO_Fixed != false && TO_Fixed != "") {
Is_Int = true
}
const map = new Map();
for (const obj of arr) {
const currSum = map.get(obj[key]) || 0;
var val = obj[value]
var crit_passed = false
if (Array.isArray(Criteria_Arr_Of_Objs) == true) {
for (var ai = 0; ai < Criteria_Arr_Of_Objs.length; ++ai) {
var crit_obj = Criteria_Arr_Of_Objs[ai]
var check_val = obj[crit_obj['crit_key']]
var crit_val = crit_obj['crit_val']
var crit_operator = crit_obj['crit_operator']
if (crit_operator == "==") {
if (check_val == crit_val) {
crit_passed = true
}
}
}
} else {
crit_passed = true
}
if (crit_passed == true) {
if (!isNaN(val)) {
val = Number(val)
}
if (typeof val === 'string' || val instanceof String) {
val = val + ","
}
map.set(obj[key], currSum + val);
}
}
if (Is_Int == true) {
var res = Array.from(map, ([k, v]) => ({ [key]: k, [value]: Number(Number(v).toFixed(2)) })); //
} else {
var res = Array.from(map, ([k, v]) => ({ [key]: k, [value]: v }));
}
var val = res[0][value]
if (typeof val === 'string' || val instanceof String) {
for (var ai = 0; ai < res.length; ++ai) {
var obj = res[ai]
var val = obj[value]
var vals_arr = val.split(",")
vals_arr[0] = vals_arr[0].substring(1) //Removing leading 0
vals_arr.pop() //trailing ","
if (DeDup_Str_Arr == true) {
let unique = [];
for (let element of vals_arr) {
if (Array.isArray(element) == true) {
for (let elm of element) { if (!unique.includes(elm)) { unique.push(elm) } }
} else {
if (!unique.includes(element)) { unique.push(element) }
}
}
obj[value] = unique
} else {
obj[value] = vals_arr
}
}
}
return res;
}
var Criteria_Arr_Of_Objs = [
{
"crit_key": "Type",
"crit_val": "A",
"crit_operator": "==",
},
]
console.log(sumByKey(arr, 'batch', 'value',false,false,Criteria_Arr_Of_Objs))
console.log(sumByKey(arr, 'batch', 'value'))
console.log(sumByKey(arr, 'batch', 'value', null, 2))
console.log(sumByKey(arr, 'batch', 'value', null, "2"))
console.log(sumByKey(arr, 'group', 'batch', null))
console.log(sumByKey(arr, 'group', 'batch', true))
console.log(sumByKey(arr, 'group', 'batch', true, "2"))
console.log(sumByKey(arr, 'group', 'batch', true, 2))
console.log(sumByKey(arr, 'group', 'value', true, 2))
(function () {
var arr = [
{'name': 'P1', 'age': 150},
{'name': 'P1', 'age': 150},
{'name': 'P2', 'age': 200},
{'name': 'P3', 'age': 450}
];
var resMap = new Map();
var result = [];
arr.map((x) => {
if (!resMap.has(x.name))
resMap.set(x.name, x.age);
else
resMap.set(x.name, (x.age + resMap.get(x.name)));
})
resMap.forEach((value, key) => {
result.push({
name: key,
age: value
})
})
console.log(result);
})();
let ary = [
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P2',
'key2': 200
},
{
'key1': 'P3',
'key2': 450
}
]
result array let newAray = []
for (let index = 0; index < ary.length; index++) {
// console.log(ary[index].key1);
for (let index2 = index + 1; index2 < ary.length; index2++) {
if (ary[index2].key1 == ary[index].key1) {
console.log('match');
ary[index].key2 += ary[index2].key2;
newAry = ary.filter( val => val !== ary[index2]);
newAry = ary.filter(function (e) {
return e !== ary[index2];
});
}
}
}
console.log(newAry)
Here provide more generic version for this question
/**
* #param {(item: T) => string} keyFn
* #param {(itemA: T, itemB: T) => T} mergeFn
* #param {number[]} list
*/
function compress(keyFn, mergeFn, list) {
return Array.from(
list
.reduce((map, item) => {
const key = keyFn(item);
return map.has(key) // if key already existed
? map.set(key, mergeFn(map.get(key), item)) // merge two items together
: map.set(key, item); // save item in map
}, new Map())
.values()
);
}
const testcase = [
{
name: "P1",
value: 150,
},
{
name: "P1",
value: 150,
},
{
name: "P2",
value: 200,
},
{
name: "P3",
value: 450,
},
];
console.log(
compress(
/* user define which is unique key */
({ name }) => name,
/* how to merge two item together */
(a, b) => ({ name: a.name, value: a.value + b.value }),
/* array */
testcase
)
)
I like this approach for readability.
const original = [
{ name: 'P1', value: 150 },
{ name: 'P1', value: 150 },
{ name: 'P2', value: 200 },
{ name: 'P3', value: 450 },
];
const aggregate = {};
original.forEach((item) => {
if (aggregate[item.name]) {
aggregate[item.name].value += item.value;
} else {
aggregate[item.name] = item;
}
});
const summary = Object.values(aggregate);
console.log(original)
console.log(aggregate)
console.log(summary);
let arr = [
{'name':'P1','value':150,'apple':10},
{'name':'P1','value':150,'apple':20},
{'name':'P2','value':200,'apple':30},
{'name':'P2','value':600,'apple':30},
{'name':'P3','value':450,'apple':40}
];
let obj = {}
arr.forEach((item)=>{
if(obj[item.name]){
obj[item.name].value = obj[item.name].value + item.value
}else{
obj[item.name] = item
}
})
let valuesArr = Object.values(obj)
console.log(valuesArr);
Output
[
{ name: "P1", value: 300, apple: 10 },
{ name: "P2", value: 800, apple: 30 },
{ name: "P3", value: 450, apple: 40 } ]
The method that Vignesh Raja posted will let you sum various values in an array of objects by the key or other property these method will work better
let data= [
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P1',
'key2': 150
},
{
'key1': 'P2',
'key2': 200
},
{
'key1': 'P3',
'key2': 450
}
]
var holder = []
data.forEach( index => {
const data = holder.find( i => i.key1=== index.key1)
if(!data){
holder.push({key1:index.key1,key2:index.key2})
}else{
data.key2 = parseInt(data.key2) + parseInt(index.key2)
}
});
console.log(holder);