How to nest multiple groupby with loadash/javascript - javascript

Thank you in advance to whoever can help me. I am trying to display data using SectionList in React Native. I have written my code below displaying what I am trying to accomplish. A regular javascript solution will work. I want the data to first be grouped together by date, and inside of that date, I need them grouped by location.
It's important that it has a title and data key
I have data in this format:
[ { game_id: 1171,
date: '2018-11-17',
location: 'Plaza'
},
{ game_id: 1189,
date: '2018-11-17',
location: 'Field - Kickball'
},
{ game_id: 1489,
date: '2018-11-16',
location: 'Field - Kickball'
},
{ game_id: 1488,
date: '2018-11-16',
location: 'Field - Soccer'
}
]
I need the output to show:
data = [{
title: "2018-11-17",
data: [{
title: "Field - Kickball",
data: [{
game_id: 1189,
date: '2018-11-17',
location: 'Field - Kickball'
}]
},
{
title: "Plaza",
data: [{
game_id: 1171,
date: '2018-11-17',
location: 'Plaza'
}]
}
]
},
{
title: "2018-11-16",
data: [{
title: "Field - Kickball",
data: [{
game_id: 1489,
date: '2018-11-16',
location: 'Field - Kickball'
}]
},
{
title: "Field - Soccer",
data: [{
game_id: 1488,
date: '2018-11-16',
location: 'Field - Soccer'
}]
}
]
}
]
I have already tried this
const games = [data here]
var groups = _(games)
.groupBy(x => x.date)
.map(value => {
return _.groupBy(value, 'location')
.map(({key, value}) => ({title: key, data: value}))
})
.map((value, key) => {
return ({title: value[Object.keys(value)[0]][0].date, data: value})
})

Use reduce like so:
const data = [{game_id:1171,date:'2016-11-17T05:00:00.000Z',time:'08:00:00',category:'Ch B Coed',background:'#FFFF00',textcolor:'#000000',bold:0,sport:'Newcomb',location:'Plaza',text:'COL2 vs. USA1 1171'},{game_id:1189,date:'2016-11-17T05:00:00.000Z',time:'08:50:00',category:'Ch A Coed',background:'#FF33CC',textcolor:'#000000',bold:0,sport:'Kickball',location:'Field - Kickball',text:'COL1 vs. USA1 1189'},{game_id:1489,date:'2016-2-17T05:00:00.000Z',time:'04:50:00',category:'Ch B Coed',background:'#FF33CC',textcolor:'#000000',bold:0,sport:'Kickball',location:'Field - Kickball',text:'COL2 vs. USA3 1489'}];
const res = Object.values(data.reduce((acc, { date, ...rest }) => acc[date] ? { ...acc, [date]: { ...acc[date], data: acc[date].data.concat({ date, ...rest })} } : { ...acc, [date]: { title: date, data: [{ date, ...rest }] }}, {}));
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }

Pardon me if I am missing something but this to me it seems can be done in somewhat simpler manner via Array.reduce:
let data = [{ game_id: 1171, date: '2016-11-17T05:00:00.000Z', time: '08:00:00', category: 'Ch B Coed', background: '#FFFF00', textcolor: '#000000', bold: 0, sport: 'Newcomb', location: 'Plaza', text: 'COL2 vs. USA1 1171' }, { game_id: 1189, date: '2016-11-17T05:00:00.000Z', time: '08:50:00', category: 'Ch A Coed', background: '#FF33CC', textcolor: '#000000', bold: 0, sport: 'Kickball', location: 'Field - Kickball', text: 'COL1 vs. USA1 1189' }, { game_id: 1489, date: '2016-2-17T05:00:00.000Z', time: '04:50:00', category: 'Ch B Coed', background: '#FF33CC', textcolor: '#000000', bold: 0, sport: 'Kickball', location: 'Field - Kickball', text: 'COL2 vs. USA3 1489' } ]
let result = data.reduce((r, c) => {
r[c.date] = r[c.date] || {title: c.date, data: []}
r[c.date].data = [...(r[c.date].data || []), c]
return r
}, {})
console.log(Object.values(result))

Related

React js time conversion in time dropdown

I need to display below options in material UI dropdown
from 5 to 60 dropdown should display 5m,10m,15m,20m,,and so on and for 720 and 1440 it should display 12h and 24h
I am using below code to set state in react
` let timeoption = "5,10,15,20,25,30,40,45,60,720,1440";
setTimeOption(timeoption.split(',').map(option => {
return {
id: option,
label: `${option}m`
}
}));`
for 720,1440 label should display in "h" instead of "m"
let timeoption = "5,10,15,20,25,30,40,45,60,720,1440"; setTimeOption(timeoption.split(',').map(option => { return { id: option, label: ${option}m } }));
You can use a ternary operator in label to assign m or h based on the time value.
let timeoption = "5,10,15,20,25,30,40,45,60,720,1440";
setTimeOption(timeoption.split(',').map(option => ({
id: option,
label: `${option}${option <= 60 ? 'm' : 'h'}`
})));
this is the output produced:
[
{ id: '5', label: '5m' },
{ id: '10', label: '10m' },
{ id: '15', label: '15m' },
{ id: '20', label: '20m' },
{ id: '25', label: '25m' },
{ id: '30', label: '30m' },
{ id: '40', label: '40m' },
{ id: '45', label: '45m' },
{ id: '60', label: '60m' },
{ id: '720', label: '720h' },
{ id: '1440', label: '1440h' }
]

Filter does not return the correct result

I have this array and I created this function that return me the filtered array:
const result = [{
key: 'A',
title: 'titleA',
data: [{
name: 'miael',
id: 'id4',
},
{
name: 'top',
id: 'id2',
}
]
},
{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
{
name: 'tomato',
id: 'id123',
}
]
},
]
const doSearch = (data) => result.filter(entry =>
entry.data.some(item =>
item.name
.toString()
.toLowerCase()
.includes(data.toString().toLowerCase().trim()),
),
);
console.log(doSearch('mich'));
This works, but it also returns results that do not contain the searched word 'mic'
if I search for mic, I expect this result:
[{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
]
}],
what am I doing wrong?
A couple of changes should make this work the way you wish.
Turning doSearch into a function.
Adding a searchFor parameter to the doSearch() function and passing to the .includes() call.
Using Array.reduce() to create the output array. Items are only added if they include the searchFor value.
const input = [{ key: 'A', title: 'titleA', data: [{ name: 'miael', id: 'id4', }, { name: 'top', id: 'id2', } ] }, { key: 'B', title: 'titleB', data: [{ name: 'mich1', id: 'id12', }, { name: 'tomato', id: 'id123', } ] }, ]
const doSearch = (searchFor, arr) => arr.reduce((acc, { key, title, data }) => {
const filteredData = data.filter(({ name }) => {
return name.toLowerCase().includes(searchFor.toLowerCase())
});
if (filteredData.length > 0) {
acc.push({ key, title, data: filteredData });
}
return acc;
}, []);
console.log(doSearch('mic', input ));
You can keep your current logic and add a map with the same filter for entry.data:
const result = [{
key: 'A',
title: 'titleA',
data: [{
name: 'miael',
id: 'id4',
},
{
name: 'top',
id: 'id2',
}
]
},
{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
{
name: 'tomato',
id: 'id123',
}
]
},
]
function nameFilter(item, data) {
return item.name
.toString()
.toLowerCase()
.includes(data.toString().toLowerCase().trim())
}
const doSearch = (data) => result.filter(entry =>
entry.data.some(item =>
nameFilter(item, data)
),
).map(entry => ({
...entry,
data: entry.data.filter(item => nameFilter(item, data))
}));
console.log(doSearch('mich'));

How to extract and group an array by key

I am building an Angular 9 app.
In this app I have a dynamically fetched JSON array. I need to take the JSON array and then extract and group based upon the keys. Example array:
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}];
I can mange to extract individual keys but I want to have a method that takes any JSON array and then extract and group per key.
This is my code for extracting individual keys. I had to hard code the key name (title).
groupByKey(array, key) {
return array.map(a => a.title);
}
This is what I want to transform the original JSON array to:
[{
header: "title",
rows: ["Product A", "Product B", "Product C", "Product D"]
}, {
header: "price",
rows: [234, 100, 344, 222]
}, {
header: "cost",
rows: [234, 200, 55, 332]
}]
You can perform a reduce operation on the array, using an object to store all the values of each key.
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}];
function group(arr){
return Object.values(
arr.reduce((acc,curr)=>{
Object.entries(curr).forEach(([k,v])=>{
(acc[k] = acc[k] || {header: k, rows: []}).rows.push(v);
});
return acc;
}, {})
);
}
console.log(group(collection));
The simplest solution is:
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}];
const transform = inputArray => {
const headers = inputArray && inputArray[0] && Object.keys(inputArray[0]);
return headers.map(header =>
({header: header, rows: collection.map(item => item[header])}));
}
console.log(transform(collection));
Out:=>
[
{
header: 'title',
rows: [ 'Product A', 'Product B', 'Product C', 'Product D' ]
},
{
header: 'price',
rows: [ 234, 100, 344, 222 ]
},
{
header: 'cost',
rows: [ 234, 200, 55, 332 ]
}
]
You could do it non functional
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}]
const keys = Object.keys(collection[0]);
const arr = []
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const obj = {
header: null,
rows: []
};
for (let item of collection) {
obj['header'] = key;
obj['rows'].push(item[key])
}
arr.push(obj)
}
console.log(arr)
Approach using a Map, a simple loop and spread of Map#values()
const map = new Map(Object.keys(collection[0]).map(k => [k, {header:k, rows:[]}]));
collection.forEach(el =>Object.entries(el).forEach(([k, v]) => map.get(k).rows.push(v)))
console.log( [...map.values()])
<script>
const collection=[{title:"Product A",price:234,cost:234},{title:"Product B",price:100,cost:200},{title:"Product C",price:344,cost:55},{title:"Product D",price:222,cost:332}];
</script>
This function will simply take in the entire collection and does not need a key. It assumes that each object in the collection has the same keys.
const groupByKey = (collection) => {
const result = []
// return empty array if collection is empty
if (collections.length === 0) return result
// assumes that all json in the collection will have the same properties
const keys = Object.keys(collection[0])
keys.forEach(key => {
const row = []
collection.forEach(json => {
row.push(json[key])
})
const formattedJSON = {
header: key,
row: row
}
result.push(formattedJSON)
})
return result
}
As a note, in your groupByKey function, to get the key dynamically, you can do:
a[title]
because a.title will literally look for a key called "title".

Restructure an array with lodash by leveraging _.map and ._groupBy

I am looking to restructure an array of objects with lodash.
I've been trying to adapt the many examples found online without any luck. It seems I would have to use a combination of _.map and ._groupBy but I can't really wrap my head around this.
Any help is appreciated!
Initial array:
const entries = [
{
year: '2019',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 20, label: 'color' },
{ name: 'green', amount: 12, label: 'color' },
],
},
{
year: '2020',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 3, label: 'color' },
],
},
]
Restructured array:
[
{
id: 'red',
data: [
{ year: '2019', amount: 1 },
{ year: '2020', amount: 1 },
],
},
{
id: 'yellow',
data: [
{ year: '2019', amount: 20 },
{ year: '2020', amount: 3 },
],
},
{
id: 'green',
data: [
{ year: '2019', amount: 12 },
],
},
]
You could chain the whole operations with flatMap, groupBy and mapping.
const entries = [{ year: '2019', children: [{ name: 'red', amount: 1, label: 'color' }, { name: 'yellow', amount: 20, label: 'color' }, { name: 'green', amount: 12, label: 'color' }] }, { year: '2020', children: [{ name: 'red', amount: 1, label: 'color' }, { name: 'yellow', amount: 3, label: 'color' }] }],
result = _(entries)
.flatMap(({ year, children }) => _.map(children, ({ name: id, amount }) => ({ year, id, amount })))
.groupBy('id')
.map((data, id) => ({ id, data: _.map(data, ({ year, amount }) => ({ year, amount })) }))
.value();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
There are probably quite a few different ways of doing this, however, I find the best approach is:
Flatten the children to one array.
Use _.groupBy to create a map of these entries keyed on name.
Use _.entries to get an array of keys and values for the map.
Finally use _.map to transform these entries into our desired output.
const entries = [
{
year: '2019',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 20, label: 'color' },
{ name: 'green', amount: 12, label: 'color' },
],
},
{
year: '2020',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 3, label: 'color' },
],
},
]
// Step 1
let flattenedChildren = _.flatMap(entries, e => e.children.map(c => { return { ...c, year: e.year } }));
// Step 2
let entryMap = _.groupBy(flattenedChildren , "name");
// Step 3
let mapEntries = _.entries(entryMap);
// Step 4
let result = _.map(mapEntries , ([id, items]) => { return { id, data: items.map(item => _.pick(item, ["amount", "year"]))} });
console.log("Result:", result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Get the aggregate size of nested object

I have the following JS object:
[
{
title: "cat1",
children: [
{
title: "Monday",
children: [
{ title: "size1", size: 120 },
{ title: "size2", size: 75 },
{ title: "size3", size: 45 }
]
},
{
title: "Tuesday",
children: [
{ title: "size1", size: 105 },
{ title: "size2", size: 45 },
{ title: "size3", size: 75 }
]
}
]
},
{
title: "cat2",
children: [
{
title: "Monday",
children: [
{ title: "size1", size: 60 },
{ title: "size2", size: 40 }
]
}
]
}
];
and want to evaluate the aggregate of the size in each nest. Say I will get
[
{
title: "cat1",
size: 465,
children: [
{
title: "Monday",
size: 240
},
{
title: "Tuesday",
size: 225
}
]
},
{
title: "cat2",
size: 200,
children: [
{
title: "Monday",
size: 200
}
]
}
];
So far I have the following code, but it doesn't work if the nest is two or more. I guess it's because of something related to "call by reference" but not sure how to fix it. Could anyone give me a help?
const getFrequency = (item: any) => {
console.log(item);
if (item[0].children[0].children === undefined) {
item.map((item2: any) => ({
title: item2.title,
size: item2.children.reduce((prev: number, curr: any) => prev + curr.size, 0)
}));
} else {
item.map((item2: any) => ({
title: item2.title,
children: item2.children,
size: getFrequency(item2.children)
}));
}
return item;
};
const freqData = getFrequency(nestedData);
EDIT: It has to be capable of recursively processing data with more than two nests.
const arr = [{
title: "cat1",
size: 465,
children: [{
title: "Monday",
size: 240
},
{
title: "Tuesday",
size: 225
}
]
},
{
title: "cat2",
size: 200,
children: [{
title: "Monday",
size: 200
}]
}
];
arr.map(el => {
const childSize = el.children.reduce((acc, item) => acc += item.size, 0);
return { ...el,
size: childSize
}
});
console.log(arr)

Categories