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

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>

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' }
]

Getting an array from array of array objects

I have an array which consists of an array objects as shown:
dataArr = [
{
id: 1,
arrObj: [
{
id: 11,
label: 'apple'
},
{
id: 12,
label: 'ball'
}
]
},
{
id: 2,
arrObj: [
{
id: 21,
label: 'car'
},
{
id: 22,
label: 'dog'
}
]
}
];
I need to extract an array consisting of only arrObj objects:
var newArr = [
{
id: 11,
label: 'apple'
},
{
id: 12,
label: 'ball'
},
{
id: 21,
label: 'car'
},
{
id: 22,
label: 'dog'
}
];
Tried using reduce method unsuccessfully:
dataArr.reduce((previousValue, currentValue, currentIndex, array) => {
return previousValue. arrObj.concat(currentValue.arrObj)
});
Let me know how to do this. Thanks
let dataArr = [
{
id: 1,
arrObj: [
{
id: 11,
label: 'apple'
},
{
id: 12,
label: 'ball'
}
]
},
{
id: 2,
arrObj: [
{
id: 21,
label: 'car'
},
{
id: 22,
label: 'dog'
}
]
}
];
let result = dataArr.flatMap(e => e.arrObj)
console.log(result)
You were pretty close.
There's no arrObj property in your result, it's just an array.
You need to provide an empty array as the initial value argument to reduce().
const dataArr = [{
id: 1,
arrObj: [{
id: 11,
label: 'apple'
},
{
id: 12,
label: 'ball'
}
]
},
{
id: 2,
arrObj: [{
id: 21,
label: 'car'
},
{
id: 22,
label: 'dog'
}
]
}
];
const newArr = dataArr.reduce((previousValue, currentValue) => {
return previousValue.concat(currentValue.arrObj)
}, []);
console.log(newArr);
You could use in one line by using Spread Operator...
dataArr.reduce((previousValue, currentValue) => [...previousValue?.arrObj, ...currentValue?.arrObj]);
Tip: use Optional chaining ?. in case there is no property arrObj!
You can use the Array#reduce method as follows:
let dataArr = [
{
id: 1,
arrObj: [
{
id: 11,
label: 'apple'
},
{
id: 12,
label: 'ball'
}
]
},
{
id: 2,
arrObj: [
{
id: 21,
label: 'car'
},
{
id: 22,
label: 'dog'
}
]
}
];
let newArr = dataArr.reduce((acc,cur) => [...acc, ...cur.arrObj], []);
console.log( newArr );

Count Unique Value from Two object of array in Javascript

Given an array of categories and an array of entries, creates an array of objects with a category name and an entry count. Consider id is equal to categoryId.
var categories = [
{ name: 'Cats', id: 10 },
{ name: 'Dogs', id: 20 },
];
var entries = [
{categoryId: 10, name: 'Fluffy'},
{categoryId: 10, name: 'Spot'},
{categoryId: 10, name: 'Lil'},
{categoryId: 20, name: 'Tom'},
{categoryId: 20, name: 'Buck'},
{categoryId: 20, name: 'Flo'},
{categoryId: 20, name: 'Cheek'},
{categoryId: 10, name: 'Stan'},
{categoryId: 20, name: 'Stila'}
]
Expected Output: [{ name:'Cats', count: 4 }, { name:'Dogs', count: 5 }];
I wrote it like this below, but there seem to be a performance problem when you try to run it through hundreds of categories and tens of thousands of entries.
const categoriesByEntryCount = (categories, entries) =>
categories.map(category => ({
name: category.name,
count: entries.filter(entry => entry.categoryId === category.id).length,
}));
My question is there another way to write or implement this ?
You need to use Maps in all possible places.
var categories = new Map();
categories.set(10, 'Cats');
categories.set(20, 'Dogs');
var entries = [
{ categoryId: 10, name: 'Fluffy' },
{ categoryId: 10, name: 'Spot' },
{ categoryId: 10, name: 'Lil' },
{ categoryId: 20, name: 'Tom' },
{ categoryId: 20, name: 'Buck' },
{ categoryId: 20, name: 'Flo' },
{ categoryId: 20, name: 'Cheek' },
{ categoryId: 10, name: 'Stan' },
{ categoryId: 20, name: 'Stila' },
];
console.log(Array.from(
entries.reduce(
(m, { categoryId, name }) =>
m.set(categoryId, (m.get(categoryId) || 1) + 1),
new Map()
),
([k, v]) => ({ name: categories.get(k), count: v })
));
const categories = [ { name: 'Cats', id: 10 }, { name: 'Dogs', id: 20 } ];
const entries = [ { categoryId: 10, name: 'Fluffy' }, { categoryId: 10, name: 'Spot' }, { categoryId: 10, name: 'Lil' }, { categoryId: 20, name: 'Tom' }, { categoryId: 20, name: 'Buck' }, { categoryId: 20, name: 'Flo' }, { categoryId: 20, name: 'Cheek' }, { categoryId: 10, name: 'Stan' }, { categoryId: 20, name: 'Stila' } ];
// get number of occurences of each category in entries
const categoriesCount = entries.reduce((countMap, { categoryId }) =>
countMap.set( categoryId, 1 + (countMap.get(categoryId) || 0) )
, new Map);
// iterate over categories and return name and count in categoriesCount
const res = categories.map(({ name, id }) =>
({ name, count: categoriesCount.get(id) })
);
console.log(res);
We can do like below with time complexity O(M + N)
var categories = [
{ name: 'Cats', id: 10 },
{ name: 'Dogs', id: 20 },
];
var entries = [
{categoryId: 10, name: 'Fluffy'},
{categoryId: 10, name: 'Spot'},
{categoryId: 10, name: 'Lil'},
{categoryId: 20, name: 'Tom'},
{categoryId: 20, name: 'Buck'},
{categoryId: 20, name: 'Flo'},
{categoryId: 20, name: 'Cheek'},
{categoryId: 10, name: 'Stan'},
{categoryId: 20, name: 'Stila'}
]
const categoriesByEntryCount = (categories, entries) => {
const entriesHash = entries.reduce((acc, ele) => {
acc[ele.categoryId] = acc[ele.categoryId] ? acc[ele.categoryId] + 1 : 1;
return acc;
}, {});
return categories.map(category => ({
name: category.name,
count: entriesHash[category.id],
}));
}
console.log(categoriesByEntryCount(categories, entries))
This is obviously a reducing job.
var categories = [ { name: 'Cats', id: 10 }
, { name: 'Dogs', id: 20 }
],
entries = [ {categoryId: 10, name: 'Fluffy'}
, {categoryId: 10, name: 'Spot'}
, {categoryId: 10, name: 'Lil'}
, {categoryId: 20, name: 'Tom'}
, {categoryId: 20, name: 'Buck'}
, {categoryId: 20, name: 'Flo'}
, {categoryId: 20, name: 'Cheek'}
, {categoryId: 10, name: 'Stan'}
, {categoryId: 20, name: 'Stila'}
],
result = entries.reduce((cs,e) => ( cs.map(c => c.id === e.categoryId ? c.count ? c.count++
: c.count = 1
: c)
, cs
), categories);
console.log(result);
You may complain that the result includes the id property but that's just good.

How to nest multiple groupby with loadash/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))

JavaScript - Flatten Array of Nested Objects

I am working with an array of objects and need to flatten and re-assign the data set into an array of single objects using the object values as the new object keys.
Incoming Data:
results = [
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Jim' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Fredsburg' },
],
},
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Greg' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Waverly' },
],
},
];
Desired Result:
output = [
{
id: 1, FirstName: 'Jim', LastName: 'Jones', City: 'Fredsburg',
},
{
id: 2, FirstName: 'Greg', LastName: 'Jones', City: 'Waverly',
},
];
My current attempt gets me to placing the updated key/value pairs in the correct order, but creates a huge array of single objects.
Current Code:
const results = [];
surveyResults.map(s => s.surveyValues)
.forEach(s => Object.entries(s)
.forEach(([key, value]) => {
Object.entries(value)
.forEach(([k, v]) => {
if (k === 'name') {
results.push({
id: value.id,
[v]: value.value.toString(),
});
}
});
}));
Current Code Output:
[
{
id: 135, FirstName: 'Jim',
},
{
id: 136, LastName: 'Jones',
},
{
id: 137, City: 'Fredsburg',
},
{
id: 135, FirstName: 'Greg',
},
{
id: 136, LastName: 'Jones',
},
{
id: 137, City: 'Waverly',
},
]
What am I missing and why is my code not created the new arrays of objects as desired?
You may use map and reduce to accomplish this. I'm also accessing an index i in the map function's callback to set the id property and string.replace() to strip the space from the FirstName and LastName keys.
const results = [
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Jim' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Fredsburg' },
],
},
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Greg' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Waverly' },
],
},
];
const result = results.map((e, i) =>
e.surveyValues.reduce((a, e) => {
a[e.name.replace(" ", "")] = e.value;
return a;
}, {id: i + 1})
);
console.log(result);
Create each object in the outer map() loop, and add properties to it in the inner forEach().
surveyResults = [
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Jim' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Fredsburg' },
],
},
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Greg' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Waverly' },
],
},
];
const results = surveyResults.map((s, i) => {
let v = s.surveyValues;
let obj = {id: i};
v.forEach(item => obj[item.name.replace(/\s+/g, "")] = item.value.toString());
return obj;
});
console.log(results);

Categories