how to group by and total sum array of object in javascript? - javascript

After I use reduce() to group the objects in an array by month-year:
let groupByMonth;
if (newlistTaskEvaluation) {
groupDataByMonth = newlistTaskEvaluation.reduce((groups, item) => {
groups[item.time] = [...groups[item.time] || [], item];
return groups;
}, {});
}
I have an array of event objects formatted by group month-year as follows:
groupByMonth = {
'7-2020': [ //july
{
time: "7-2020",
task: [
{ code: "p1", value: 123 },
{ code: "p2", value: 234 },
]
},
{
time: "7-2020",
task: [
{ code: "p1", value: 345 },
{ code: "p2", value: 456 },
]
},
],
'8-2020': [ //august
{
time: "8-2020",
task: [
{ code: "p1", value: 567 },
{ code: "p2", value: 678 },
]
},
{
time: "8-2020",
task: [
{ code: "p1", value: 789 },
{ code: "p2", value: 999 },
]
},
]
}
How to group object of Array by key 'code', time and total sum by value?
Expected result:
output = [
{
time: "7-2020", //total month 7-2020
task: [
{ code: "p1", valueSum: 468 }, // 123 + 345
{ code: "p2", valueSum: 690 }, // 234 +456
]
},
{
time: "8-2020",
task: [
{ code: "p1", valueSum: 1356 }, // 567 + 789
{ code: "p2", valueSum: 1677 }, // 999 +678
]
}
]
Please help me.

You could try something like this
const output = Object.entries(groupByMonth).map(([time, datapoints]) => {
const codes = {}
const allTasks = datapoints.flatMap(point => point.task)
for (const { code, value } of allTasks) {
codes[code] = (codes[code] || 0) + value
}
return {
time,
tasks: Object.entries(codes).map(([code, value]) => ({ code, value }))
}
}
Though one downside is that the time complexity isn't perfect because of the structure of the data

const groupByMonth = {
"7-2020": [
//july
{
time: "7-2020",
task: [
{ code: "p1", value: 123 },
{ code: "p2", value: 234 },
],
},
{
time: "7-2020",
task: [
{ code: "p1", value: 345 },
{ code: "p2", value: 456 },
],
},
],
"8-2020": [
//august
{
time: "8-2020",
task: [
{ code: "p1", value: 567 },
{ code: "p2", value: 678 },
],
},
{
time: "8-2020",
task: [
{ code: "p1", value: 789 },
{ code: "p2", value: 999 },
],
},
],
};
const result = Object.keys(groupByMonth).reduce((arr, key)=>{
const task = (groupByMonth[key]).reduce((acc, rec) => {
return [...acc, rec.task]
}, [])
const newObj = {time: key, task}
return [...arr, newObj]
}, [])
console.log(JSON.stringify(result, 2, 2))
You can get object's keys and when you iterate over them find out inner objects, then, in every object use another oner reduce to get tasks in that data-type what you need.
for iterations i suddenly use reduce() method as a swiss-knife to working with arrays.

Related

Create new array from existing array with unique joined values from existing array

Does anyone know how I can create a new array from existing array with unique joined values from existing array?
const originalArray = [
[
{ value: 'red', id: 99 },
{ value: 'blue', id: 100 },
],
[
{ value: 'small', id: 101 },
{ value: 'medium', id: 102 },
],
[
{ value: 'modern', id: 103 },
{ value: 'classic', id: 104 },
],
];
//
//
const newArrayBasedOnOriginalArray = [
{ value: 'red/small/modern' },
{ value: 'red/small/classic' },
{ value: 'red/medium/modern' },
{ value: 'red/medium/classic' },
{ value: 'blue/small/modern' },
{ value: 'blue/small/classic' },
{ value: 'blue/medium/modern' },
{ value: 'blue/medium/classic' },
];
I calculated that the length of the new array should always be as following:
// length of new array
const lengthOfNewArray = originalArray
.map((value) => {
return value.length;
})
.reduce((current, old) => {
return current * old;
});
//
//
console.log('length of new array:', lengthOfNewArray); // 8
You can do it recursively
const originalArray = [
[
{ value: 'red', id: 99 },
{ value: 'blue', id: 100 },
],
[
{ value: 'small', id: 101 },
{ value: 'medium', id: 102 },
],
[
{ value: 'modern', id: 103 },
{ value: 'classic', id: 104 },
],
];
const getPossibleCombination = (currentValue, arraysRemaining) => {
if(arraysRemaining.length === 0) return currentValue
const values = []
const firstArray = arraysRemaining[0]
firstArray.forEach(({value}) => {
values.push(getPossibleCombination(`${currentValue}/${value}`, arraysRemaining.slice(1, arraysRemaining.length)))
})
return values.flat()
}
const values = getPossibleCombination('', originalArray)
console.log(values)
In this case, you do not necessarily need recursion. Array.reduce() greatly does the job:
const originalArray = [
[
{ value: 'red', id: 99 },
{ value: 'blue', id: 100 },
],
[
{ value: 'small', id: 101 },
{ value: 'medium', id: 102 },
],
[
{ value: 'modern', id: 103 },
{ value: 'classic', id: 104 },
],
];
const newArray = originalArray
.map(elem => elem.map(({value}) => value))
.reduce((acc, cur) => acc.flatMap(seq => cur.map(part => `${seq}/${part}`)))
.map(elem => ({value: elem}))
console.log(newArray)
Aside from the initial and final map(), used to simplify the input objects, what I am doing is continuously combining the accumulator with the next sub-array.
For each object in the sub-array I duplicate every object in the accumulator, using the nested map(). flatMap() is used to keep the accumulator flat, with a simple map() the accumulator depth would increase every time we visit a new sub-array.
First of all if values in each of your arrays is unique then the concatenated values will be unique as well. After you make sure values are unique you can use this code to create combinations of strings:
const newArrayBasedOnOriginalArray = originalArray.reduce(
(acc, el) =>
el.flatMap(({ value }) =>
acc.length ? acc.map((str) => str + "/" + value) : value
),
[]
).map(value=>({value});

Merge nested Array with same key javascript

I have to organise the array, Getting a response array like this
let data = [
{
date: "2022-07-01T07:26:22",
tips: [
{ id: 1 }
]
},
{
date: "2022-07-01T12:05:55",
tips: [
{ id: 1 }
]
},
{
date: "2022-07-05T13:09:16",
tips: [
{ id: 1 }
]
},
{
date: "2022-07-05T13:31:07",
tips: [
{ id: 1 }
]
},
{
date: "2022-06-29T09:21:26",
tips: [
{ id: 1 }
]
}
]
The desired output :
let data = [
{
'2022-07-01': [
{
tips: [
{ id: 1 }
]
},
{
tips: [
{ id: 1 }
]
},
]
},
{
'2022-07-05': [
{
tips: [
{ id: 1 }
]
},
{
tips: [
{ id: 1 }
]
},
]
},
{
'2022-06-29': [
{
tips: [
{ id: 1 }
]
},
]
}
]
I need to get data with the same key in the above array format. I have tried different ways to achieve this but have not gotten the proper result Which is the best way to get the desired output.
Thanks in advance!!!
Here is a solution using reduce. Grouping first using reduce and after that getting the values of the object using Object.values
let data = [ { date: "2022-07-01T07:26:22", tips: [ { id: 1 } ] }, { date: "2022-07-01T12:05:55", tips: [ { id: 1 } ] }, { date: "2022-07-05T13:09:16", tips: [ { id: 1 } ] }, { date: "2022-07-05T13:31:07", tips: [ { id: 1 } ] }, { date: "2022-06-29T09:21:26", tips: [ { id: 1 } ] }]
let res = Object.values(data.reduce((acc,{date,tips})=>{
let key = date.substring(0,10)
acc[key] = acc[key] || {[key]:[]}
acc[key][key].push({tips})
return acc
},{}))
console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to get the sum of specific values in an array contained within another array

I have an object with an array which contains another array. I need to add up the values from these child arrays where the name matches each other.
let arr = {
expenses: [
{
id: 11,
freqs: [
{ name: "day", value: 100 },
{ name: "week", value: 200 },
{ name: "month", value: 300 },
],
},
{
id: 12,
freqs: [
{ name: "day", value: 100 },
{ name: "week", value: 200 },
{ name: "month", value: 300 },
],
},
{
id: 13,
freqs: [
{ name: "day", value: 100 },
{ name: "week", value: 200 },
{ name: "month", value: 300 },
],
},
],
};
In this example, I would need the results:
let result = [
{ name: "day", value: 300 },
{ name: "week", value: 600 },
{ name: "month", value: 900 },
];
I've been trying for ages with a combination of filter() and reduce() methods (unsure if these are the right way), but I just can't get it - it's really a headscratcher for me!
Thank you
This combines all the freqs into one array then sums their values into an object and then reformats that object to be an array of objects with the name and value keys.
const arr = {"expenses":[{"id":11,"freqs":[{"name":"day","value":100},{"name":"week","value":200},{"name":"month","value":300}]},{"id":12,"freqs":[{"name":"day","value":100},{"name":"week","value":200},{"name":"month","value":300}]},{"id":13,"freqs":[{"name":"day","value":100},{"name":"week","value":200},{"name":"month","value":300}]}]};
const res = Object.entries(
arr.expenses
.flatMap(({ freqs }) => freqs)
.reduce(
(acc, { name, value }) => Object.assign(acc, { [name]: (acc[name] ?? 0) + value }),
{}
)
).map(([name, value]) => ({ name, value }));
console.log(res);
If I didn't know the structure then this could be difficult. However, given your input, I think this should solve your problem.
// Your input
let arr = {
expenses: [
{
id: 11,
freqs: [
{ name: "day", value: 100 },
{ name: "week", value: 200 },
{ name: "month", value: 300 },
],
},
{
id: 12,
freqs: [
{ name: "day", value: 100 },
{ name: "week", value: 200 },
{ name: "month", value: 300 },
],
},
{
id: 13,
freqs: [
{ name: "day", value: 100 },
{ name: "week", value: 200 },
{ name: "month", value: 300 },
],
},
],
};
// My code
let result = new Array();
// Create each object structure and push into empty result array
arr.expenses[0].freqs.forEach((item)=>result.push({name: item.name, value: 0}));
// Map through each object in arr.expenses
arr.expenses.map((object)=>{
// Loop through each object in freqs
object.freqs.forEach((item)=>{
result.filter(eachItem=>{
// Check if result objs' name matches the freqs objs' name
if(eachItem.name==item.name){
eachItem.value+=item.value; // Add the values
}
})
});
});
// Check the output
console.log(result);
We want to reduce the frequences to a single element, so we can do:
let arr = { expenses: [ { id: 11, freqs: [ {name: "day", value: 100}, {name: "week", value: 200}, {name: "month", value: 300}, ], }, { id: 12, freqs: [ {name: "day", value: 100}, {name: "week", value: 200}, {name: "month", value: 300}, ], }, { id: 13, freqs: [ {name: "day", value: 100}, {name: "week", value: 200}, {name: "month", value: 300}, ], }, ], };
let result = arr.expenses.reduce((total, curr) => {
total[0].value += curr.freqs[0].value
total[1].value += curr.freqs[1].value
total[2].value += curr.freqs[2].value
return total
}, [{name: "day", value: 0}, {name: "week", value: 0}, {name: "month", value: 0}])
console.log(result)

Recursively Transform my JSON data with JS

I'm trying to figure out how to transform some JSON i'm getting back from a web service so i can easily parse it into a nice type-safe object. I want to transform this format from:
[{
"name": "AwesomePeople",
"value": [
[{
"name": "TypeId",
"value": 1
}, {
"name": "People",
"value": [
[{
"name": "id",
"value": 2
}, {
"name": "name",
"value": "Danno"
}
],
[{
"name": "id",
"value": 3
}, {
"name": "name",
"value": "Julio"
}
]
]
}
],
[{
"name": "TypeId",
"value": 2
}, {
"name": "People",
"value": [
[{
"name": "id",
"value": 4
}, {
"name": "name",
"value": "Jenna"
}
],
[{
"name": "id",
"value": 5
}, {
"name": "name",
"value": "Coolio"
}
]
]
}
]
]
}
]
To the following format:
[{
"AwesomePeople": [
[{
"TypeId": 1,
}, {
"People": [
[{
"id": 2
}, {
"firstName":"Danno"
}
],
[{
"id": 3,
}, {
"firstName": "Julio"
}
]
]
}
],
[{
"TypeId": 2
}, {
"People": [
[{
"id": 4
}, {
"firstName": "Jenna"
}
],
[{
"id": 5
}, {
"firstName": "Coolio"
}
]
]
}
]
]
}
];
Two main things need to happen, these stupid "name"/"value" pairs need to be swapped at any and all levels. For example, instead of "name": "id", "value": "3", it would be simply be "id":3. The values are sometimes are arrays, so they need to processed in a similar way...the depth is variable, so i can't assume a certain number of levels deep, so i need to keep processing everything recursively.
I have started playing with the following code...you'll see an empty "newResult" array that i'm trying to build as i traverse the original JSON, taking different action whether i'm currently looking at an object, an array, or a key/property.
let count = 0;
let result = <the original array above>
let newResult = [];
result.forEach(function(resObj) {
console.log("STARTING to TRAVERSE HIGHER LEVEL OBJECTS!");
traverse(resObj);
count++;
//we're done processing high level objects, so return from this function and enjoy the newResult!
if (count===result.length)
//return from this function
console.log(newResult);
console.log("FINISHED PROCESSING HIGHER LEVEL OBJECTS, SO DONE!");
});
//Below are the functions for traversing
function traverse(x, level) {
if (isArray(x)) {
console.log("array");
traverseArray(x);
} else if ((typeof x === 'object') && (x !== null)) {
console.log("object");
traverseObject(x);
} else {
console.log("property: "+x);
//console.log(level + x);
}
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
function traverseArray(arr, level) {
//console.log(level + "<array>");
arr.forEach(function(x) {
traverse(x);
});
}
function traverseObject(obj, level) {
var keyName, keyValue;
//console.log(level + "<object>");
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (key==="name"){
keyName = obj[key];
}else if (key==="value"){
keyValue = obj[key];
}
if (keyName && keyValue){
var newObj = {[keyName]: keyValue}
newResult.push(newObj);
//console.log("the KEY NAME IS: "+ keyName + ", and the VALUE is: "+keyValue);
}
//if we have a key value, but the value is an array, stop and
// if (isArray(newOj)
console.log("traversing..." +obj[key]);
traverse(obj[key]);
}//end if property
}//end foreach key in object
}//end traverseObject
thanks all...kudos to the person who can get their brain around this :)
You can do this with JSON.stringify and JSON.parse - with a reviver, check if the value has a name property, and if it does, return { [value.name]: value.value }:
const arr=[{name:"AwesomePeople",value:[[{name:"TypeId",value:1},{name:"People",value:[[{name:"id",value:2},{name:"name",value:"Danno"}],[{name:"id",value:3},{name:"name",value:"Julio"}]]}],[{name:"TypeId",value:2},{name:"People",value:[[{name:"id",value:4},{name:"name",value:"Jenna"}],[{name:"id",value:5},{name:"name",value:"Coolio"}]]}]]}];
const result = JSON.parse(JSON.stringify(arr, (key, value) => (
value?.name
? { [value.name]: value.value }
: value
)));
console.log(result);
If you also want to change the name values to firstName keys, add a conditional in the computed property:
const arr=[{name:"AwesomePeople",value:[[{name:"TypeId",value:1},{name:"People",value:[[{name:"id",value:2},{name:"name",value:"Danno"}],[{name:"id",value:3},{name:"name",value:"Julio"}]]}],[{name:"TypeId",value:2},{name:"People",value:[[{name:"id",value:4},{name:"name",value:"Jenna"}],[{name:"id",value:5},{name:"name",value:"Coolio"}]]}]]}];
const result = JSON.parse(JSON.stringify(arr, (key, value) => (
value?.name
? { [value.name === 'name' ? 'firstName' : value.name]: value.value }
: value
)));
console.log(result);
Manually:
const arr=[{name:"AwesomePeople",value:[[{name:"TypeId",value:1},{name:"People",value:[[{name:"id",value:2},{name:"name",value:"Danno"}],[{name:"id",value:3},{name:"name",value:"Julio"}]]}],[{name:"TypeId",value:2},{name:"People",value:[[{name:"id",value:4},{name:"name",value:"Jenna"}],[{name:"id",value:5},{name:"name",value:"Coolio"}]]}]]}];
const recurse = (val) => {
if (!val || typeof val !== 'object') return val;
if (Array.isArray(val)) return val.map(recurse);
return { [val.name === 'name' ? 'firstName' : val.name]: val.value };
};
const result = recurse(arr);
console.log(result);
I grabbed your data, quickly wrote a function to convert it, came to post it and realized that my output wasn't at all what you requested. I would just throw it away, except that it seems to me this output is much more useful than what you requested. So if you can use something like this:
{
AwesomePeople: [
{
TypeId: 1,
People: [
{id: 2, name: "Danno"},
{id: 3, name: "Julio"}
]
},
{
TypeId: 2,
People: [
{id: 4, name: "Jenna"},
{id: 5, name: "Coolio"}
]
}
]
}
then this function may help:
const convert = (xs) =>
Object .fromEntries (
xs .map (({name, value}) => [
name,
Array .isArray (value) ? value .map (convert) : value
])
)
const data = [{name: "AwesomePeople", value: [[{name: "TypeId", value: 1}, {name: "People", value: [[{name: "id", value: 2}, {name: "name", value: "Danno"}], [{name: "id", value: 3}, {name: "name", value: "Julio"}]]}], [{name: "TypeId", value: 2}, {name: "People", value: [[{name: "id", value: 4}, {name: "name", value: "Jenna"}], [{name: "id", value: 5}, {name: "name", value: "Coolio"}]]}]]}]
console .log (convert (data))
.as-console-wrapper {min-height: 100% !important; top: 0}
If not, well then maybe someone else might get some use out of it.
Here is an answer using object-scan. This code modifies the original input, which can be significantly faster than rebuilding the structure.
Note that in your input the data is a bit inconsistent: Where does firstName come from? So I've assumed consistency
// const objectScan = require('object-scan');
const data = [{ name: 'AwesomePeople', value: [ [{ name: 'TypeId', value: 1 }, { name: 'People', value: [ [{ name: 'id', value: 2 }, { name: 'name', value: 'Danno' } ], [{ name: 'id', value: 3 }, { name: 'name', value: 'Julio' } ] ] } ], [{ name: 'TypeId', value: 2 }, { name: 'People', value: [ [{ name: 'id', value: 4 }, { name: 'name', value: 'Jenna' } ], [{ name: 'id', value: 5 }, { name: 'name', value: 'Coolio' } ] ] } ] ] } ];
objectScan(['**[*].name'], {
filterFn: ({ parent }) => {
const { name, value } = parent;
delete parent.name;
delete parent.value;
parent[name] = value;
}
})(data);
console.log(data);
// => [ { AwesomePeople: [ [ { TypeId: 1 }, { People: [ [ { id: 2 }, { name: 'Danno' } ], [ { id: 3 }, { name: 'Julio' } ] ] } ], [ { TypeId: 2 }, { People: [ [ { id: 4 }, { name: 'Jenna' } ], [ { id: 5 }, { name: 'Coolio' } ] ] } ] ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.0.0"></script>
Disclaimer: I'm the author of object-scan

Find another way to solve the problem (group an array of objects)?

I am solving the problem as follows:
I have an array of objects with input format:
let allTasks = [
{
time: "07-2020",
tasks: [
{
code: "p1",
value: 1111
}
],
},
{
time: "07-2020",
tasks: [
{
code: "p2",
value: 2222
}
]
},
{
time: "08-2020",
tasks: [
{
code: "p1",
value: 3333
}
]
},
{
time: "08-2020",
tasks: [
{
code: "p2",
value: 4444
}
]
},
{
time: "09-2020",
tasks: [
{
code: "p1",
value: 5555
}
]
},
{
time: "09-2020",
tasks: [
{
code: "p2",
value: 6666
}
]
},
]
I want to convert into a format that is formatted as follows:
output = [
{
p1: [
["07-2020",1111],
["08-2020",3333],
["09-2020",5555],
]
},
{
p2: [
["07-2020",2222],
["08-2020", 4444],
["09-2020", 6666],
]
},
// ... p3, p4....
]
I use reduce() method to group the objects in an array.
let newArr = [];
allTasks.forEach((x,index)=>{
newArr.push({
tasks: x.tasks.map(y=>Object.assign(y,{time: x.time}))
})
})
console.log('newArr',newArr);
let listTask = newArr.flatMap(x=>x.tasks);
let groupData = listTask.reduce((gr,item)=>{
gr[item.code] = [...gr[item.code] || [],item];
return gr;
},[])
let newGroupData = Object.entries(groupData).map(([key,data])=>{
console.log('-------------------------')
let result = Object.values(data.reduce((arr,item)=>{
arr[item.time] = arr[item.time] || [item.time];
arr[item.time].push(item.value);
return arr;
},[]));
return {
[key]: result
}
})
console.log('newGroupData',newGroupData)
The results have been expected as above. But my code is long, how do I refactor my code and is there a better way to do it ?
Please help me.
Thank you!
A different approach
You could flatten the tasks first, with complemented key-value pair of time. To array of this
[
...
{ code: 'p1', value: 1111, time: '07-2020' }
...
]
After that, group task by code and do some manipulation with the grouped to achieve your expected result
const groupByCode = {}
allTasks
.flatMap((parentTask) =>
parentTask.tasks.map((task) => ({ ...task, time: parentTask.time }))
)
.forEach((flattenedTask) => {
if (groupByCode[flattenedTask.code]) {
groupByCode[flattenedTask.code].push(flattenedTask)
} else {
groupByCode[flattenedTask.code] = [flattenedTask]
}
})
const res = Object.entries(groupByCode).map(([code, tasks]) => ({
[code]: tasks.map((task) => [task.time, task.value]),
}))
Full implementation
let allTasks = [
{
time: "07-2020",
tasks: [
{
code: "p1",
value: 1111,
},
],
},
{
time: "07-2020",
tasks: [
{
code: "p2",
value: 2222,
},
],
},
{
time: "08-2020",
tasks: [
{
code: "p1",
value: 3333,
},
],
},
{
time: "08-2020",
tasks: [
{
code: "p2",
value: 4444,
},
],
},
{
time: "09-2020",
tasks: [
{
code: "p1",
value: 5555,
},
],
},
{
time: "09-2020",
tasks: [
{
code: "p2",
value: 6666,
},
],
},
]
const groupByCode = {}
allTasks
.flatMap((parentTask) =>
parentTask.tasks.map((task) => ({ ...task, time: parentTask.time }))
)
.forEach((flattenedTask) => {
if (groupByCode[flattenedTask.code]) {
groupByCode[flattenedTask.code].push(flattenedTask)
} else {
groupByCode[flattenedTask.code] = [flattenedTask]
}
})
const res = Object.entries(groupByCode).map(([code, tasks]) => ({
[code]: tasks.map((task) => [task.time, task.value]),
}))
console.log(JSON.stringify(res, null, 2))
Reference
Array.prototype.flatMap()
Object.entries()

Categories