GroupBy with Multiple Columns using Lodash or Underscore in Javascript - javascript

Getting Problem while converting JSON structure. My JSON Structure is as below.
const member = [{
memberId: 4,
memberName: 'ABC',
age: 22,
eventId: 5,
eventName: 'Dance'
},
{
memberId: 4,
memberName: 'ABC',
age: 22,
eventId: 6,
eventName: 'Music'
},
{
memberId: 4,
memberName: 'ABC',
age: 22,
eventId: 7,
eventName: 'FootBall'
},
{
memberId: 5,
memberName: 'PQR',
age: 24,
eventId: 6,
eventName: 'Music'
},
{
memberId: 5,
memberName: 'PQR',
age: 24,
eventId: 5,
eventName: 'Dance'
},
]
Here I have two members with associated events. And I want to convert JSON as follows.
const member = [
{
memberId: 4,
memberName: 'ABC',
age: 22,
events: [
{
id: 5,
name: 'Dance'
},
{
id: 6,
name: 'Music'
},
{
id: 7,
name: 'FootBall'
}
]
},
{
memberId: 5,
memberName: 'PQR',
age: 24,
events: [
{
id: 6,
name: 'Music'
},
{
id: 5,
name: 'Dance'
}
]
}
]
I tried creating the structure using below code but it doesn't provide as the desired output. It just creates two Key-Value pair.
var result = _.chain(member)
.groupBy("memberId")
.pairs()
.map(function(currentItem) {
return _.object(_.zip(["memberId", "events"], currentItem));
})
.value();
I don't know how to add other values of JSON in the hierarchy.

After you group the items, map them. Take the 1st item of each group, and remove the event properties, and spread it. To get the the events data, map the group's items and take only the relevant event properties:
const member = [{"memberId":4,"memberName":"ABC","age":22,"eventId":5,"eventName":"Dance"},{"memberId":4,"memberName":"ABC","age":22,"eventId":6,"eventName":"Music"},{"memberId":4,"memberName":"ABC","age":22,"eventId":7,"eventName":"FootBall"},{"memberId":5,"memberName":"PQR","age":24,"eventId":6,"eventName":"Music"},{"memberId":5,"memberName":"PQR","age":24,"eventId":5,"eventName":"Dance"}]
const result = _(member)
.groupBy('memberId')
.map(group => ({
..._.omit(_.head(group), ['eventId', 'eventName']),
events: _.map(group, o => ({ id: o.eventId, name: o.eventName }))
}))
.value();
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
And the same solution using underscore:
const member = [{"memberId":4,"memberName":"ABC","age":22,"eventId":5,"eventName":"Dance"},{"memberId":4,"memberName":"ABC","age":22,"eventId":6,"eventName":"Music"},{"memberId":4,"memberName":"ABC","age":22,"eventId":7,"eventName":"FootBall"},{"memberId":5,"memberName":"PQR","age":24,"eventId":6,"eventName":"Music"},{"memberId":5,"memberName":"PQR","age":24,"eventId":5,"eventName":"Dance"}]
const result = _.chain(member) // <- change for underscore
.groupBy('memberId')
.map(group => ({
..._.omit(_.head(group), ['eventId', 'eventName']),
events: _.map(group, o => ({ id: o.eventId, name: o.eventName }))
}))
.value();
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>

Related

Get unique values from array values in json array

I'm writing a code where I need to return uniques values from a JSON array. Here my challenge is, I've got these values as an array for one of the keys.
Here is my code.
let mobilePhones = [{
id: 1,
brand: ["B1", "B2"]
}, {
id: 2,
brand: ["B2"]
}, {
id: 3,
brand: ["B1", "B2"]
}, {
id: 4,
brand: ["B1"]
}, {
id: 5,
brand: ["B2", "B1"]
}, {
id: 6,
brand: ["B3"]
}]
let allBrandsArr = mobilePhones.map(row => {
return row.brand;
});
let uniqueBrands = allBrandsArr.filter((item, index, arry) => (arry.indexOf(item) === index));
console.log(JSON.stringify(uniqueBrands));
Here my expected result is to get ["B1", "B2", "B3"]. Please let me know how can I achieve this.
Updated new sample data:
let mobilePhones = [{
id: 1,
brand: ["B1, B2"]
}, {
id: 2,
brand: ["B2"]
}, {
id: 3,
brand: ["B1, B2"]
}, {
id: 4,
brand: ["B1"]
}, {
id: 5,
brand: ["B2, B1"]
}, {
id: 6,
brand: ["B3"]
}]
let allBrandsArr = mobilePhones.map(row => {
return row.brand;
});
Thanks
You need to use flat for merge sub array then your code was good:
let mobilePhones = [{
id: 1,
brand: ["B1, B2"]
}, {
id: 2,
brand: ["B2"]
}, {
id: 3,
brand: ["B1, B2"]
}, {
id: 4,
brand: ["B1"]
}, {
id: 5,
brand: ["B2, B1"]
}, {
id: 6,
brand: ["B3"]
}]
let allBrandsArr = mobilePhones.map(row => {
return row.brand[0].split(',').map(function(item) {
return item.trim();
});
});
let uniqueBrands = allBrandsArr.flat().filter((item, index, arry) => (arry.indexOf(item) === index));
console.log(JSON.stringify(uniqueBrands));
Reference:
Array.prototype.flat()
After new Data posted i add split with trim.
Reference:
String.prototype.split()
String.prototype.trim()
You can use .flatMap to get all the brand values and pass it to a Set to make it unique.
const uniqueBrands = [...new Set(mobilePhones.flatMap(({
brand
}) => brand))];
let mobilePhones = [{
id: 1,
brand: ["B1", "B2"]
}, {
id: 2,
brand: ["B2"]
}, {
id: 3,
brand: ["B1", "B2"]
}, {
id: 4,
brand: ["B1"]
}, {
id: 5,
brand: ["B2", "B1"]
}, {
id: 6,
brand: ["B3"]
}]
const unique = [...new Set(mobilePhones.flatMap(({
brand
}) => brand))];
console.log(unique);

How to flatten the nested Array?

How do I flatten the nested Array in the Array?
Here is the example input Array,
const input = [
{
id: 1,
name: 'Charles',
otherFields: [{
id: 2,
name: 'Pung',
}, {
id: 3,
name: 'James',
}]
}, {
id: 4,
name: 'Charles',
otherFields: [{
id: 5,
name: 'Pung',
}, {
id: 6,
name: 'James',
}]
}
]
Output Array I want to get.
[{
id: 1,
name: 'Charles'
}, {
id: 2,
name: 'Pung',
}, {
id: 3,
name: 'James',
}, {
id: 4,
name: 'Charles'
}, {
id: 5,
name: 'Pung',
}, {
id: 6,
name: 'James',
}]
I want to somehow get the output in one statement like
input.map((sth) => ({...sth??, sth.field...})); // I'm not sure :(
With flatMap you can take out the otherFields property, and returning an array containing the parent item and the other array:
const input = [{
id: 1,
name: 'Charles',
otherFields: [{
id: 2,
name: 'Pung',
}, {
id: 3,
name: 'James',
}]
}];
console.log(
input.flatMap(({ otherFields, ...item }) => [item, ...otherFields])
);
For more than one level, you could take a recursive approach of flattening.
const
flat = ({ otherFields = [], ...o }) => [o, ...otherFields.flatMap(flat)],
input = [{ id: 1, name: 'Charles', otherFields: [{ id: 2, name: 'Pung' }, { id: 3, name: 'James', otherFields: [{ id: 4, name: 'Jane' }] }] }],
result = input.flatMap(flat);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to make the property of one array property of another (code optimization)

That's what I have
const users = [
{ id: 1, name: 'Mike', postIds: [11, 22] },
{ id: 2, name: 'Dan', postIds: [33] },
{ id: 3, name: 'Lance', postIds: [44] },
];
const posts = [
{ id: 11, title: 'How good is he' },
{ id: 22, title: 'How fast is he' },
{ id: 33, title: 'How to make it faster' },
{ id: 44, title: 'How can he do it' },
];
That's what I need to get in the output
const expectedResult = [
{
id: 1,
name: 'Mike',
posts: [
{ id: 11, title: 'How good is he' },
{ id: 22, title: 'How fast is he' },
]
},
{
id: 2,
name: 'Dan',
posts: [{ id: 33, title: 'How to make it faster' }]
},
{
id: 3,
name: 'Lance',
posts: [{ id: 44, title: 'How can he do it' }]
},
]
That's what I tried. It works, but it's stupid and I think it can be done in one operation. Please check what can I do make to it cleaner
const users = [
{ id: 1, name: 'Mike', postIds: [11, 22] },
{ id: 2, name: 'Dan', postIds: [33] },
{ id: 3, name: 'Lance', postIds: [44] },
];
const posts = [
{ id: 11, title: 'How good is he' },
{ id: 22, title: 'How fast is he' },
{ id: 33, title: 'How to make it faster' },
{ id: 44, title: 'How can he do it' },
];
let updUsers = users.map(obj => ({ ...obj,
posts: [...posts]
}))
const output = updUsers.map(
user => ({
...user,
posts: user.posts.filter(
post => user.postIds.includes(post.id)
)
})
);
const expectedOut = output.map(({
id,
name,
posts
}) => ({
id,
name,
posts
}))
console.log(expOut)
Turn the posts array into a mapping of post ID -> post object for faster lookup:
const postMap = new Map(posts.map((p) => [p.id, p]));
const expectedResult = users.map((u) => {
const newU = { ...u, posts: u.postIds.map((id) => postMap.get(id)) };
delete newU.postIds; // Remove the undesired `postIds` property from the copy
return newU;
});
console.log(expectedResult);
You can filter posts when you're mapping users rather than doing it as a second pass.
const users = [
{ id: 1, name: 'Mike', postIds: [11, 22] },
{ id: 2, name: 'Dan', postIds: [33] },
{ id: 3, name: 'Lance', postIds: [44] },
];
const posts = [
{ id: 11, title: 'How good is he' },
{ id: 22, title: 'How fast is he' },
{ id: 33, title: 'How to make it faster' },
{ id: 44, title: 'How can he do it' },
];
let expOut = users.map(({id, name, postIds}) => ({ id, name,
posts: posts.filter(({id}) => postIds.includes(id))
}))
console.log(expOut)
This is another naive method but time complexity is more.
P.S.: Time complexity is n^3
const users = [
{ id: 1, name: 'Mike', postIds: [11, 22] },
{ id: 2, name: 'Dan', postIds: [33] },
{ id: 3, name: 'Lance', postIds: [44] },
];
const posts = [
{ id: 11, title: 'How good is he' },
{ id: 22, title: 'How fast is he' },
{ id: 33, title: 'How to make it faster' },
{ id: 44, title: 'How can he do it' },
];
let updUsers = users.map(function(obj){
let postsArr = [];
for(i=0; i<obj.postIds.length; i++){
const postArr = posts.find((item) => item.id == obj.postIds[i]);
postsArr.push(postArr);
};
return{'id':obj.id,'name':obj.name,'posts':postsArr};});
console.log(updUsers);
You can first convert your posts array to key value pairs and take object using Object.fromEntries, now just map it. Here is an implementation:
const users = [ { id: 1, name: 'Mike', postIds: [11, 22] }, { id: 2, name: 'Dan', postIds: [33] }, { id: 3, name: 'Lance', postIds: [44] },];
const posts = [ { id: 11, title: 'How good is he' }, { id: 22, title: 'How fast is he' }, { id: 33, title: 'How to make it faster' }, { id: 44, title: 'How can he do it' },];
//convert posts to object and then map it:
const postObjects = Object.fromEntries(posts.map(p=>[p.id, p]));
const result = users.map(({id, name, postIds})=>({id, name, posts:postIds.map(p=>postObjects[p])}));
console.log(result);
We can simplify this using Array.prototype.reduce and Array.prototype..filter and use a Set for finding the right posts for a user for faster lookup:
const users = [
{ id: 1, name: 'Mike', postIds: [11, 22] },
{ id: 2, name: 'Dan', postIds: [33] },
{ id: 3, name: 'Lance', postIds: [44] },
];
const posts = [
{ id: 11, title: 'How good is he' },
{ id: 22, title: 'How fast is he' },
{ id: 33, title: 'How to make it faster' },
{ id: 44, title: 'How can he do it' },
];
const mapUsersByPost = (users, posts) => {
return users.reduce((acc, {id, name, postIds}) => {
const filteredPosts = posts.filter(({id}) => new Set(postIds).has(id));
acc.push({ id, name, posts: filteredPosts});
return acc;
}, []);
}
console.log(mapUsersByPost(users, posts));

refactoring assistance with reducing some array data objects in JavaScript

I need to reduce data in profiles array in a way such that the final object groups the data in profile obj based on the favorite movie and the users that liked/favorited the movie.
I want something like:
{
'Forrest Gump': ["Nicholas Lain"],
'Planet Earth 1': ["Jane Jones", "Matthew Johnson"]
}
from the following data objects:
const profiles = [
{
id: 1,
userID: '1',
favoriteMovieID: '1',
},
{
id: 2,
userID: '2',
favoriteMovieID: '1',
},
{
id: 3,
userID: '4',
favoriteMovieID: '5',
}
];
const users = {
1: {
id: 1,
name: 'Jane Cruz',
userName: 'coder',
},
2: {
id: 2,
name: 'Matthew Johnson',
userName: 'mpage',
}
};
const movies = {
1: {
id: 1,
name: 'Planet Earth 1',
},
2: {
id: 2,
name: 'Selma',
}
};
I need some ideas in refactoring the following code for it to go back to the users and movies object to grab their names from the IDs I have captured below. Instead of IDs, I need to capture the names.
profiles.reduce(function (acc, obj) {
let key = obj['favoriteMovieID']
if (!acc[key]) {
acc[key] = []
}
acc[key].push(obj.userID)
return acc
}, {})
Here is one technique, doing a fold on the profiles, grabbing the movie and person names inside the parameters, and then simply writing a new accumulator with that data. Note that there is a potential performance problem with this, as described in Rich Snapp's excellent article. If that causes you an actual issue, it's easy enough to change this to mutate the accumulator.
I added some additional data to show what happens when the user or the movie isn't in the appropriate lists. If that cannot ever happen, you can simplify the name and person declarations a bit. But I wouldn't recommend it, as things that "can never happen" in fact regularly do happen.
const groupNamesByMovie = (profiles, users, movies) =>
profiles .reduce ((
a, {userID, favoriteMovieID}, _, __,
{name} = movies [favoriteMovieID] || {name: 'Unknown Movie'},
{name: person} = users [userID] || {name: 'Unknown Person'}
) => ({
...a,
[name]: [... (a [name] || []), person]
}), {})
const profiles = [{id: 1, userID: "1", favoriteMovieID: "1"}, {id: 2, userID: "2", favoriteMovieID: "1"}, {id: 3, userID: "4", favoriteMovieID: "5"}, {id: 4, userID: "6", favoriteMovieID: "5"}, {id: 5, userID: "5", favoriteMovieID: "7"}]
const users = {1: {id: 1, name: "Jane Cruz", userName: "coder"}, 2: {id: 2, name: "Matthew Johnson", userName: "mpage"}, 4: {id: 4, name: "Nicholas Lain", userName: "nlain"}, 5: {id: 5, name: "Fred Flintstone", userName: "bedrock1"}}
const movies = {1: {id: 1, name: 'Planet Earth 1'}, 2: {id: 2, name: 'Selma'}, 5: {id: 5, name: 'Forrest Gump'}}
console .log (
groupNamesByMovie (profiles, users, movies)
)
Note that the arguments _ and __ are just meant to be placeholders, since we don't care about reduce's index and array parameters.
Update
There was a request for clarification. For comparison, here's a more imperative version of this same idea:
const getNamesByMovie = (profiles, users, movies) =>
profiles .reduce ((acc, {userID, favoriteMovieID}) => {
const movie = movies [favoriteMovieID]
const name = movie ? movie.name : 'Unknown Movie'
const user = users [userID]
const person = user ? user.name : 'Unknown Person'
const fans = acc [name] || []
return {
... acc,
[name]: [... fans, person]
}
}, {})
And if you wanted to avoid that potential performance problem, you could replace the return statement with something like this:
acc [name] = fans
fans .push (person)
return acc
Either of these does the same sort of thing as the original above. I choose that initial style because I don't like mutating the accumulator object, preferring to always create a new version... and because I prefer working with expressions over statements. But this style does take some getting used to.
You also asked how we passed additional parameters to the reduce callback. We don't. Instead we define some additional parameters and initialize them based on the earlier parameters.
const profiles = [
{
id: 1,
userID: '1',
favoriteMovieID: '1',
},
{
id: 2,
userID: '2',
favoriteMovieID: '1',
},
{
id: 3,
userID: '4',
favoriteMovieID: '5',
}
];
const users = {
1: {
id: 1,
name: 'Jane Cruz',
userName: 'coder',
},
2: {
id: 2,
name: 'Matthew Johnson',
userName: 'mpage',
}
};
const movies = {
1: {
id: 1,
name: 'Planet Earth 1',
},
2: {
id: 2,
name: 'Selma',
}
};
const singleProfileNameFavoriteMovie = profiles.map(profile => `${users[profile.userID].name}'s favorite movie is ${movies[profile.favoriteMovieID].name}` )
const profiles = [
{
id: 1,
userID: '1',
favoriteMovieID: '1',
},
{
id: 2,
userID: '2',
favoriteMovieID: '1',
},
{
id: 3,
userID: '4',
favoriteMovieID: '5',
},
{
id: 4,
userID: '5',
favoriteMovieID: '2',
},
{
id: 5,
userID: '3',
favoriteMovieID: '5',
},
{
id: 6,
userID: '6',
favoriteMovieID: '4',
},
];
const users = {
1: {
id: 1,
name: 'Jane Cruz',
userName: 'coder',
},
2: {
id: 2,
name: 'Matthew Johnson',
userName: 'mpage',
},
3: {
id: 3,
name: 'Autumn Green',
userName: 'user123',
},
4: {
id: 4,
name: 'John Doe',
userName: 'user123',
},
5: {
id: 5,
name: 'Lauren Carlson',
userName: 'user123',
},
6: {
id: 6,
name: 'Nicholas Lain',
userName: 'user123',
},
};
const movies = {
1: {
id: 1,
name: 'Planet Earth 1',
},
2: {
id: 2,
name: 'Selma',
},
3: {
id: 3,
name: 'Million Dollar Baby',
},
4: {
id: 4,
name: 'Forrest Gump',
},
5: {
id: 5,
name: 'Get Out',
},
};
const singleProfileFavoriteMovie = profiles.map(profile => `${users[profile.userID].name}'s favorite movie is ${movies[profile.favoriteMovieID].name}` );
console.log(singleProfileFavoriteMovie);

Searching values in array of objects /Javascript

Consider the following arrays of objects :
[{ id: 5, name: "Spain"},
{ id: 6, name: "Denmark"},
{ id: 7, name: "USA"},
{ id: 8, name: "Iceland"},
{ id: 9, name: "Greece"},
{ id: 10, name: "UK"},
{ id: 11, name: "Germany"},
{ id: 12, name: "Italy"}]
[{ id: 13, name: "US"},
{ id: 14, name: "GR"},
{ id: 15, name: "ESP"},
{ id: 16, name: "ICEL"},
{ id: 17, name: "DEN"},
{ id: 18, name: "UK"},
{ id: 19, name: "IT"},
{ id: 20, name: "GER"}]
I want to search through the first array and find the respective match for the second array. Or even better get one element from the second array and search for it against all the values in the first array .
EX. GER = { id: 11, name: "Germany"}
IT = { id: 12, name: "Italy"}
Is there a best practise for implementing this kind of search ?
If you are looking for a data structure, you could use an object with the reference to the objects of the first array.
You need to implement it by hand as long as you do not have a relation of the matching id.
countries = {
ESP: { id: 5, name: "Spain"},
DEN: { id: 6, name: "Denmark"},
US: { id: 7, name: "USA"},
ICEL: { id: 8, name: "Iceland"},
GR: { id: 9, name: "Greece"},
UK: { id: 10, name: "UK"},
GER: { id: 11, name: "Germany"},
IT: { id: 12, name: "Italy"}
}
Access via
countries.ICEL.name // Iceland
or
country = 'GR'
countries[country].name // Greece

Categories

Resources