summarize values of objects with same attribute name - javascript

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; }

Related

How to sum object values based on another object key JavaScript [duplicate]

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);

Javascript - How to combine all combinations into an array of objects

I have the following array:
[{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}]
Using some javascript logic I would like to output the following array:
[{
option1: 10,
option2: 'red'
},
{
option1: 10,
option2: 'blue'
},
{
option1: 12,
option2: 'red'
},
{
option1: 12,
option2: 'blue'
}]
What is the best and correct way to achieve this using javascript?
Lets say your first array is named arr.
var arr = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}];
var v1 = arr[0].values.split(',');
var v2 = arr[1].values.split(',');
var res = new Array();
for(i in v1){
for(j in v2){
res.push({'option1':v1[i],'option2':v2[j]});
}
}
console.log(res);
Here's an approach that can handle an arbitrary number of objects.
function valuesCrossProduct(input) {
return input.flatMap((current, index, array) => {
let result = [];
let values = current.values.split(',');
for (let v of values) {
for (let i = 0; i < array.length; i++) {
if (i <= index) {
// Skip creating cross products with self (i.e. == index)
// and with previously visited objects (i.e. < index).
continue;
}
let iValues = array[i].values.split(',');
let currentKey = `option${index}`;
let iKey = `option${i}`;
for (let iv of iValues) {
result.push({
[currentKey]: v,
[iKey]: iv,
});
}
}
}
return result;
});
}
let twoElementArray = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue',
}];
let threeElementArray = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue',
},
{
name: 'baz',
values: 'wham,bam',
}];
console.log(valuesCrossProduct(twoElementArray));
console.log(valuesCrossProduct(threeElementArray));
Functional for the win.
Note: as it is, this only works for an array of two objects, with any number of values in each, where the first set of values are numbers and the second set are strings, which is what you described above.
const arr = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}];
const values = arr
.map(o => o.values.split(','))
.reduce((cur, next) => {
return cur.map(c => {
return next.map(n => {
return {
option1: parseInt(c),
option2: n
};
});
}).flat();
});
console.log(values);
If you need generic approach to get possible options from various values.
const options = data => {
let sets = [[]];
data.forEach(({ values }, i) => {
const new_set = [];
values.split(",").forEach(value => {
new_set.push(
Array.from(sets, set => [...set, [`option${i + 1}`, value]])
);
});
sets = new_set.flatMap(set => set);
});
return sets.map(set => Object.fromEntries(set));
};
const data = [
{
name: "foo",
values: "10,12"
},
{
name: "bar",
values: "red,blue,green"
},
{
name: "test",
values: "top,bottom"
}
];
console.log(options(data));

How to manipulate an array by grouping items into arrays

Let's say I have this array:
let obj = [
{ prop: 'a', val: 2 },
{ prop: 'a', val: 1 },
{ prop: 'b', val: 3 },
{ prop: 'b', val: 1 }
]
Let's say i want to get an array for each existing prop val:
let myArr = [
[ { prop: 'a', val: 2 }, { prop: 'a', val: 1 } ]
[ { prop: 'b', val: 3 }, { prop: 'b', val: 1 } ]
]
how would you do that by using high order functions?
function sumProps(arr) {
const sum = arr.reduce((ac,cv) => {
if (ac[cv] === undefined) ac[cv.prop] = ac[cv.prop] = cv.val;
else ac[cv.prop] += cv.val;
}, {});
const res = [];
for (const p in sum) {
res.push({ prop:p, sum:sum[p] });
}
return res;
}
You could reduce the objects by looking for an object with same prop and update the value.
var array = [{ prop: 'a', val: 2 }, { prop: 'a', val: 1 }, { prop: 'b', val: 3 }, { prop: 'b', val: 1 }],
result = array.reduce((r, o) => {
var temp = r.find(({ prop }) => prop === o.prop);
if (temp) {
temp.val += o.val;
} else {
r.push(Object.assign({}, o));
}
return r
}, []);
console.log(result);

Array of Objects add amount for id [duplicate]

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);

Merge arrays in JS

Suppose I have the following arrays:
var first = [
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' }
]
var second = [
{ id: 2, field: 'foo2' },
{ id: 3, field: 'foo3' },
{ id: 4, field: 'foo4' }
]
var third = [
{ id: 2, data: 'some2' },
{ id: 5, data: 'some5' },
{ id: 6, data: 'some6' }
]
I want to merge them to get the following result:
var result = [
{ id: 1, name: 'first', field: undefined, data: undefined },
{ id: 2, name: 'second', field: 'foo2', data: 'some2' },
{ id: 3, name: 'third', field: 'foo3', data: undefined },
{ id: 4, name: undefined, field: 'foo4', data: undefined },
{ id: 5, name: undefined, field: undefined, data: 'some5' },
{ id: 6, name: undefined, field: undefined, data: 'some6' }
]
How could I do it with JavaScript?
You should get all existed keys and after create new Objects with fill "empty" keys:
function mergeArrays(){
var keys = {};
//save all existed keys
for(var i=arguments.length;--i;){
for(var j=arguments[i].length;--j;){
for(var key in arguments[i][j]){
keys[key] = true;
}
}
}
var res = [];
for(var i=arguments.length;--i;){
for(var j=arguments[i].length;--j;){
//set clone of object
var clone = JSON.parse(JSON.stringify(arguments[i][j]));
for(var key in keys){
if(!(key in clone)){
clone[key] = undefined;
}
}
res.push(clone);
}
}
return res;
}
https://jsfiddle.net/x3b0tk3g/
There is no simple solution for what you want. Here is my suggestion.
var first = [
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' }
]
var second = [
{ id: 2, filed: 'foo2' },
{ id: 3, field: 'foo3' },
{ id: 4, field: 'foo4' }
];
var third = [
{ id: 2, data: 'some2' },
{ id: 4, data: 'some4' },
{ id: 6, data: 'some6' }
];
var result = {};
first.concat(second,third).forEach(function(item){
var id = item.id;
var row = result[id];
if(!row){
result[id] = item;
return;
}
for(var column in item){
row[column] = item[column];
}
});
var finalResult = Object.keys(result).map(function(id){
return result[id];
});
console.log(finalResult);
fiddle: http://jsfiddle.net/bs20jvnj/2/
function getByProperty(arr, propName, propValue) {
for (var i = 0; i < arr.length; i++) {
if (arr[i][propName] == propValue) return arr[i];
}
}
var limit = first.length + second.length + third.length;
var res = [];
for (var i = 1; i < limit; i++) {
var x = $.extend({}, getByProperty(first, "id", i), getByProperty(second, "id", i), getByProperty(third, "id", i));
console.log(x["id"]);
if (x["id"] === undefined) x["id"] = i;
res.push(x);
}
console.log(res);
There's probably a shorter way to solve this, but this covers all the steps, including ensuring that there are default properties that are undefined if not found. It also takes any number of input arrays, and you can specify what default keys you require if they're not already covered by the keys in the existing objects, so pretty future-proof for your needs.
// merges the key/values of two objects
function merge(a, b) {
var key;
if (a && b) {
for (key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
}
return a;
}
function concatenate() {
var result = [];
var args = arguments[0];
for (var i = 0, l = args.length; i < l; i++) {
result = result.concat(args[i]);
}
return result;
}
// return a default object
function getDefault() {
return {
id: undefined,
name: undefined,
data: undefined,
field: undefined
};
}
// loop over the array and check the id. Add the id as a key to
// a temporary pre-filled default object if the key
// doesn't exist, otherwise merge the existing object and the
// new object
function createMergedArray(result) {
var temp = {};
var out = [];
for (var i = 0, l = result.length; i < l; i++) {
var id = result[i].id;
if (!temp[id]) temp[id] = getDefault();
merge(temp[id], result[i]);
}
// loop over the temporary object pushing the values
// into an output array, and return the array
for (var p in temp) {
out.push(temp[p]);
}
return out;
}
function mergeAll() {
// first concatenate the objects into a single array
// and then return the results of merging that array
return createMergedArray(concatenate(arguments));
}
mergeAll(first, second, third);
DEMO

Categories