How can I add data by grouping with country label and create data array that has 12 index indicating months and containing value of data before grouping.
I need help, how to push and group the data according to the month number. e.x
arr = [
{ label: 'US', data: '10', monthNumber: 1 },
{ label: 'US', data: '2', monthNumber: 3 },
{ label: 'US', data: '60', monthNumber: 2 },
{ label: 'UK', data: '10', monthNumber: 5 },
{ label: 'SA', data: '1', monthNumber: 1 },
{ label: 'CA', data: '70', monthNumber: 1 },
{ label: 'SA', data: '10', monthNumber: 12 },
];
now i need the results to be like
[
{ label: 'US', data: [10,60,2,0,0,0,0,0,0,0,0,0] },
{ label: 'UK', data: [0,0,0,0,10,0,0,0,0,0,0,0] },
{ label: 'SA', data: [1,0,0,0,0,0,0,0,0,0,0,10] },
{ label: 'CA', data: [70,0,0,0,0,0,0,0,0,0,0,0] },
];
Create a new object by reducing over the array using the labels as object keys, and initialising the property value as an object with a label, and a pre-filled array of zeros. Then update the array with the data at the relevant position.
const arr=[{label:"US",data:"10",monthNumber:1},{label:"US",data:"2",monthNumber:3},{label:"US",data:"60",monthNumber:2},{label:"UK",data:"10",monthNumber:5},{label:"SA",data:"1",monthNumber:1},{label:"CA",data:"70",monthNumber:1},{label:"SA",data:"10",monthNumber:12}];
function grouper(arr) {
// `reduce` over the array passing in an
// empty object as the initial accumulator value
const out = arr.reduce((acc, c) => {
// Destructure the properties from the current
// iterated object
const { label, data, monthNumber } = c;
// If the label doesn't exist as a key on the object
// create it, and assign an object as its value, using
// the label, and adding a pre-filled array of zeros to
// its data property
acc[label] ??= { label, data: new Array(12).fill(0) };
// Update the data array with the data value at the
// appropriate position
acc[label].data[monthNumber - 1] = Number(data);
// Return the accumulator for the next iteration
return acc;
}, {});
// Finally get the array of updated objects
// from the accumulated data
return Object.values(out);
}
console.log(grouper(arr));
Additional documentation
Logical nullish assignment
fill
Object.values
For grouping, you could make use of reduce
const arr = [
{ label: "US", data: "10", monthNumber: 1 },
{ label: "US", data: "2", monthNumber: 3 },
{ label: "US", data: "60", monthNumber: 2 },
{ label: "UK", data: "10", monthNumber: 5 },
{ label: "SA", data: "1", monthNumber: 1 },
{ label: "CA", data: "70", monthNumber: 1 },
{ label: "SA", data: "10", monthNumber: 12 },
]
let res = arr.reduce((acc, { label, monthNumber, data }) => {
if (!acc[label]) acc[label] = Array(12).fill(0)
acc[label][monthNumber - 1] = Number(data)
return acc
}, {})
res = Object.entries(res).map(([label, data]) => ({ label, data }))
console.log(res)
First I have extracted the label from the current array.
After that, I have a loop through those labels, and inside that loop through the array, and created an object with store values of data and label.
please see the comments for a better understanding.
Important thing to notice here is that for the same month number for labels value will be replaced with the last one.
arr = [{
label: 'US',
data: '10',
monthNumber: 1
},
{
label: 'US',
data: '2',
monthNumber: 3
},
{
label: 'US',
data: '60',
monthNumber: 2
},
{
label: 'UK',
data: '10',
monthNumber: 5
},
{
label: 'SA',
data: '1',
monthNumber: 1
},
{
label: 'CA',
data: '70',
monthNumber: 1
},
{
label: 'SA',
data: '10',
monthNumber: 12
},
];
const labels = [];
const result = [];
// extracting unique labels
arr.forEach(item => {
!labels.includes(item.label) && labels.push(item.label);
})
// looping through labels
labels.forEach(label => {
// creating empty object
const object = {};
// creating data array with 12 values by default 0
const data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// loop though array values
arr.forEach(item => {
// checking if current outer label is matching with array label
if (item.label == label) {
// if abive condition true add label key in object with value of current outer label
object["label"] = label;
// updating month value
data[item.monthNumber - 1] = item.data;
}
// adding data in object with key data.
object["data"] = data;
})
// pushing final object in result
result.push(object);
})
//printing final result
console.log(result);
Good starting point will be to group the data by label first.
Then take the values from that to remap the data.
Create a Month array generating a new array with 0 values
Loop through and add the data to the month array based on MonthNumber
const remap = () => {
let arr = [
{ label: 'US', data: '10', monthNumber: 1 },
{ label: 'US', data: '2', monthNumber: 3 },
{ label: 'US', data: '60', monthNumber: 2 },
{ label: 'UK', data: '10', monthNumber: 5 },
{ label: 'SA', data: '1', monthNumber: 1 },
{ label: 'CA', data: '70', monthNumber: 1 },
{ label: 'SA', data: '10', monthNumber: 12 }
];
return Object.values(
arr.reduce((acc, v) => {
if (!acc.hasOwnProperty(v.label)) {
acc[v.label] = [];
}
acc[v.label].push(v);
return acc;
}, {})
).map((flatData) => {
const label = Array.isArray(flatData) && flatData.length > 0 ? flatData[0].label : '';
const monthArray = new Array(12).fill(0);
flatData.forEach(({ data, monthNumber }) => {
monthArray[monthNumber] = parseInt(data);
});
return { label, data: monthArray };
});
};
console.log(remap())
Try this solution:
function setupGraph(arr){
data = [];
for (let i = 0; i < arr.length; i++) {
let found = false;
for (let j = 0; j < data.length; j++) {
if (data[j].label === arr[i].label) {
data[j].data[arr[i].monthNumber - 1] = Number(arr[i].data);
found = true;
break;
}
}
if (!found) {
data.push({ label: arr[i].label, data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] });
data[data.length - 1].data[arr[i].monthNumber - 1] = Number(arr[i].data);
}
}
return data;
}
Testing the function:
arr = [
{ label: 'US', data: '10', monthNumber: 1 },
{ label: 'US', data: '2', monthNumber: 3 },
{ label: 'US', data: '60', monthNumber: 2 },
{ label: 'UK', data: '10', monthNumber: 5 },
{ label: 'SA', data: '1', monthNumber: 1 },
{ label: 'CA', data: '70', monthNumber: 1 },
{ label: 'SA', data: '10', monthNumber: 12 },
];
let result = setupGraph(arr)
console.log(result);
[
{
label: 'US',
data: [
10, 60, 2, 0, 0,
0, 0, 0, 0, 0,
0, 0
]
},
{
label: 'UK',
data: [
0, 0, 0, 0, 10,
0, 0, 0, 0, 0,
0, 0
]
},
{
label: 'SA',
data: [
1, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 10
]
},
{
label: 'CA',
data: [
70, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0
]
}
]
const defaultMonthsData = [0,0,0,0,0,0,0,0,0,0,0,0];
const arr = [
{ label: 'US', data: '10', monthNumber: 1 },
{ label: 'US', data: '2', monthNumber: 3 },
{ label: 'US', data: '60', monthNumber: 2 },
{ label: 'UK', data: '10', monthNumber: 5 },
{ label: 'SA', data: '1', monthNumber: 1 },
{ label: 'CA', data: '70', monthNumber: 1 },
{ label: 'SA', data: '10', monthNumber: 12 },
];
const results = _(arr)
.groupBy("label")
.map((value, label) => {
const monthsData = JSON.parse(JSON.stringify(defaultMonthsData));
return {
label,
data: _.map(value, function(o, index) {
monthsData[o.monthNumber] = parseInt(o.data);
return monthsData
})[0],
}
}).value();
console.log(results)
You can try with this (I'm using lodash 4.13.1). Thanks
I have an object looking like this
const item = {
id: 123,
type: 'book',
sections: [{
type: 'section',
id: '456',
index: 1,
lessons: [{
type: 'lesson',
id: 789,
index: 1
},
{
type: 'lesson',
id: 999,
index: 2
}
]
}, {
type: 'section',
index: 2,
id: 321,
lessons: [{
type: 'lesson',
id: 444,
index: 1
},
{
type: 'lesson',
id: 555,
index: 2
}
]
}]
}
It should be assumed that there are more objects in sections and lessons array. I want to create a new object like this
result = [{
section: 456,
lessons: [789, 999]
}, {
section: 321,
lessons: [444, 555]
}]
I tried this loop but this just pushes indexes and not lesson's ids
let obj = {};
let sectionWithLessons = [];
let lessons = []
for (const i in item.sections) {
obj = {
sectionId: item.sections[i].id,
lessonIds: item.sections[i].lessons.map((lesson) => {
return lessons.push(lesson.id)
}),
};
sectionWithLessons.push(obj);
}
console.log(sectionWithLessons);
How can i do this correctly and preferably with good performance in consideration?
I believe the best/shortest thing is to use the map function, like:
const result2 = item.sections.map(({id, lessons}) => ({
id,
lessons: lessons.map(({id: lessionId}) => lessionId)
}))
I would suggest using Array.map() to convert the item sections to the desired result.
We'd convert each section into an object with a section value and lessons array.
To create the lessons array, we again use Array.map() to map each lesson to a lesson id.
const item = { id: 123, type: 'book', sections: [{ type: 'section', id: '456', index: 1, lessons: [{ type: 'lesson', id: 789, index: 1 }, { type: 'lesson', id: 999, index: 2 } ] }, { type: 'section', index: 2, id: 321, lessons: [{ type: 'lesson', id: 444, index: 1 }, { type: 'lesson', id: 555, index: 2 } ] }] }
const result = item.sections.map(({ id, lessons }) => {
return ({ section: +id, lessons: lessons.map(({ id }) => id) })
});
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }
i'm trying to filter an array of objects and return a single object, organised by the keys continents, countries and cities and we place those objects in the correct key.
const data = [{
location: [{
name: 'Africa',
type: 'Continent'
},
{
name: 'Angola',
type: 'Country'
},
{
name: 'Luanda',
type: 'City'
}
],
values: []
}, {
location: [{
name: 'Europe',
type: 'Continent'
},
{
name: 'Italy',
type: 'Country'
},
{
name: 'Rome',
type: 'City'
}
],
values: []
}, {
location: [{
name: 'Europe',
type: 'Continent'
},
{
name: 'Spain',
type: 'Country'
},
{
name: 'Valencia',
type: 'City'
}
],
values: []
}]
It should result in:
{
Africa: {
countries: {
Angola: {
cities: {
Luanda: {
values: []
}
}
}
}
},
Europe: {
countries: {
Italy: {
cities: {
Rome: {
values: []
}
}
},
Spain: {
cities: {
Valencia: {
values: []
}
}
}
}
}
}
I've tried to filter by its keys but when it comes to placing the objects in the right place (e.g by same Continent) I couldn't get it working.
const result = data.reduce((acc, total) => {
const continentFilter = total.location.find(s => s.type === 'Continent')
acc[continentFilter.name] = {
// ...
}
return acc
}, {})
console.log(result)
// {
// Africa: { ... },
// Europe: { ... }
// }
UPDATE:
The type is always 'Continent', 'Country', or 'City'
Some Continents/Countries might not have a City
The goal is to organize by Continent, then by Country and City
You could take an array for the nested properties which are not given by the location arrays (maybe there could be the key instead of unused type).
Then iterate the array and create a nested structure. At the end apply the values.
For unsorted location sort it in advance.
const
data = [{ location: [{ name: 'Angola', type: 'Country' }, { name: 'Luanda', type: 'City' }, { name: 'Africa', type: 'Continent' }], values: [] }, { location: [{ name: 'Europe', type: 'Continent' }, { name: 'Italy', type: 'Country' }, { name: 'Rome', type: 'City' }], values: [] }, { location: [{ name: 'Europe',type: 'Continent' }, { name: 'Spain', type: 'Country' }, { name: 'Valencia', type: 'City' }], values: [] }];
levels = ['countries', 'cities'],
result = data.reduce((r, { location, values }) => {
const
order = { Continent: 1, Country: 2, City: 3 },
temp = location
.sort((a, b) => order[a.type] - order[b.type])
.reduce((o, { name }, i) => {
o[name] ??= {};
return levels[i]
? (o[name][levels[i]] ??= {})
: o[name];
}, r);
(temp.values ??= []).push(...values);
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a working example. I'm sure it can be improved.
const data = [{
location: [{
name: 'Africa',
type: 'Continent'
},
{
name: 'Angola',
type: 'Country'
},
{
name: 'Luanda',
type: 'City'
}
],
values: []
}, {
location: [{
name: 'Europe',
type: 'Continent'
},
{
name: 'Italy',
type: 'Country'
},
{
name: 'Rome',
type: 'City'
}
],
values: []
}, {
location: [{
name: 'Europe',
type: 'Continent'
},
{
name: 'Spain',
type: 'Country'
},
{
name: 'Valencia',
type: 'City'
}
],
values: []
}];
function countryDataToStructuredObject(data){
let countryData = { };
data.map(a => {
countryData[a.location.find(x => x.type == 'Continent').name] = {};
countryData[a.location.find(x => x.type == 'Continent').name]['countries'] = {};
countryData[a.location.find(x => x.type == 'Continent').name]['countries'][a.location.find(x => x.type == 'Country').name] = {};
countryData[a.location.find(x => x.type == 'Continent').name]['countries'][a.location.find(x => x.type == 'Country').name]['cities'] = {};
countryData[a.location.find(x => x.type == 'Continent').name]['countries'][a.location.find(x => x.type == 'Country').name]['cities'][a.location.find(x => x.type == 'City').name] = { values: a.values };
});
return countryData;
}
console.log(countryDataToStructuredObject(data));
So I have a data like this
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 112
},
{
date: 112
}
],
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
},
{
name: 'Voucher B',
participants: [
{
date: 111
},
{
date: 112
}
],
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
And I want to group it based on the date (if it has same date). So for data above, the expected result will be
expected = [
{
name: 'Voucher A',
date: 1,
count: 1,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
},
{
name: 'Voucher A',
date: 2,
count: 1,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
Because it has different date. But if it has same date, the expected result will be
expected = [
{
name: 'Voucher A',
date: 1,
count: 2,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
I was trying to use reduce to group it but it did not give the structure I want
carts.forEach(cart => {
cart.participants.reduce((acc, obj) => {
acc[obj.date] = [...acc[obj.date] || [], obj]
return acc
}, {})
})
To organize the data, I think you need two associations to group by: the name and the dates and their counts for that name:
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 1
},
{
date: 2
}
]
}
];
const groupedByNames = {};
for (const { name, participants } of carts) {
if (!groupedByNames[name]) groupedByNames[name] = {};
for (const { date } of participants) {
groupedByNames[name][date] = (groupedByNames[name][date] || 0) + 1;
}
}
const output = Object.entries(groupedByNames).flatMap(
([name, dateCounts]) => Object.entries(dateCounts).map(
([date, count]) => ({ name, date: Number(date), count })
)
);
console.log(output);
If you want use, just plain for loops, you can try this solution. It looks simple and elegant 😜😜
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 1
},
{
date: 1
},
{
date: 2
}
]
},
{
name: 'Voucher B',
participants: [
{
date: 1
},
{
date: 2
},
{
date: 2
}
]
}
]
const finalOutput = []
for (const cart of carts) {
for (const participant of cart.participants) {
const res = finalOutput.find(e => e.name === cart.name && e.date === participant.date)
if (res) {
res.count += 1
} else {
finalOutput.push({ name: cart.name, date: participant.date, count: 1 })
}
}
}
console.log(finalOutput)
Use forEach and destructuring
const process = ({ participants, name }) => {
const res = {};
participants.forEach(({ date }) => {
res[date] ??= { name, count: 0, date };
res[date].count += 1;
});
return Object.values(res);
};
const carts = [
{
name: "Voucher A",
participants: [
{
date: 1,
},
{
date: 2,
},
],
},
];
console.log(carts.flatMap(process));
const carts2 = [
{
name: "Voucher A",
participants: [
{
date: 1,
},
{
date: 1,
},
],
},
];
console.log(carts2.flatMap(process));