Alternative way to map object key values - javascript

What is the alternative/best way to map through an object key-values without using forEach?
This is what I have currently:
sortObject(source) {
const object = Object.assign({}, source);
Object.keys(object).forEach(key => {
// Sort array by priority (the higher the number, the higher the priority)
object[key] = object[key].sort((a, b) => b.priority - a.priority);
// Limit the array length to the 5
if (object[key].length > 5) {
object[key] = object[key].slice(0, 5);
}
});
return object;
}
source is an object like:
{
base: [
{ id: 1, priority: 5 },
{ id: 2, priority: 10 },
{ id: 3, priority: 1 },
{ id: 4, priority: 5 },
{ id: 5, priority: 15 }
],
extra: [
{ id: 1, priority: 1 },
{ id: 2, priority: 5 },
{ id: 3, priority: 10 }
],
additional: [
{ id: 1, priority: 5 },
{ id: 2, priority: 10 },
{ id: 3, priority: 10 },
{ id: 4, priority: 15 },
{ id: 5, priority: 1 },
{ id: 6, priority: 29 },
{ id: 7, priority: 100 },
{ id: 8, priority: 100 },
{ id: 9, priority: 5 }
]
}
The final output is like:
{
base: [
{ id: 5, priority: 15 },
{ id: 2, priority: 10 },
{ id: 1, priority: 5 },
{ id: 4, priority: 5 },
{ id: 3, priority: 1 }
],
extra: [
{ id: 3, priority: 10 },
{ id: 2, priority: 5 },
{ id: 1, priority: 1 }
],
additional: [
{ id: 7, priority: 100 },
{ id: 8, priority: 100 },
{ id: 6, priority: 29 },
{ id: 4, priority: 15 },
{ id: 2, priority: 10 }
]
}
is there a better/cleaner way to do this?

I'd look at Object.entries (new as of ES2017 but polyfillable) and destructuring in a for-of loop.
If nothing else has access to the arrays on source (which I tend to assume, since sort works in-place, so the code already modifies the original array; but at the same time, source is coming in from outside, so...):
sortObject(source) {
const object = Object.assign({}, source);
for (const [key, array] of Object.entries(object)) {
array.sort((a, b) => b.priority - a.priority);
array.length = Math.min(array.length, 5);
}
return object;
}
If something else has access to those arrays and you shouldn't modify them other than sorting them, then you'll need your original length check and slice:
sortObject(source) {
const object = Object.assign({}, source);
for (const [key, array] of Object.entries(object)) {
array.sort((a, b) => b.priority - a.priority);
if (array.length > 5) {
object[key] = array.slice(0, 5);
}
}
return object;
}
Your code suggests you may not have realized sort works in-place since you were assigning the result back to the original location. If so and you didn't intend to sort the arrays in-place, you'll need to copy the arrays before sorting:
sortObject(source) {
const object = Object.assign({}, source);
for (const [key, array] of Object.entries(object)) {
object[key] = array = array.slice();
array.sort((a, b) => b.priority - a.priority);
array.length = Math.min(array.length, 5);
}
return object;
}
You could replace
object[key] = array = array.slice();
array.sort((a, b) => b.priority - a.priority);
with
object[key] = array = Array.from(array).sort((a, b) => b.priority - a.priority);
if you like, but that'll use an iterator, which is more overhead than slice.

You can also use map for looping the object entries and splice for cutting off the array length:
const getSortedObj = (source) => {
let obj = Object.assign({}, source);
Object.entries(obj).map( entry => {
let [key, arr] = entry;
arr.sort((a, b) => b.priority - a.priority).splice(Math.min(arr.length, 5));
return entry;
});
return obj;
};

Related

How to loop the object inside key's object in react js [duplicate]

How would I find all values by specific key in a deep nested object?
For example, if I have an object like this:
const myObj = {
id: 1,
children: [
{
id: 2,
children: [
{
id: 3
}
]
},
{
id: 4,
children: [
{
id: 5,
children: [
{
id: 6,
children: [
{
id: 7,
}
]
}
]
}
]
},
]
}
How would I get an array of all values throughout all nests of this obj by the key of id.
Note: children is a consistent name, and id's won't exist outside of a children object.
So from the obj, I would like to produce an array like this:
const idArray = [1, 2, 3, 4, 5, 6, 7]
This is a bit late but for anyone else finding this, here is a clean, generic recursive function:
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object')
? acc.concat(findAllByKey(value, keyToFind))
: acc
, [])
}
// USAGE
findAllByKey(myObj, 'id')
You could make a recursive function like this:
idArray = []
function func(obj) {
idArray.push(obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
Snippet for your sample:
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
}
idArray = []
function func(obj) {
idArray.push(obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
func(myObj)
console.log(idArray)
I found steve's answer to be most suited for my needs in extrapolating this out and creating a general recursive function. That said, I encountered issues when dealing with nulls and undefined values, so I extended the condition to accommodate for this. This approach uses:
Array.reduce() - It uses an accumulator function which appends the value's onto the result array. It also splits each object into it's key:value pair which allows you to take the following steps:
Have you've found the key? If so, add it to the array;
If not, have I found an object with values? If so, the key is possibly within there. Keep digging by calling the function on this object and append the result onto the result array; and
Finally, if this is not an object, return the result array unchanged.
Hope it helps!
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
}
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object' && value)
? acc.concat(findAllByKey(value, keyToFind))
: acc
, []) || [];
}
const ids = findAllByKey(myObj, 'id');
console.log(ids)
You can make a generic recursive function that works with any property and any object.
This uses Object.entries(), Object.keys(), Array.reduce(), Array.isArray(), Array.map() and Array.flat().
The stopping condition is when the object passed in is empty:
const myObj = {
id: 1,
anyProp: [{
id: 2,
thing: { a: 1, id: 10 },
children: [{ id: 3 }]
}, {
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{ id: 7 }]
}]
}]
}]
};
const getValues = prop => obj => {
if (!Object.keys(obj).length) { return []; }
return Object.entries(obj).reduce((acc, [key, val]) => {
if (key === prop) {
acc.push(val);
} else {
acc.push(Array.isArray(val) ? val.map(getIds).flat() : getIds(val));
}
return acc.flat();
}, []);
}
const getIds = getValues('id');
console.log(getIds(myObj));
Note: children is a consistent name, and id's wont exist outside
of a children object.
So from the obj, I would like to produce an array like this:
const idArray = [1, 2, 3, 4, 5, 6, 7]
Given that the question does not contain any restrictions on how the output is derived from the input and that the input is consistent, where the value of property "id" is a digit and id property is defined only within "children" property, save for case of the first "id" in the object, the input JavaScript plain object can be converted to a JSON string using JSON.stringify(), RegExp /"id":\d+/g matches the "id" property and one or more digit characters following the property name, which is then mapped to .match() the digit portion of the previous match using Regexp \d+ and convert the array value to a JavaScript number using addition operator +
const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};
let res = JSON.stringify(myObject).match(/"id":\d+/g).map(m => +m.match(/\d+/));
console.log(res);
JSON.stringify() replacer function can alternatively be used to .push() the value of every "id" property name within the object to an array
const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};
const getPropValues = (o, prop) =>
(res => (JSON.stringify(o, (key, value) =>
(key === prop && res.push(value), value)), res))([]);
let res = getPropValues(myObject, "id");
console.log(res);
Since the property values of the input to be matched are digits, all the JavaScript object can be converted to a string and RegExp \D can be used to replace all characters that are not digits, spread resulting string to array, and .map() digits to JavaScript numbers
let res = [...JSON.stringify(myObj).replace(/\D/g,"")].map(Number)
Using recursion.
const myObj = { id: 1, children: [ { id: 2, children: [ { id: 3 } ] }, { id: 4, children: [ { id: 5, children: [ { id: 6, children: [ { id: 7, } ] } ] } ] }, ]},
loop = (array, key, obj) => {
if (!obj.children) return;
obj.children.forEach(c => {
if (c[key]) array.push(c[key]); // is not present, skip!
loop(array, key, c);
});
},
arr = myObj["id"] ? [myObj["id"]] : [];
loop(arr, "id", myObj);
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can make a recursive function with Object.entries like so:
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
};
function findIds(obj) {
const entries = Object.entries(obj);
let result = entries.map(e => {
if (e[0] == "children") {
return e[1].map(child => findIds(child));
} else {
return e[1];
}
});
function flatten(arr, flat = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, flat);
} else {
flat.push(value);
}
}
return flat;
}
return flatten(result);
}
var ids = findIds(myObj);
console.log(ids);
Flattening function from this answer
ES5 syntax:
var myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
};
function findIds(obj) {
const entries = Object.entries(obj);
let result = entries.map(function(e) {
if (e[0] == "children") {
return e[1].map(function(child) {
return findIds(child)
});
} else {
return e[1];
}
});
function flatten(arr, flat = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, flat);
} else {
flat.push(value);
}
}
return flat;
}
return flatten(result);
}
var ids = findIds(myObj);
console.log(ids);
let str = JSON.stringify(myObj);
let array = str.match(/\d+/g).map(v => v * 1);
console.log(array); // [1, 2, 3, 4, 5, 6, 7]
We use object-scan for a lot of our data processing needs now. It makes the code much more maintainable, but does take a moment to wrap your head around. Here is how you could use it to answer your question
// const objectScan = require('object-scan');
const find = (data, needle) => objectScan([needle], { rtn: 'value' })(data);
const myObj = { id: 1, children: [{ id: 2, children: [ { id: 3 } ] }, { id: 4, children: [ { id: 5, children: [ { id: 6, children: [ { id: 7 } ] } ] } ] }] };
console.log(find(myObj, '**.id'));
// => [ 7, 6, 5, 4, 3, 2, 1 ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
import {flattenDeep} from 'lodash';
/**
* Extracts all values from an object (also nested objects)
* into a single array
*
* #param obj
* #returns
*
* #example
* const test = {
* alpha: 'foo',
* beta: {
* gamma: 'bar',
* lambda: 'baz'
* }
* }
*
* objectFlatten(test) // ['foo', 'bar', 'baz']
*/
export function objectFlatten(obj: {}) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(objectFlatten(value));
} else {
result.push(value);
}
}
return flattenDeep(result);
}
Below solution is generic which will return all values by matching nested keys as well e.g for below json object
{
"a":1,
"b":{
"a":{
"a":"red"
}
},
"c":{
"d":2
}
}
to find all values matching key "a" output should be return
[1,{a:"red"},"red"]
const findkey = (obj, key) => {
let arr = [];
if (isPrimitive(obj)) return obj;
for (let [k, val] of Object.entries(obj)) {
if (k === key) arr.push(val);
if (!isPrimitive(val)) arr = [...arr, ...findkey(val, key)];
}
return arr;
};
const isPrimitive = (val) => {
return val !== Object(val);
};

How can add properties to my objects based on the duplicates inside the array?

I have this array of objects
let arr = [
{
id: 1,
},
{
id: 1,
},
{
id: 2,
},
{
id: 1,
},
{
id:4,
},
{
id: 3,
},
{
id:4,
}
]
i need to find and change every object in the array based on condition.
So if there are duplicates in the array i need to set on my objects 100 except last duplicate where i should have 200.
If i don't have any duplicates than i should have again 200
So the output shpuld be
let arr = [
{
id: 1,
number: 100
},
{
id: 1,
number: 100
},
{
id: 2,
number: 200
},
{
id: 1,
number: 200
},
{
id:4,
number: 100
},
{
id: 3,
number: 200
},
{
id:4,
number: 200
}
]
so id 1 has duplicates.
That is why the fiurst occurences are set with number:100 and the last one i set with number:200.
Id 2 has number 200 because there are no duplicates and it is first occurance in the list.
what i tried
I got stuck at
for(let item of arr) {
for(let item2 of arr) {
if(item.id === item2.id) {
item.number = 100;
} else {
item.number = 200;
}
}
}
You can simply iterate through the array in reverse and track which ids you've seen, here using a Set.
const arr = [{ id: 1, }, { id: 1, }, { id: 2, }, { id: 1, }, { id: 4, }, { id: 3, }, { id: 4, }]
let i = arr.length;
const seen = new Set();
while (i--) {
arr[i].number = seen.has(arr[i].id) ? 100 : 200;
seen.add(arr[i].id)
}
console.log(arr)
You can use array.map() to iterate over your array. I think it can provide a nice and concise solution:
const result = arr.map((item, index) => {
const duplicate = arr.filter((_, indx) => indx > index).some((i) => i.id === item.id);
return { ...item, number: duplicate ? 100 : 200 }
});
console.log(result);
We can simply achieve it via Array.map() along with Array.indexOf() & Array.lastIndexOf() methods.
Working Demo :
// Input array
let arr = [{
id: 1,
}, {
id: 1,
}, {
id: 2,
}, {
id: 1,
}, {
id:4,
}, {
id: 3,
}, {
id:4,
}];
// Getting ID's from each object and create a seperate array
let idArr = arr.map(function(item) { return item.id });
// Iterating through the id's array and assigning number property to an original array as per the requirement.
idArr.forEach((item, index) => {
if (idArr.indexOf(item) === idArr.lastIndexOf(item)) {
arr[index].number = 200;
} else {
arr[index].number = 100;
arr[idArr.lastIndexOf(item)].number = 200;
}
});
// Result
console.log(arr);

Flattern object in nested array of arrays Javascript

I have an array of arrays, which contain objects, would like to get the value of a certain key and return it as a big array, have tried a nested map but it returns multiple array's rather than a single array.
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
)
console.log(subItemIDs);
Expected output
[1, 2, 3, 4, 5, 6]
Actual output
[ [1,2,3], [4,5,6] ]
You can use arrays.flat(). I can provide more specific code once output is mentioned in the question
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
You could take Array#flatMap to get a flat array from nested arrays.
const
items = [{ id: 1, sub_items: [{ id: 1 }, { id: 2 }, { id: 3 }] }, { id: 2, sub_items: [{ id: 4 }, { id: 5 }, { id: 6 }] }],
subItemIDs = items.flatMap(({ sub_items }) => sub_items.map(({ id }) => id));
console.log(subItemIDs);
Achieved this with:
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = [].concat(...items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
))
console.log(subItemIDs);
Sometimes, the obvious is the easiest:
Given a data structure that looks like this
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
A trivial function like this
function extract_item_ids( items ) {
const ids = [];
for ( const item of items ) {
for ( const {id} of sub_items ) {
ids.push(id);
}
}
return ids;
}
should do the trick. If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = [];
const pending = items;
while ( pending.length > 0 ) {
const item = pending.pop();
ids.push(item.id);
pending.push(...( item.sub_items || [] ) );
}
return ids;
}
And collecting the set of discrete item IDs is no more difficult:
If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = new Set();
const pending = [...items];
while ( pending.length > 0 ) {
const item = pending.pop();
ids.add(item.id);
pending.push(...( item.sub_items || [] ) );
}
return Array.from(ids);
}
As is the case with most things JavaScript, you have several options. Some are more efficient than others, others have a certain stylistic purity, others might better speak to your fancy. Here are a few:
Array.flat
With array flat you can take your original code and have the JS Engine flatten the array down to a one-dimensional array. Simply append .flat() onto the end of your map.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
).flat()
console.log(subItemIds);
Array.reduce
Another method is to use reduce to iterate over the object and build an accumulation array using Array.reduce. In the example below, when pushing onto the array, the spread operator (...) is used to break the array into elements.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.reduce((arr,item) => (
arr.push(...item.sub_items.map((subItem) => subItem.id)), arr
),[])
console.log(subItemIds);
Other
Other answers here make use of custom functions or Array.flatMap, which should be explored as they could lead to more readable and efficient code, depending on the program's needs.

Sort array and SUM item counts [duplicate]

I have javascript array object as below. My need is to sum value base on seach id in the array object.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
],
For example sum of value for id 1 is 10 + 30 + 25 + 20 = 85 , It may be something link linq but I'm not sure in javascript. Thanks for all answers.
You can use a combination of filter and reduce to get the result you want:
sumOfId = (id) => array.filter(i => i.id === id).reduce((a, b) => a + b.val, 0);
Usage:
const sumOf1 = sumOfId(1); //85
Reading material:
Array.prototype.filter
Array.prototype.reduce
A way to do it with a traditional for loop
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sums = {};
for (var i = 0; i < array.length; i++) {
var obj = array[i];
sums[obj.id] = sums[obj.id] === undefined ? 0 : sums[obj.id];
sums[obj.id] += parseInt(obj.val);
}
console.log(sums);
running example
You can use reduce() and findIndex()
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
let res = array.reduce((ac,a) => {
let ind = ac.findIndex(x => x.id === a.id);
ind === -1 ? ac.push(a) : ac[ind].val += a.val;
return ac;
},[])
console.log(res);
JS noob here ... I guess something like this should be here too :-)
let newArray = {}
array.forEach((e) => {
!newArray[e.id] && (newArray[e.id] = 0);
newArray[e.id] += e.val;
});
You can loop on the array and check the ids.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sum = 0;
var id = 1;
$.each(array, function(index, object){
if (object.id == id) {
sum += object.val;
}
});
console.log(sum);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Using Array#reduce and Map you can get the sum for each id like so. This also uses destructuring to have quicker access to properties.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
console.log(res.get(1));
console.log(res.get(2));
If you wanted to output all the sums, then you need to use Array#from
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
);
console.log(res);
If the format should be similar as to your original structure, you need to add a Array#map afterwards to transform it.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
).map(([id,sum])=>({id,sum}));
console.log(res);
You could take GroupBy from linq.js with a summing function.
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }],
result = Enumerable
.From(array)
.GroupBy(null, null, "{ id: $.id, sum: $$.Sum('$.val') }", "$.id")
.ToArray();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.js"></script>
Here is another option, introducing an Array.prototype.sum helper:
Array.prototype.sum = function (init = 0, fn = obj => obj) {
if (typeof init === 'function') {
fn = init;
init = 0;
}
return this.reduce(
(acc, ...fnArgs) => acc + fn(...fnArgs),
init
);
};
// .sum usage examples
console.log(
// sum simple values
[1, 2, 3].sum(),
// sum simple values with initial value
[1, 2, 3].sum(10),
// sum objects
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(obj => obj.a),
// sum objects with initial value
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(10, obj => obj.a),
// sum custom combinations
[{ amount: 1, price: 2 }, { amount: 3, price: 4 }]
.sum(product => product.amount * product.price)
);
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }];
// solutions
console.log(
array.filter(obj => obj.id === 1).sum(obj => obj.val),
array.filter(({id}) => id === 1).sum(({val}) => val),
array.sum(({id, val}) => id === 1 ? val : 0)
);
references:
Array.prototype.reduce
Array.prototype.filter
Arrow functions used in sum(obj => obj.val)
Object destructing assignment used in ({id}) => id === 1
Rest parameters used in (acc, ...fnArgs) => acc + fn(...fnArgs)
Conditional (ternary) operator used in id === 1 ? val : 0

Sum data from two array of objects

I have two array of objects, and I want to sum the object that have a same key (in this case, id), and if there is no match key, then just create a new one.. I'm sorry if I am not explaining it clearly, I am new to JavaScript/Array/Object thing...
var dataOne = [ { id:"1", total: 10, win: 5 }, { id:"2", total: 5, win: 1 }, { id:"3", total: 5, win: 2 } ]
and
var dataTwo = [ { id:"1", total: 5, win: 2 }, { id:"2", total: 2, win: 3 }, { id:"5", total: 5, win: 4 } ]
Expected result:
var combinedData = [ { id:"1", total: 15, win: 7 }, { id:"2", total: 7, win: 4 }, { id:"3", total: 5, win: 2 }, { id:"5", total: 5, win: 4 } ]
I have tried to use the solution from Sum all data in array of objects into new array of objects
but, apparently the type of data is kind of different
So, I tried to use this method from Javascript - Sum of two object with same properties
function sumObjectsByKey(...objs) {
for (var prop in n) {
if (acc.hasOwnProperty(prop)) acc[prop] += n[prop];
else acc[prop] = n[prop];
}
return acc;
}
and
var combinedData = sumObjectsByKey(dataOne, dataTwo);
But apparently, that method won't work for an array of objects.
I get
{0: "0[object Object][object Object]", 1: "0[object Object][object Object]", 2: "0[object Object][object Object]"}
as a result.
Combine both arrays by spread into Array.concat(). Reduce the items into a Map, while combining the current item, with the item that already exists in the Map. Convert back to array by spreading the Map.values() iterator:
// utility function to sum to object values (without the id)
const sumItem = ({ id, ...a }, b) => ({
id,
...Object.keys(a)
.reduce((r, k) => ({ ...r, [k]: a[k] + b[k] }), {})
});
const sumObjectsByKey = (...arrs) => [...
[].concat(...arrs) // combine the arrays
.reduce((m, o) => // retuce the combined arrays to a Map
m.set(o.id, // if add the item to the Map
m.has(o.id) ? sumItem(m.get(o.id), o) : { ...o } // if the item exists in Map, sum the current item with the one in the Map. If not, add a clone of the current item to the Map
)
, new Map).values()]
const dataOne = [ { id:"1", total: 10, win: 5 }, { id:"2", total: 5, win: 1 }, { id:"3", total: 5, win: 2 } ]
const dataTwo = [ { id:"1", total: 5, win: 2 }, { id:"2", total: 2, win: 3 }, { id:"5", total: 5, win: 4 } ]
const result = sumObjectsByKey(dataOne, dataTwo)
console.log(result)
I'd suggest Array.reduce here. In the body of the reduction function, if the id isn't in the result accumulator, add it, then increment all of the values for each property in the object.
var dataOne = [ { id:"1", total: 10, win: 5 }, { id:"2", total: 5, win: 1 }, { id:"3", total: 5, win: 2 } ]
var dataTwo = [ { id:"1", total: 5, win: 2 }, { id:"2", total: 2, win: 3 }, { id:"5", total: 5, win: 4 } ]
var sumObjectsByKey = (...objs) =>
Object.values(objs.reduce((a, e) => {
a[e.id] = a[e.id] || {id: e.id};
for (const k in e) {
if (k !== "id") {
a[e.id][k] = a[e.id][k] ? a[e.id][k] + e[k] : e[k];
}
}
return a;
}, {}))
;
console.log(sumObjectsByKey(...dataOne, ...dataTwo));

Categories

Resources