How to sum object values based on another object key JavaScript [duplicate] - javascript
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);
Related
Push array of objects that make up a count of 100
I have an array of objects like the following: const items = [ { 'name': 'P1', 'value': 50 }, { 'name': 'P1', 'value': 49 }, { 'name': 'P2', 'value': 50 }, { 'name': 'P3', 'value': 50 } ] I need to add up the values and then push into an array of objects only if the 1 or more objects make up a value total of 100. Expected output: const groupedItems = [ [ { 'name': 'P1', 'value': 50 }, { 'name': 'P1', 'value': 49 }, ], [ { 'name': 'P2', 'value': 50 }, { 'name': 'P3', 'value': 50 } ] ] What I have tried: const temp = []; for (var i = 0; i < items.length; i++) { if ((items[i] + items[i + 1]) < 100) { temp.push(items[i]); } } groupedItems.push(temp);
I guess i would approch your solution like this : Each time retrieving the first array, reducing it and if the sum of the array + the value of your item is less than 100, pushing your item in the array. Otherwise, pushing a new array in your groupedItems that contains your item. There might be a better way of doing this but this is the main idea. const items = [{ 'name': 'P1', 'value': 50 }, { 'name': 'P1', 'value': 49 }, { 'name': 'P2', 'value': 50 }, { 'name': 'P3', 'value': 50 } ] const groupedItems = [] items.forEach(item => { if (groupedItems.length > 0) { const lastArray = groupedItems[groupedItems.length - 1] const sumLastArray = lastArray.reduce((total, current) => current.value + total, 0) if (sumLastArray + item.value < 100) { lastArray.push(item) } else { groupedItems.push([item]) } } else { groupedItems.push([item]) } }) console.log(groupedItems)
Update an array using another array based on matched property value
I have two arrays like this let array1 = [{ 'id': 1, 'name': 'A' }, { 'id': 2, 'name': 'B' }, { 'id': 3, 'name': 'C' }] let array2 = [{ 'id': 1, 'name': 'x' }, { 'id': 2, 'name': 'y' }] I want to update array 1 with the array 2 object values which are matching based on id values. Result would be something like this. [{ 'id': 1, 'name': 'x' }, { 'id': 2, 'name': 'y' }, { 'id': 3, 'name': 'C' }] I have written something like this but not working . array1.forEach(item1 => { const itemFromArr2 = array2.find(item2 => item2.id== item1.id); if (itemFromArr2) { item1= itemFromArr2; } } ) Please suggest me how to do this.
You might want to check this one-liner out: array1.map(e => (e.name = array2.find(a => a.id == e.id)?.name || e.name, e)); Explanation: We are mapping over array1 and searching for the matching id in array2, if it is found (array2.find(a => a.id == e.id)?.name), we override the name property (e.name = ...), otherwise we keep it as it is (... || e.name). Small example: let array1 = [{ 'id': 1, 'name': 'A' }, { 'id': 2, 'name': 'B' }, { 'id': 3, 'name': 'C' }] let array2 = [{ 'id': 1, 'name': 'x' }, { 'id': 2, 'name': 'y' }] const newarray = array1.map(e => (e.name = array2.find(a => a.id == e.id)?.name || e.name, e)); console.log(newarray); Edit according to #Roster's comment, if you want to override the whole entry use this line: array1.map(e => (e = array2.find(a => a.id == e.id) || e, e)); Second example: let array1 = [{ 'id': 1, 'name': 'A' }, { 'id': 2, 'name': 'B' }, { 'id': 3, 'name': 'C' }] let array2 = [{ 'id': 1, 'name': 'x' }, { 'id': 2, 'name': 'y', 'otherproperty': 42 }] const newarray = array1.map(e => (e = array2.find(a => a.id == e.id) || e, e)); console.log(newarray);
Using a hashmap to update the array. The reason of hashmap is for performance. const array1 = [ { id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, ]; const array2 = [ { id: 1, name: "x" }, { id: 2, name: "y" }, ]; const hashMap2 = array2.reduce((carry, item) => { const { id } = item; if (!carry[id]) { carry[id] = item; } return carry; }, {}); const output = array1.map(item => { const newName = hashMap2[item.id]?.name; if (newName) { item.name = newName; } return item; }); console.log(output);
Here's a solution using Generics: const array1 = [{ 'id': 1, 'name': 'A' }, { 'id': 2, 'name': 'B' }, { 'id': 3, 'name': 'C' }] const array2 = [{ 'id': 1, 'name': 'x' }, { 'id': 2, 'name': 'y' }] function mergeArray<T>(arr1: T[], arr2: T[], identifier: keyof T): T[] { for(const oItem of arr2){ const itemInArr1 = arr1.find(item => item[identifier] === oItem[identifier]); if(itemInArr1){ for(const key in itemInArr1){ itemInArr1[key] = oItem[key]; } } else { arr1.push(oItem); } } return arr1; } console.log(mergeArray(array1,array2, 'id')); Playground This iterates over the itmes in array2and checks their existence inside array1 based on the identifier. Based on whether or not the item exists in array1, the item will be modified, or the item from array2 is pushed into array1.
Convert the update array to a Map, then iterate the target array with Array.map(), and merge it with the object of the same key in the upateMap if it exists: const fn = (predicate, target, update) => { const updateMap = new Map(update.map(o => [predicate(o), o])) return target.map(o => { const key = predicate(o) return updateMap.has(key) ? { ...o, ...updateMap.get(key)} : o }) } const array1 = [{"id":1,"name":"A"},{"id":2,"name":"B"},{"id":3,"name":"C"}] const array2 = [{"id":1,"name":"x"},{"id":2,"name":"y"}] const result = fn(o => o.id, array1, array2); console.log(result); With types (TS playground): const fn = <T>(predicate: (arg: T) => any, target: T[], update: T[]) => { const updateMap = new Map(update.map(o => [predicate(o), o])) return target.map(o => { const key = predicate(o) return updateMap.has(key) ? { ...o, ...updateMap.get(key)} : o }) }
Get possible combinations for array of objects by type and values
I need to create every combination of objects possible out of an array with types/identifiers. E.g.: Input: let inputArray = [ { 'type': '1', 'values': [ 'val1', 'val2' ] }, { 'type': '2', 'values': [ 'val1', 'val2', 'val3' ] } ] Output: let outputArray = [ [ { 'type': '1', 'value': 'val1' }, { 'type': '2', 'value': 'val1' } ], [ { 'type': '1', 'value': 'val1' }, { 'type': '2', 'value': 'val2' } ], [ { 'type': '1', 'value': 'val1' }, { 'type': '2', 'value': 'val3' } ], [ { 'type': '1', 'value': 'val2' }, { 'type': '2', 'value': 'val1' } ], [ { 'type': '1', 'value': 'val2' }, { 'type': '2', 'value': 'val2' } ], [ { 'type': '1', 'value': 'val2' }, { 'type': '2', 'value': 'val3' } ], ] And it needs to work dynamically depending on the number of types, and values inside the input array. I.E. the input array has another object, where that objects values array has 3 or 4 elements... What I tried: function doTheThing(inputArray) { let outputArray = [] for (let element of inputArray) { for (let value of element.values) { outputArray.push({ type: element.type, value: value }) } } } Which gives me every value possible, but doesn't give me every combination that I need...
You can create recursive function using simple for loop that exists when the value of n is larger then the length of the data or continues to increment it and then runs for loop. let data = [{"type":"1","values":["val1","val2"]},{"type":"2","values":["val1","val2","val3"]}] function f(data, n = 0, prev = []) { const result = []; if (n > data.length - 1) { result.push(prev.slice()) return result; } const { type, values } = data[n]; for (let i = 0; i < values.length; i++) { prev[n] = { type, value: values[i] } result.push(...f(data, n + 1, prev)) } return result; } const result = f(data); console.log(JSON.stringify(result, 0, 4)) Or you could use the reduce method instead of the for loop and make recursive call as long as the n < data.length - 1 let data = [{"type":"1","values":["val1","val2"]},{"type":"2","values":["val1","val2","val3"]}] function f(data, n = 0, prev = []) { return data[n].values.reduce((r, value) => { prev[n] = { type: data[n].type, value } if (n === data.length - 1) { r.push(prev.slice()) } if (n < data.length - 1) { r.push(...f(data, n + 1, prev)) } return r; }, []) } const result = f(data); console.log(JSON.stringify(result, 0, 4))
This function can help us to extend a permutation: let permute = (iterable, arr) => { let vals = [ ...iterable ]; let result = []; for (let v of arr) for (let items of vals) result.push([ v, ...items ]); return result; }; For example if we want to permute [ 'a', 'b' ], [ 1, 2 ], and [ '?', '!' ] we can do: let permute = (iterable, arr) => { let vals = [ ...iterable ]; let result = []; for (let v of arr) for (let items of vals) result.push([ v, ...items ]); return result; }; let v1 = permute([ [] ], [ 1, 2 ]); let v2 = permute(v1, [ 'a', 'b' ]); let v3 = permute(v2, [ '?', '!' ]); console.log(JSON.stringify({ v1, v2, v3 })); You can use this function with inputArray after performing a basic transformation on it (to achieve the type of result you posted in your question). For an entry in inputArray like { type: '1', values: [ 'v1', 'v2' ] }, you need to convert that to a form like: [ { type: '1', value: 'v1' }, { type: '1', value: 'v2' } ]. // Your original data let inputArray = [{'type': '1','values': ['val1','val2']},{'type': '2','values': ['val1','val2','val3']}]; // Function to format data as described let format = arr => arr.map(({ type, values }) => values.map(value => ({ type, value }))); // The same `permute` function as before let permute = (iterable, arr) => { let vals = [ ...iterable ]; let result = []; for (let v of arr) for (let items of vals) result.push([ v, ...items ]); return result; }; let formatted = format(inputArray); let v1 = permute([ [] ], formatted[0]); let v2 = permute(v1, formatted[1]); console.log(JSON.stringify(v2, null, 2));
summarize values of objects with same attribute name
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; }
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);