refactoring assistance with reducing some array data objects in JavaScript - 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);

Related

Replace the records of particular category

There is one scenario where i need to replace the existing records from cached data with new incoming data source. Looking for the cleaner approach to handle the array operations.
For example:
var userCategory = [
{
id: 'platinum',
name: 'bob',
},
{
id: 'platinum',
name: 'bar',
},
{
id: 'platinum',
name: 'foo',
},
{
id: 'gold',
name: 'tom',
},
{
id: 'silver',
name: 'billy',
},
];
Here is new users of particular category
var newPlatinumUsers = [
{
id: 'platinum',
name: 'bob',
},
{
id: 'platinum',
name: 'mike',
},
];
This is the expected result needed:
var expected = [
{
id: 'platinum',
name: 'bob',
},
{
id: 'platinum',
name: 'mike',
},
{
id: 'gold',
name: 'tom',
},
{
id: 'silver',
name: 'billy',
},
];
I tried with filtering all the platinum user from existing records then added the new records but it looks verbose
Is there any cleaner approach like lodash operator??
Thanks for your time!!!
May you are looking for this.
function getUnique(arr){
// removing duplicate
let uniqueArr = [...new Set(arr)];
document.write(uniqueArr);
}
const array = ['acer','HP','Apple','Apple','something'];
// calling the function
getUnique(array);
Verify my answer if it help you.
Please find the Javascript implementation of the same
var userCategory = [
{ id: 'platinum', name: 'bob', },
{ id: 'platinum', name: 'bar', },
{ id: 'platinum', name: 'foo', },
{ id: 'gold', name: 'tom', },
{ id: 'silver', name: 'billy', },
];
var newPlatinumUsers = [
{ id: 'platinum', name: 'bob', },
{ id: 'platinum', name: 'mike', },
];
const result = [...newPlatinumUsers];
userCategory.forEach((node) => {
if(node.id !== 'platinum') {
result.push(node);
}
});
console.log(result);
With this solution you can change more than one category:
var userCategory = [
{id: 'platinum',name: 'bob'},
{id: 'platinum',name: 'bar'},
{id: 'platinum',name: 'foo'},
{id: 'gold',name: 'tom'},
{id: 'silver',name: 'billy'},
];
var newUsers = [
{id: 'platinum',name: 'bob'},
{id: 'platinum',name: 'mike'},
{id: 'gold',name: 'will'},
{id: 'gold',name: 'jerry'},
];
const idsToReplace = {}
const result = [...newUsers]
result.forEach(u => {
idsToReplace[u.id] = true
})
userCategory.forEach(u => {
if(!idsToReplace[u.id]){
result.push(u)
}
})
console.log(result)

Return a subarray with a value changed to reflect parent object

I am trying to change the value of single key in an associative array which is inside another assoc array using javascript.
I have an array like this:
let arr = [{
id: 4,
name: 'test',
docs: [{
id: 1,
name: 'abc'
},{
id: 2,
name: 'xyz'
}]
}, {
id: 8,
name: 'test2',
docs: [{
id: 5,
name: 'abc'
},{
id: 7,
name: 'xyz'
}]
}]
I want to change the value of name of xyz to xyz (test), where test is name key of parent object and get final array as Output:
[{
id: 1,
name: 'abc (test)'
},
{
id: 2,
name: 'xyz (test)'
},
{
id: 5,
name: 'abc (test2)'
},
{
id: 7,
name: 'xyz (test2)'
}]
I am using approach.
let docs = new Array();
arr.forEach((item, index) => {
let docx = item.documents.map(item1 => {
item1.name = item1.name + " ("+item.name+")";
});
docs.push(docx);
});
return docs;
this is returning array of undefined array.
Try a flatMap
let arr = [{ id: 4, name: 'test', docs: [{ id: 1, name: 'abc' },{ id: 2, name: 'xyz' }] }, { id: 8, name: 'test2', docs: [{ id: 5, name: 'abc' },{ id: 7, name: 'xyz' }] }]
const output = arr.flatMap(item =>
item.docs.map(({id,name}) => ({ id, name: `${name} (${item.name})` }))
)
console.log(output)
There is an issue in your data, the docs inner array contains an object with duplicate keys:
let arr = [{
id: 4,
name: 'test',
docs: [{
id: 1,
name: 'abc',
id: 2, // <-- duplicate key
name: 'xyz' // <-- duplicate key
}]
},
If I remove the duplication, you can use this code to create a new object with the name value updated to xyz123 if the original value was xyz:
const original = [{
id: 4,
name: 'test',
docs: [{
id: 1,
name: 'abc'
}, {
id: 2,
name: 'xyz'
}]
}, {
id: 8,
name: 'test2',
docs: [{
id: 1,
name: 'abc'
}, {
id: 2,
name: 'xyz'
}]
}];
const updates = original.map(currentObject => {
const newObject = Object.assign(currentObject);
const newDocs = newObject.docs.map(doc => {
const newDoc = Object.assign(doc);
if (newDoc.name === "xyz") {
newDoc.name = "xyz123";
}
return newDoc;
});
newObject.docs = newDocs;
return newObject
});
console.log(updates);

Compare two array of objects in javascript/angular and return as soon first condition satisfies (any lodash operator)

Say I have two arrays of objects:
let ar1 = [{
id: 1,
name: 'abc',
job: 'dev'
}, {
id: 2,
name: 'xyz',
job: 'qa'
},{
id: 3,
name: 'pqr',
job: 'dev'
}
];
let arr2 = [{
id: 1,
name: 'abc',
job: 'dev'
},{
id: 2,
name: 'zzz',
job: 'qa'
},{
id: 3,
name: 'pqr',
job: 'dev'
}];
I need to compare them so that I get 'true' and exit the comparison without iterating further as the name is different in the second element between two objects.
I tried with the following code:
private compareObjects(obj1, obj2) {
obj1.forEach((x) => {
obj2.forEach((y) => {
if (x.id === y.id) {
return (!_.isEqual(x, y));
}
});
});
}
I suggest reduce-ing the second array to what's needed for testing the first array: { id : name }. This is a single pass on the second array.
Then use find to test the first array with a single pass that halts on the first mismatch.
let ar1 = [{
id: 1,
name: 'abc',
job: 'dev'
}, {
id: 2,
name: 'xyz',
job: 'qa'
},{
id: 3,
name: 'pqr',
job: 'dev'
}
];
let arr2 = [{
id: 1,
name: 'abc',
job: 'dev'
},{
id: 2,
name: 'zzz',
job: 'qa'
},{
id: 3,
name: 'pqr',
job: 'dev'
}];
let index = arr2.reduce((acc, e) => {
acc[e.id] = e.name
return acc
}, {})
let firstMismatched = ar1.find(e => index[e.id] !== e.name)
console.log(firstMismatched)

GroupBy with Multiple Columns using Lodash or Underscore in 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>

JavaScript objects property to array

I have the following object:
const movies = {
1: {
id: 1,
name: 'Planet Earth',
},
2: {
id: 2,
name: 'Selma',
},
3: {
id: 3,
name: 'Million Dollar Baby',
},
4: {
id: 4,
name: 'Forrest Gump',
},
5: {
id: 5,
name: 'Get Out',
},
};
Then I want an array with only the property id. To do so I've tried something like:
const moviesArray = Object.values(movies);
const idArray = moviesArray.map(movie => Object.values(movie)[0]);
console.log(idArray);
It prints idArray properly but my question is if am I missing a method to solve this problem.
You could use the id property directly:
const
movies = { 1: { id: 1, name: 'Planet Earth' }, 2: { id: 2, name: 'Selma' }, 3: { id: 3, name: 'Million Dollar Baby' }, 4: { id: 4, name: 'Forrest Gump' }, 5: { id: 5, name: 'Get Out' } },
moviesArray = Object.values(movies),
idArray = moviesArray.map(movie => movie.id);
console.log(idArray);
const movies = {
1: {
id: 1,
name: 'Planet Earth',
},
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 moviesArray = Object.values(movies);
const idArray = moviesArray.map(movie => movie.id);
console.log(idArray);
I this case, I'd be more inclined to use movie => movie.id as your mapper function, rather than movie => Object.values(movie)[0].
The issue with your current function is that it assumes id will always happen to be the first property in the Array returned by Object.values. That happens to be true with your current function as written, but I'm not sure you can necessarily guarantee that in the general case. Directly referencing movie.idworks even if the properties come in a different order. It should also be a bit faster, since you don't have to convert eaxh individual object to an Array each time.
I think there wasn't a need for using Object.values in the map part here. It would have been same without it:
const movies = {
1: {
id: 1,
name: 'Planet Earth',
},
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 moviesArray = Object.values(movies);
const idArray = moviesArray.map(movie => movie);
console.log(moviesArray);
May be you can go with more core version.
In my solution the loop will be running only once.
const movies = {
1: {
id: 1,
name: 'Planet Earth',
},
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 idArray = [];
for (let i in movies) {
idArray.push(movies[i].id);
}
console.log(idArray);

Categories

Resources