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);
Suppose an array of objects:
arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
]
How can I randomly choose two Item of catId=1 and each Items from remaining category.
Required
arr2 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 3, 'name': 'G' }
]
This is a very simple and naïve approach like I explained on my comment, but it gets the job done:
var arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
];
//shuffles an array
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
//gets items with catId = 1, and shuffles them
var cat1 = shuffle(arr1.filter(function(item) {
return item.catId == 1;
}));
var otherCat = [];
//pushes items in the otherCat array that aren't catId = 1, and not duplicate category
for (var i = 0; i < arr1.length; i++) {
//if not catId = 1 and not already in array
if (arr1[i].catId != 1 && !find(arr1[i])) {
//get all items in this category, and shuffles them to get a random item
var thisCat = shuffle(arr1.filter(function(item) { return item.catId == arr1[i].catId; }))[0];
otherCat.push(thisCat);
}
}
//finds an item in otherCat array by catId
function find(item) {
return otherCat.find(function(i) {
return item.catId === i.catId;
});
}
var result = [];
result.push(cat1[0]);
result.push(cat1[1]);
//concatenate both arrays
Array.prototype.push.apply(result, otherCat);
console.log(JSON.stringify(result));
I coded it this way because it is very simple to see. You could in theory loop through the whole array once to get all catId = 1 and other catId into 2 different arrays (I know I am doing multiple passes to the array, but like I said, this is just so you can get the idea).
Another way of doing it (perhaps a little more complex) is by grouping the items by category, then looping thru each category and grabbing a random element (2 in the case of catId == 1):
var arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
];
//groups by a property
//https://stackoverflow.com/a/34890276/752527
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
//shuffles an array
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
var result = [];
var grouped = groupBy(arr1, 'catId');
var keys = Object.keys(grouped);
for (var i = 0; i < keys.length; i++) {
var group = grouped[keys[i]]
//if i have items in my group, shuffle them and grab 1, or 2 items from it
if (group && group.length > 0) {
var cat = shuffle(group);
result.push(cat[0]);
//adds the second item with catId ==1
if (group[0].catId === 1) {
result.push(cat[1]);
}
}
}
console.log(JSON.stringify(result));
If you want to return a list of n-number of items from a particular category and one from the remaining categories, then you could group the items by their catId and then map the entries to a randomized count (length) based on whether the current key is the chosen bias.
Edit: I added a bias to keep n-number of items from the category of choice.
const categories = [
{ 'catId': 1, 'name': 'A' }, { 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' }, { 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' }, { 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' }, { 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' }, { 'catId': 1, 'name': 'J' }
];
const sortFn = (
{ catId: ai, name: an },
{ catId: bi, name: bn }
) =>
(ai - bi) || an.localeCompare(bn);
const main = () => {
print(pickRand(categories, ({ catId }) => catId, 1, 2).sort(sortFn));
};
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
const grouped = (arr, keyFn) => arr.reduce((acc, item, idx) =>
({ ...acc, [keyFn(item)]: [...(acc[keyFn(item)] ?? []), idx] }), {});
const pickRand = (arr, keyFn, bias, count) =>
Object
.entries(grouped(arr, keyFn))
.flatMap(([key, idx]) =>
shuffle(idx).slice(0, bias == key ? count : 1)
.map(i => arr[i]));
const print = (arr) => console.log(arr.map(x => JSON.stringify(x)).join('\n'));
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
I have a an array containing some values. I need to populate values dynamically using the array elements.
Below is the master array.
list = [{name: 'm1'}, {name: 'm2'},{name: 'm3'},{name: 'm4'},{name: 'm5'},]
I have JSON called demo.json
demo: {
listCard = {
'x': 0,
'y': 1,
'name': ''
},
layout: [
{
'col': '3',
'row': '3'
'grids': []
}
]
};
The result should be -
demo: {
listCard = {
'x': 0,
'y': 1,
'name': ''
},
layout: [
{
'col': '3',
'row': '3'
'grids': [
{
'x': 0,
'y': 1,
'name': 'm1'
},
{
'x': 0,
'y': 1,
'name': 'm2'
}
]
},
{
'col': '3',
'row': '3'
'grids': [
{
'x': 0,
'y': 1,
'name': 'm3'
},
{
'x': 0,
'y': 1,
'name': 'm4'
}
]
},
{
'col': '3',
'row': '3'
'grids': [
{
'x': 0,
'y': 1,
'name': 'm5'
}
]
}
]
};
Basically the result should be like demo.listcard.name should iterate list.length times & get each value. After that layout.grid should be assigned with the whole object of listcard with 2 objects.
Below is the method I used but I am constantly failing.
let listcard = demo.listcard; //here I get listcard object
const layout = demo.layout; // layout object
const noOfScreens = (arr, size) => arr.reduce((acc, e, i) => (i % size ? acc[acc.length - 1].push(e) : acc.push([e]), acc), []);
const screens = noOfScreens(list, 2); // master array is split into arrays of size 2.
for (let i = 0; i < screens.length; i++) {
layout[0].grids.push(layout);
}
I am stuck here. Please help me out
Here is what you want:
const demo = {
listCard: {
'x': 0,
'y': 1,
'name': ''
},
layout: []
};
const list = [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }, { name: 'm4' }, { name: 'm5' }]
for (var i = 0; i < list.length; i++) {
if (i % 2 == 0) {
demo.layout.push({
'col': '3',
'row': '3',
'grids': [{
'x': 0,
'y': 1,
'name': list[i].name
}]
})
} else {
demo.layout[Math.floor(i / 2)].grids.push({
'x': 0,
'y': 1,
'name': list[i].name
});
}
}
console.log(demo);
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 made a function in order to find an element in a tree object. My function works, but sometimes the function don't find the value and stop before looking all the tree.
I can't explain why it works sometimes and sometimes not.
Here is my fiddle : http://jsfiddle.net/3cdwA/2/
When you click on categories like "Sciences" you can see that it works. But if you click on "Bandes-Dessinées" it should display "Comics" but it doesn't.
Here is my recursive function :
function getChildrenFromCurrentFolder(tree, targetFolder) {
console.log(tree);
// Find recursivly all direct children of targeted folder
if (targetFolder == tree.id) {
return tree.folders;
} else if (tree.folders.length > 0) {
var folders = [];
for (i = 0; folders.length == 0 && i < tree.folders.length; i++) {
folders = getChildrenFromCurrentFolder(tree.folders[i], targetFolder);
}
return folders;
}
return [];
}
here is my test tree :
tree = {
'id': 1,
'name': 'Mes Bookmarks',
'folders': [
{
'id': 2,
'name': 'Sciences',
'folders': [
{
'id': 3,
'name': 'Biologie',
'folders': [
{
'id': 12,
'name': 'Neurologie',
'folders': []
}
]
},
{
'id': 4,
'name': 'Astrophysique',
'folders': [
{
'id': 8,
'name': 'Cosmologie',
'folders': [
{
'id': 10,
'name': 'Système solaire',
'folders': []
}
]
},
{
'id': 9,
'name': 'Trous noirs',
'folders': []
}
]
},
{
'id': 5,
'name': 'Mathématiques',
'folders': []
}
]
},
{
'id': 6,
'name': 'Actualités',
'folders': [
{
'id': 11,
'name': 'Monde',
'folders': []
}
]
},
{
'id': 7,
'name': 'Bandes-dessinées',
'folders': [
{
'id': 13,
'name': 'Comics',
'folders': []
}
]
}
]
};
It's a simple, but common mistake. You forgot to declare your loop variables and that's trouble when doing recursion as you're creating a global that's being reused:
function displayFolders() {
...
for (var i = folders.length-1; i >= 0; i--)
... --^--
}
function getChildrenFromCurrentFolder(tree, targetFolder) {
...
for (var i = 0; folders.length == 0 && i < tree.folders.length; i++) {
... --^--
}
function getBreadcrumb(tree, targetFolder, breadcrumb) {
...
for (var i = 0; i < tree['folders'].length; i++)
... --^--
}
I'm not sure all the other logic is correct, but this definitely changes the behavior.
http://jsfiddle.net/3cdwA/4/