Group array by year and month - javascript

I would like to group an array with events by year and month. My data looks like this:
const events = [
{
name: "event 1",
year: 2021,
month: 1,
},
{
name: "event 2",
year: 2021,
month: 9,
},
{
name: "event 3",
year: 2021,
month: 1,
},
{
name: "event 4",
year: 2022,
month: 7,
},
]
And my expected outcome should be something like this:
[
{
year: 2021,
month: 1,
events: [
{
name: "event 1"
},
{
name: "event 3"
}
]
},
{
year: 2021,
month: 9,
events: [
{
name: "event 2"
}
]
}
]
What would be the best approach to do this? I found a couple stackoverflow posts to group an array by it's key value but that not what I'm looking for.
const groupBy = (array, key) => {
return array.reduce((result, currentValue) => {
// If an array already present for key, push it to the array. Else create an array and push the object
(result[currentValue[key]] = result[currentValue[key]] || []).push(currentValue);
// Return the current iteration `result` value, this will be taken as next iteration `result` value and accumulate
return result;
}, {}); // empty object is the initial value for result object
};
const groupedByYear = groupBy(events, 'year');

You can do this with reduce and Object.values
const events = [
{
name: "event 1",
year: 2021,
month: 1,
},
{
name: "event 2",
year: 2021,
month: 9,
},
{
name: "event 3",
year: 2021,
month: 1,
},
];
const result = Object.values(events.reduce( (acc,evt) => {
const key = `${evt.year}-${evt.month}`;
if(!acc[key]) {
acc[key] = {year: evt.year, month: evt.month, events:[]}
}
acc[key].events.push( {name:evt.name} );
return acc;
},{}));
console.log(result);

You could take a dynamic approach by using a combined key for wanted properties for grouping.
Then remove all keys of grouing and push a new object without unwanted properties.
const
events = [{ name: "event 1", year: 2021, month: 1 }, { name: "event 2", year: 2021, month: 9 }, { name: "event 3", year: 2021, month: 1 }],
keys = ['year', 'month'],
result = Object.values(events.reduce((r, o) => {
let value,
key = keys.map(k => o[k]).join('|');
if (!r[key]) r[key] = { ...Object.fromEntries(keys.map(k => [k, o[k]])), events: [] };
r[key].events.push(keys.reduce((t, k) => (({ [k]: value, ...t } = t), t), o));
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Group Array of Objects to Year and Month by Date

Given the following Array of Objects:
[{
"id": 1,
"name": "random_name1",
"published_at": "2021-01-16T08:52:24.408Z",
},
{
"id": 2,
"name": "random_name2",
"published_at": "2022-02-16T08:52:24.408Z",
},
{
"id": 3,
"name": "random_name3",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 4,
"name": "random_name4",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 5,
"name": "random_name5",
"published_at": "2022-05-16T08:52:24.408Z",
}
]
I need to group the items in one array of nested objects (descending) by Year and Month, result should be:
[
{
year: '2022',
months: [
{
month: '5',
items: [
{
id: '5',
name: 'random_name5'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2021',
months: [
{
month: '1',
items: [
{
id: '1',
name: 'random_name1'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2020',
months: [
{
month: '4',
items: [
{
id: '3',
name: 'random_name3'
},
{
id: '4',
name: 'random_name4'
}
]
}
]
}
];
I have tried the following:
items = [...new Set(items.map((item) => parseInt(item.published_at.split('-')[0])))].map((year) => [
{
year: year,
months: [
...new Set(
items
.filter((item) => parseInt(item.published_at.split('-')[0]) === year)
.map((item) => parseInt(item.published_at.split('-')[1]))
)
].map((month) => [
{
month: month,
items: items.filter(
(item) => parseInt(item.published_at.split('-')[0]) === year && parseInt(item.published_at.split('-')[1]) === month
)
}
])
}
]);
return items
The problem with the above solution, is that it will create a two dimensional array like so (months being two dimensional too):
[
[ { year: 2022, months: [Array] } ],
[ { year: 2021, months: [Array] } ],
[ { year: 2020, months: [Array] } ],
[ { year: 2019, months: [Array] } ],
[ { year: 2018, months: [Array] } ]
]
How to fix this?
If you get a unique list of year-months you can use this to map your object
const items = [{ "id": 1,"name": "random_name1","published_at": "2021-01-16T08:52:24.408Z", },
{ "id": 2, "name": "random_name2", "published_at": "2022-02-16T08:52:24.408Z",},
{ "id": 3, "name": "random_name3","published_at": "2020-04-16T08:52:24.408Z",},
{"id": 4, "name": "random_name4", "published_at": "2020-04-16T08:52:24.408Z",},
{ "id": 5, "name": "random_name5", "published_at": "2022-05-16T08:52:24.408Z",}]
let uniqueYearMonths = [... new Set(items.map(x => x.published_at.substring(0,7)))];
let results = [... new Set(items.map(x => x.published_at.substring(0,4)))]
.map(year => ({
year: year,
months: uniqueYearMonths
.filter(ym => ym.startsWith(year))
.map(ym => ({
month: ym.substring(5,7),
items: items
.filter(item => item.published_at.startsWith(ym))
.map(item => ({
id: item.id,
name: item.name
}))
}))
}));
console.log(results);
Given you array as data, you could do something with array methods like map and reduce.
Like this:
const groupedByYear = data.map((e) => ({ ...e, published_at: new Date(e.published_at) }))
.reduce((acc, e) => {
const year = e.published_at.getFullYear();
const month = e.published_at.getMonth() + 1;
if (!acc[year]) acc[year] = { year };
if (!acc[year][month]) acc[year][month] = [];
acc[year][month] = e;
return acc;
}, {})
const result = Object.values(groupedByYear).reduce((acc, e) => {
const { year, ...months } = e;
acc.push({ year: year, months: months });
return acc;
}, [])
This is an example and is probably not the best way to do this. It is only intended to show you a path of data transformations.
First data.map to be able to do operations on dates. Then a reduce to group data (here using an object). Then creating an array from the object values to match the output you want.
Compared to a solution like you showed, there is the advantage that you limit the number of times that you iterate over the array. It is always a good idea to avoid iterating to much time on an array for better performance.

I want to turn a javascript array to create a new array

I want to display a graph of the total count for groupA and groupB for each month.
The graph library uses chart.js
I want to put the sum of the counts for each month in data:[].
I want to turn the array of values to be retrieved from the data to determine groupA and groupB, and put the count for each month into data
script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"
javascript:
var users = #{raw #user_counts.to_json}
console.log(users)
var ct = document.getElementById('ex_chart');
var ex_chart = new Chart(ct, {
type: 'horizontalBar',
data: {
labels: ["Jan", "Feb", "Mar", "Apr", "May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
datasets: [
{
label: 'groupA',
data: [],
backgroundColor: '#C7CED7'
},
{
label: 'groupB',
data: [],
backgroundColor: '#0068B4'
}
]
},
options: options
})
Contents of users
[
{
activity_type: "groupA"
count: 10
created_at: "2021-01-14T13:46:18.000Z"
id: 1
year: 2020
month: "Jan"
updated_at: "2021-01-14T13:46:18.000Z"
},
{
activity_type: "groupA"
count: 8
created_at: "2021-01-14T13:46:18.000Z"
id: 2
year: 2020
month: "Feb"
updated_at: "2021-01-14T13:46:18.000Z"
},
{
activity_type: "groupB"
count: 8
created_at: "2021-01-14T13:46:18.000Z"
id: 3
year: 2020
month: "Feb"
updated_at: "2021-01-14T13:46:18.000Z"
}
]
In technical terms, you want to group your user counts by two parameters: 'activityType' and 'month'.
Below is a solution using functional programming. You may modify keys, for example to add 'year' parameter, which actually makes sense.
const users = [
{
activity_type: "groupA",
count: 10,
year: 2020,
month: 1
},
{
activity_type: "groupA",
count: 17,
year: 2019,
month: 2,
},
{
activity_type: "groupA",
count: 8,
year: 2020,
month: 2,
},
{
activity_type: "groupB",
count: 8,
year: 2020,
month: 1,
}
];
const keys = ['activity_type', 'month'];
function matches(table, entry, keys) { // finds item with same values
return table.find(e => keys.every(k => e[k] == entry[k]));
}
const usersGroupedByKeys = users.reduce((cur, val) => {
let alreadyIn = matches(cur, val, keys);
if (alreadyIn) {
alreadyIn['count'] = alreadyIn['count'] + val['count'];
} else {
cur.push(val);
}
return cur;
}, []);
console.log(usersGroupedByKeys);
Check the Docs.

ES5 Javascript map through objects in array, then change the object key by group

I have an array of object, I tried to add one more word the object key, but having trouble with it, please check my data and expect output and the code I tried below:
Trying to get output like this:
const data = [
{
'name_A': Joe,
'year_A': 2019,
'saving_A': 0,
'member_A': false,
'group:_A': "A"
},
{
'name_B': Amy,
'year_B': 2019,
'saving_B': 0,
'member_B': false,
'group_B': "B"
},
{
'name_C': Bob,
'year_C': 2019,
'saving_C': 0,
'member_C': true,
'group_C': "C"
}
];
Here's my code:
const data = [{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "A"
},
{
name: 'Amy',
year: 2019,
saving: 0,
member: false,
group: "B"
},
{
name: 'Bob',
year: 2019,
saving: 0,
member: true,
group: "C"
}
];
const filter = data.reduce((acc, key) => {
const groupName = key.group;
const obj = {};
if (key.member === false) {
const nodeId = key.nodeId + groupName;
obj.nodeId = key.nodeId;
obj.year = key.year;
acc.push(obj);
}
return acc;
}, []);
console.log(filter)
Thanks for the help!
You can do it by using reduce:
const data = [
{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "A"
},
{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "B"
},
{
name: 'Joe',
year: 2019,
saving: 0,
member: true,
group: "C"
}
];
let result = data.map(el => {
const group = el.group
return Object.keys(el).reduce((a, i) => {
a[`${i}_${group}`] = el[i]
return a
}, {})
})
console.log(result)
You can take advantage of Array.prototype.map and Array.prototype.reduce to resolve this issue:
const data = [{
name: 'Joe',
year: 2019,
saving: 0,
member: false,
group: "A"
},
{
name: 'Amy',
year: 2019,
saving: 0,
member: false,
group: "B"
},
{
name: 'Bob',
year: 2019,
saving: 0,
member: true,
group: "C"
}
];
const format = arr => arr.map(el => {
return Object.keys(el).reduce((accum, key) => {
accum[`${key}_${el.group}`] = el[key];
return accum;
}, {});
});
console.log(format(data));
An alternative is a mix between the function map and function reduce.
The function map for converting the current objects to the desired structure, and the function reduce to concatenate the group with the current keys.
const data = [ { name:'Joe', year: 2019, saving: 0, member: false, group:"A" }, { name: 'Amy', year: 2019, saving:0, member: false, group: "B" }, { name:'Bob', year: 2019, saving: 0, member: true, group:"C" } ];
let result = data.map(({group, ...rest}) => Object.entries(rest).reduce((a, [k, v]) => Object.assign(a, {[`${k}_${group}`]: v}), {[`group_${group}`]: group}));
console.log(result);

JS - merge object same array lodash

I need to transform a array of multiple object in one array object,
I explain myself, I wish to group for each table the objects that carry the same "month" and replace the properties that have the same value by adding their ID at the beginning:
ex:
quantity: 1
becomes fpsIssuedQuantity (in camelCase). http://jsfiddle.net/rLjQx/96589/
here are my data :
var data = {
"2018-01": [
{ id:"fpsIssued", month:"2018-01", quantity:"28" },
{ id:"dgfipIncome", month:"2018-01", amount:1350 },
{ id:"antaiPaidFps", month:"2018-01", quantity:2242 }
],
"2018-02": [
{ id: "fpsIssued", month: "2018-02", quantity: "29" },
{ id: "dgfipIncome", month: "2018-02", amount: 8530 },
{ id: "antaiPaidFps", month: "2018-02", quantity: 4857}
]
};
console.log(data);
and the expected data :
var expectedData = {
"2018-01": [
{ month: "2018-01", fpsIssuedquantity: "28",
dgfipIncomeamount: 1350, antaiPaidFpsQuantity: 2242
}
],
"2018-02": [
{ month: "2018-02", fpsIssuedquantity: "29",
dgfipIncomeamount: 8530, antaiPaidFpsQuantity: 4857
}
]
};
console.log(expectedData);
i use lodash and angularjs but i can not get my result .. please could you help me?
You could map new objects with wanted new property names and values in new objects.
var data = { "2018-01": [{ id: "fpsIssued", month: "2018-01", quantity: "28" }, { id: "dgfipIncome", month: "2018-01", amount: 1350 }, { id: "antaiPaidFps", month: "2018-01", quantity: 2242 }], "2018-02": [{ id: "fpsIssued", month: "2018-02", quantity: "29" }, { id: "dgfipIncome", month: "2018-02", amount: 8530 }, { id: "antaiPaidFps", month: "2018-02", quantity: 4857 }] },
result = Object.assign(
...Object
.entries(data)
.map(
([k, v]) => ({ [k]: Object.assign(...v.map(o => ({ month: o.month, [o.id + ('quantity' in o ? 'Quantity' : 'Amount')]: 'quantity' in o ? o.quantity : o.amount }))) })
)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter through array of objects in javascript

I have a data structure that looks like this:
const carsData = [
{
name: "Cars",
collection: [
{ year: 2011, model: "B", price: 4400 },
{ year: 2015, model: "A", price: 32000 },
{ year: 2016, model: "B", price: 15500 }
]
},
{
name: "Trucks",
collection: [
{ year: 2014, model: "D", price: 18000 },
{ year: 2013, model: "E", price: 5200 }
]
},
{
name: "Convertibles",
collection: [
{ year: 2009, model: "F", price: 20000 },
{ year: 2010, model: "G", price: 8000 },
{ year: 2012, model: "H", price: 12500 },
{ year: 2017, model: "M", price: 80000 }
]
}
];
and want to return a new array let's say const newCarsData(see below) where collection consists of only objects with year higher than 2013, so it will look like this:
const newCarsData = [
{
name: "Cars",
collection:[
{ year: 2015, model: "A", price: 32000 },
{ year: 2016, model: "B", price: 15500 }
]
},
{
name: "Trucks",
collection: [
{ year: 2014, model: "D", price: 18000 }
]
},
{
name: "Convertibles",
collection: [
{ year: 2017, model: "M", price: 80000 }
]
}
];
I tried filter method collection.filter(x => x.year > 2013) inside of for loop, but couldn't make it work. At the end my code looked like this
const newCarsData = getNewData(carsData);
let arr = [];
function getNewData(somedata) {
for (let i = 0; i < somedata.length; i++) {
// console.log(somedata[i].collection);
for (let j = 0; j < somedata[i].collection.length; j++) {
let arr.push(somedata[i].collection[j]);
// console.log(somedata[i].collection[j]);
}
// return somedata[i].collection.filter(x => x.year > 2013);
}
return arr.filter(x => x.year > 2013);
}
Since the collection is in another array inside the array items, you can't directly use filter. You can use map first then use filter.
const carsData=[{name:"Cars",collection:[{year:2011,model:"B",price:4400},{year:2015,model:"A",price:32000},{year:2016,model:"B",price:15500}]},{name:"Trucks",collection:[{year:2014,model:"D",price:18000},{year:2013,model:"E",price:5200}]},{name:"Convertibles",collection:[{year:2009,model:"F",price:20000},{year:2010,model:"G",price:8000},{year:2012,model:"H",price:12500},{year:2017,model:"M",price:80000}]}]
const filteredCarData = carsData.map(carType => {
return {
...carType,
collection: carType.collection.filter(car => car.year>2013)
}
})
console.log(JSON.stringify(filteredCarData))
The ...carType notation collects the object properties in the new mapped object. If you have no other properties than name, you can instead do
const filteredCarData = carsData.map(carType => {
return {
name: carType.name,
collection: carType.collection.filter(car => car.year>2013)
}
})
You'll have to update your collection:
carsData.forEach(function(carData){
carData.collection = carData.collection.filter(x => x.year > 2013);
});
One way to do it can be using reduce.
var res = carsData.reduce((acc, value) => {
let data = { name: value.name, collections: value.collection.filter(v => v.year > 2013 )}
return acc.concat(data)
}, [])
You can actually, replace the reduce with a map:
carsData.map(value => {
return { name: value.name, collections: value.collection.filter(v => v.year > 2013 )}
})

Categories

Resources