Use invert in an object with many entries - javascript

I am trying to reverse the keys/values.
So , I am trying something like:
var _ = require('underscore');
var myObj = [
{'name': 'Mike', 'number' : 'b1' , 'level' : 0 },
{'name': 'Tom', 'number' : 'b2' , 'level' : 0 }
];
Object.keys(myObj).forEach(function(k) {
_.invert(myObj[k]);
});
console.log(myObj);
So, I want to receive:
{'Mike': 'name', 'b1' : 'number' , '0' : level },
{'Tom': 'name', 'b2' : 'number' , '0' : level }
But the above gives me again the initial object.

This should do what you want:
var result = _.map(myObj, _.invert);

_.invert() doesn't alter the input object. Here's what you need:
var myObj = [
{'name': 'Mike', 'number' : 'b1' , 'level' : 0 },
{'name': 'Tom', 'number' : 'b2' , 'level' : 0 }
];
// ES6 one-liners with arrow function
// with array input
var invertedObj = myObj.map(k => { _.invert(myObj[k]) });
// with object input
var invertedObj = Object.keys(myObj).map(k => { _.invert(myObj[k]) });
// ES5
// with array input
var invertedObj = myObj.map(function(k) {
return _.invert(myObj[k]);
});
// with object input
var invertedObj = Object.keys(myObj).map(function(k) {
return _.invert(myObj[k]);
});

Lodash _.invert just return a new inverted object, so you need to update the array element with returned object. Also there is no need for Object.keys, since it's an array Array#forEach is enough.
var myObj = [
{
'name': 'Mike',
'number': 'b1',
'level': 0
}, {
'name': 'Tom',
'number': 'b2',
'level': 0
}
];
myObj.forEach(function(v, k) {
myObj[k] = _.invert(v);
});
console.log(myObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
If you want to generate new inverted array then use Array#map method.
var res = myObj.map(_.invert);
var myObj = [
{
'name': 'Mike',
'number': 'b1',
'level': 0
}, {
'name': 'Tom',
'number': 'b2',
'level': 0
}
];
var res = myObj.map(_.invert);
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>

First of all your myObj is an array, not an object so there is no need to perform Object.keys() on it.
var array = [
{'name': 'Mike', 'number' : 'b1' , 'level' : 0 },
{'name': 'Tom', 'number' : 'b2' , 'level' : 0 }
];
var myNewArray = array.map(object => {
var box = {};
Object.keys(object).forEach(key => {
box[object[key]] = key;
});
return box;
});

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

Remove array of objects from another array of objects

Assume we have the following arrays of objects to be compared based on property id:
a = [{'id':'1', 'name':'a1'}, {'id':'2', 'name':'a2'}, {'id':'3', 'name':'a3'}]
and
b = [[{'id':'2', 'name':'a2'}, ]
How can I subtract b from a? So that we have c = a - b which should be equal to [ {'id':'1', 'name':'a1'}, {'id':'3', 'name':'a3'}].
I have tried using this:
var c= a.filter(function(item) {
return !b.includes(item.id);
});
but still not working.
How about this solution? It assumes that 'b' is also an array so for each element of 'a' you check if there is a matching object in 'b'. If there is a matching object then return a false in the filter function so that that element is discarded.
var a = [{
'id': '1',
'name': 'a1'
}, {
'id': '2',
'name': 'a2'
}, {
'id': '3',
'name': 'a3'
}]
var b = [{
'id': '2',
'name': 'a2'
}]
var c = a.filter(function(objFromA) {
return !b.find(function(objFromB) {
return objFromA.id === objFromB.id
})
})
console.log(c)
Here is a nice one line answer :)
Basically, you can filter, as you were trying to do already. Then you can also filter b for each a element and if the length of the filtered b is zero, then you return true because that means the a element is unique to a.
var a = [{
'id': '1',
'name': 'a1'
}, {
'id': '2',
'name': 'a2'
}, {
'id': '3',
'name': 'a3'
}];
var b = [{
'id': '2',
'name': 'a2'
}];
c = a.filter( x => !b.filter( y => y.id === x.id).length);
console.log(c);
Easy with new ES6 Syntax
Second and Third way are more performant i guess....
a.filter(i => !b.filter(y => y.id === i.id).length); // One Way
a.filter(i => !b.find(f => f.id === i.id)); // Second Way
a.filter(i => b.findIndex(f => f.id === i.id)) // Third Way
First, you build just a map of the ids you want to delete.
Then, you filter your first array with it, like that:
var a = [{
'id': '1',
'name': 'a1'
}, {
'id': '2',
'name': 'a2'
}, {
'id': '3',
'name': 'a3'
}];
var b = [{
'id': '2',
'name': 'a2'
}];
var idsToDelete = b.map(function(elt) {return elt.id;});
var result = a.filter(function(elt) {return idsToDelete.indexOf(elt.id) === -1;});
console.log(result)

Javascript: Iterating over array of objects and forming new array

I want to convert the following array as follows:
[{'dog' : 2} {'cat' : 4} , {'chimp' : 3}] -> [{'text' : 'dog', 'size' : 2}, {'text' : 'cat', 'size' : 4}, {'text' : 'chimp', 'size' : 3}]
In pseudocode
for obj in array:
new_obj = {'text' : obj.key, 'size' : obj.val}
new_array.append(new_obj)
return new_array
let arr = [{
'dog': 2
}, {
'cat': 4
}, {
'chimp': 3
}];
let result = arr.map(a => {
let key = Object.keys(a)[0];
return {
text: key,
size: a[key]
}
});
console.log(result);
Well, it's not that hard, you can just use
Array.map and Object.keys()
const newArray = oldArray.map(item => {
const key = Object.keys(item)[0];
// Add some guard to handle empty objects..
return { text: key, size: item[key] };
});

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

separate an object into three parts

If I have an object like this:
var myObj = [
{'name': 'Mike', 'number' : 'b1' , 'level' : 0 },
{'name': 'Tom', 'number' : 'b2' , 'level' : 0 }
];
How can I create three separate objects like:
"names" :
{
'name1' : 'Mike',
'name2' : 'Tom',
..}
"numbers" :
{
'number1' : 'b1',
'number2' : 'b2',
..}
"levels" :
{
'level1' : '0',
'level2' : '0',
...
}
The alternative solution using Object.keys function, ES6 arrow function expression and predefined parts object:
var myObj = [{'name': 'Mike', 'number': 'b1', 'level': 0},{'name': 'Tom', 'number': 'b2', 'level': 0}],
parts = {'names': {}, 'numbers': {}, 'levels': {}};
myObj.forEach(function(obj, i){
Object.keys(obj).forEach((k) => (parts[k +"s"][k +(i+1)] = obj[k]));
});
console.log(JSON.stringify(parts, 0, 4));
The output:
{
"names": {
"name1": "Mike",
"name2": "Tom"
},
"numbers": {
"number1": "b1",
"number2": "b2"
},
"levels": {
"level1": 0,
"level2": 0
}
}
You could use the keys and the number of the element iteration as property.
var myObj = [{ 'name': 'Mike', 'number': 'b1', 'level': 0 }, { 'name': 'Tom', 'number': 'b2', 'level': 0 }],
result = {};
myObj.forEach(function (a, i) {
Object.keys(a).forEach(function (k) {
result[k + 's'] = result[k + 's'] || {};
result[k + 's'][k + (i + 1)] = a[k];
});
});
console.log(result);
Keys in JS objects must be unique.
The subsequent declaration of name : Tom overwrites your previous one.
Code :
var newObj = {};
newObj.names = {};
newObj.numbers = {};
newObj.levels = {};
for (var i in myObj) {
newObj.names.name = myObj[i].name;
newObj.numbers.number = myObj[i].number;
newObj.levels.level = myObj[i].level;
}
console.log(newObj);
Demo fiddle : https://jsfiddle.net/29pb33dj/
Updated answer with proper keys with index :
var newObj = {};
newObj.names = {};
newObj.numbers = {};
newObj.levels = {};
for (var i in myObj) {
newObj.names[i] = myObj[i].name;
newObj.numbers[i] = myObj[i].number;
newObj.levels[i] = myObj[i].level;
}
console.log(newObj);
Working demo : https://jsfiddle.net/29pb33dj/1/

Categories