Better way to handle Arrays concatenation placed inside the Object - javascript

Well, I need to make an array concatenation, placed inside the Object.
So the worked solution I've made is:
const usersList = {
tester: [{ id: 1, //... }, //...],
custumers: [{ id: 1, //... }, //...],
admin: [{ id: 1, //... }, //...]
}
let allUsers = []
Object.keys(usersList).forEach(listKey => {
allUsers = [
...allUsers,
...usersList[listKey]
]
})
return allUsers
Besides, I wonder perhaps there is present a much fashionable way to deal with such a case? I tried this one, but it doesn't work:
[...Object.keys(usersList).map(listKey => usersList[listKey])]

Take the object's values, which will give you an array of arrays, then flatten:
const allUsers = Object.values(usersList).flat();
If you can't use .flat, then:
const allUsers = [].concat.apply(...Object.values(usersList));

Another way is to use flatMap
const usersList = {
tester: [{ id: 1 }],
custumers: [{ id: 1 }],
admin: [{ id: 1 }]
};
const res = Object.values(usersList).flatMap(x => x);
console.log(res);

Related

filter array of object using lodash or underscore

I have the following structure and this data is displaying as the list in (as in my given screenshot), here I want to add a filter, like say If I put "a" in my search box it should display all the names which has "a" and when I type the full name like "atewart Bower" it should only show the one list. So far I doing this
const searchContact = newData.filter(d => { // here newData is my arr of objs
let alphabet = d.alpha.toLowerCase();
return alphabet.includes(this.state.searchUserName.toLowerCase())
})
it is returning on the basis of "alpha" not "name" inside the users array. I was trying to use Lodash and underscore.js, but didn't find what I want to achieve there too.
I tried this code of Lodash
const dd = _.filter(newData, { users: [ { name: this.state.searchUserName } ]});
but it also return the array of object when I write the full name like when this.state.searchUserName = atewart Bower
[
{
alpha: "a",
users: [
{
id: "1",
name: "atewart Bower"
},
{
id: "1",
name: "aatewart Bower"
},
]
},
{
alpha: "b",
users: [
{
id: "1",
name: "btewart Bower"
},
{
id: "1",
name: "bbtewart Bower"
},
]
}
]
It is filtering on basis of alpha because inside the filter, we are using alpha value to check.
let alphabet = d.alpha.toLowerCase();
return alphabet.includes(this.state.searchUserName.toLowerCase())
To check inside the users array, you can do something like this
const getSearchedContacts = (newData, searchUserName) => {
const searchResults = [];
newData.forEach((item) => {
const users = item.users.filter(user => user.name.toLowerCase().startsWith(searchUserName.toLowerCase()));
if (users.length) searchResults.push({...item, users});
});
return searchResults;
};
getSearchedContacts(yourData, 'atewart Bower'); // Returns [{"alpha":"a","users":[{"id":"1","name":"atewart Bower"}]}]
Note: I'm using startsWith instead of includes because we want to return only one name when search string is for example "atewart Bower"

How to perform array join in Node.js efficiently/fast similar to MongoDB $lookup?

I want to perform a $lookup in Node.js similar to $lookup aggreation from MongoDB.
I have a solution but I'm not sure how fast it performs with more objects in each of the two arrays or with bigger objects.
let users = [
{userId: 1, name: 'Mike'},
{userId: 2, name: 'John'}
]
let comments = [
{userId: 1, text: 'Hello'},
{userId: 1, text: 'Hi'},
{userId: 2, text: 'Hello'}
]
let commentsUsers = [
{userId: 1, text: 'Hello', user: {userId: 1, name: 'Mike'}},
{userId: 1, text: 'Hi', user: {userId: 1, name: 'Mike'}},
{userId: 2, text: 'Hello', user: {userId: 2, name: 'John'}}
] //Desired result
I know this can be done easily with ECMA6 arrays. For example:
let commentsUsers = comments.map(comment => {comment, users.find(user => user.userId === comment.userId)} )
I that an effective way to do this for a large number of users eg. 1M users. How does lodash compare to this or any other more specialized library? Are there better ways to do this with vanilla JS eg. with Array.prototype.reduce()? Can indexing be used in any way to improve the performance of the join?
Edit:
My ideal solution
let users = [{userId:1,name:'Mike'},{userId:2,name:'John'}]
let comments = [{userId:1,text:'Hello'},{userId:1,text:'Hi'},{userId:2,text:'Hello'}];
let usersMap = new Map(users.map(user => [user.userId, user]))
let commentsUsers = comments.map(comment => ({...comment, user: usersMap.get(comment.userId)}))
console.log(commentsUsers)
Thanks for the feedback!
Your desired result is not a proper data structure. You are missing a key to your object of e.g. {userId: 1, name: 'Mike'}. I added user as the key value for a indexing solution.
First I create a Map where the userId will be our loop-up value. Afterwards I just iterate over the comments with map, transforming each object to a new one that contains all the comment information plus a new k-v pair of user. For that pair we don't need to use find anymore instead we have a simple HashMap get call.
Time-complexity-wise this changes the code from O(n^2) to O(n).
let users = [{userId:1,name:'Mike'},{userId:2,name:'John'}],
comments = [{userId:1,text:'Hello'},{userId:1,text:'Hi'},{userId:2,text:'Hello'}];
function mergeCommentUser(users, comments) {
let map = new Map(users.map(v => [v.userId, v]));
return comments.map(o => ({...o, user: map.get(o.userId)}));
}
console.log(JSON.stringify(mergeCommentUser(users,comments)))
Depending on what you want (and to save on redundancy), you could also change the following line:
let map = new Map(users.map(v => [v.userId, v]));
to the following instead:
let map = new Map(users.map(v => [v.userId, v.name]));
By that your result would look like:
[
{"userId":1,"text":"Hello","user":"Mike"},
{"userId":1,"text":"Hi","user":"Mike"},
{"userId":2,"text":"Hello","user":"Paul"}
]
Otherwise, you could omit the comment.userId and instead add the full user to the object for another way to avoid redundancy.
Currently, the code example you provide is O(n * m), or, O(n2). You could create a map of each of the userId's and their respective indexes in the users array, and then rather than find the user, you can directly access it by index. This will reduce the time to O(n + m), that is, O(n).
The code would look something like this:
const users = [{ userId: 1, name: "Mike" }, { userId: 2, name: "John" }];
const comments = [
{ userId: 1, text: "Hello" },
{ userId: 1, text: "Hi" },
{ userId: 2, text: "Hello" }
];
const map = new Map(users.map((o, i) => [o.userId, i]));
console.log(
comments.map(o => {
const index = map.get(o.userId);
return index !== undefined
? {
comment: o.text,
user: users[index]
}
: o;
})
);
Obviously, you can modify the end result, but this approach would be much more efficient than the one you proposed.

Merge objects with corresponding key values from two different arrays of objects

I've got two arrays that have multiple objects
[
{
"name":"paul",
"employee_id":"8"
}
]
[
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
}
]
How can I achieve the following with either ES6 or Lodash?
[
{
"name":"paul",
"employee_id":"8"
"data": {
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
}
]
I can merge but I'm not sure how to create a new child object and merge that in.
Code I've tried:
school_data = _.map(array1, function(obj) {
return _.merge(obj, _.find(array2, {employee_id: obj.e_id}))
})
This merges to a top level array like so (which is not what I want):
{
"name":"paul",
"employee_id":"8"
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
The connector between these two is "employee_id" and "e_id".
It's imperative that it's taken into account that they could be 1000 objects in each array, and that the only way to match these objects up is by "employee_id" and "e_id".
In order to match up employee_id and e_id you should iterate through the first array and create an object keyed to employee_id. Then you can iterate though the second array and add the data to the particular id in question. Here's an example with an extra item added to each array:
let arr1 = [
{
"name":"mark",
"employee_id":"6"
},
{
"name":"paul",
"employee_id":"8"
}
]
let arr2 = [
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
},
{
"years_at_school": 12,
"department":"Arr",
"e_id":"6"
}
]
// empObj will be keyed to item.employee_id
let empObj = arr1.reduce((obj, item) => {
obj[item.employee_id] = item
return obj
}, {})
// now lookup up id and add data for each object in arr2
arr2.forEach(item=>
empObj[item.e_id].data = item
)
// The values of the object will be an array of your data
let merged = Object.values(empObj)
console.log(merged)
If you perform two nested O(n) loops (map+find), you'll end up with O(n^2) performance. A typical alternative is to create intermediate indexed structures so the whole thing is O(n). A functional approach with lodash:
const _ = require('lodash');
const dataByEmployeeId = _(array2).keyBy('e_id');
const result = array1.map(o => ({...o, data: dataByEmployeeId.get(o.employee_id)}));
Hope this help you:
var mainData = [{
name: "paul",
employee_id: "8"
}];
var secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
var finalData = mainData.map(function(person, index) {
person.data = secondaryData[index];
return person;
});
Sorry, I've also fixed a missing coma in the second object and changed some other stuff.
With latest Ecmascript versions:
const mainData = [{
name: "paul",
employee_id: "8"
}];
const secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
// Be careful with spread operator over objects.. it lacks of browser support yet! ..but works fine on latest Chrome version for example (69.0)
const finalData = mainData.map((person, index) => ({ ...person, data: secondaryData[index] }));
Your question suggests that both arrays will always have the same size. It also suggests that you want to put the contents of array2 within the field data of the elements with the same index in array1. If those assumptions are correct, then:
// Array that will receive the extra data
const teachers = [
{ name: "Paul", employee_id: 8 },
{ name: "Mariah", employee_id: 10 }
];
// Array with the additional data
const extraData = [
{ years_at_school: 6, department: "Mathematics", e_id: 8 },
{ years_at_school: 8, department: "Biology", e_id: 10 },
];
// Array.map will iterate through all indices, and gives both the
const merged = teachers.map((teacher, index) => Object.assign({ data: extraData[index] }, teacher));
However, if you want the data to be added to the employee with an "id" matching in both arrays, you need to do the following:
// Create a function to obtain the employee from an ID
const findEmployee = id => extraData.filter(entry => entry.e_id == id);
merged = teachers.map(teacher => {
const employeeData = findEmployee(teacher.employee_id);
if (employeeData.length === 0) {
// Employee not found
throw new Error("Data inconsistency");
}
if (employeeData.length > 1) {
// More than one employee found
throw new Error("Data inconsistency");
}
return Object.assign({ data: employeeData[0] }, teacher);
});
A slightly different approach just using vanilla js map with a loop to match the employee ids and add the data from the second array to the matching object from the first array. My guess is that the answer from #MarkMeyer is probably faster.
const arr1 = [{ "name": "paul", "employee_id": "8" }];
const arr2 = [{ "years_at_school": 6, "department": "Mathematics", "e_id": "8" }];
const results = arr1.map((obj1) => {
for (const obj2 of arr2) {
if (obj2.e_id === obj1.employee_id) {
obj1.data = obj2;
break;
}
}
return obj1;
});
console.log(results);

How to generate new array of objects from existing array of objects using lodash

Can someone help me generate a new array of objects from an existing one using lodash? I've been trying a combination of _.zipObject and map but to no avail... basically, I have an array of objects like:
const names = [
{
first_name: 'nedd',
given_name: 'cersei'
},
{
first_name: 'tyrion',
given_name: 'tywin'
}
]
However, I want it to look like:
[
{
name: 'nedd'
},
{
name: 'cersei'
},
{
name: 'tyrion'
},
{
name: 'tywin'
},
]
I have tried various iterations of:
const newArray = _.zipObject( names, _.fill( Array(names.length), {name: ['first_name' || 'given_name']} ) );
But without any luck... can someone help?
Thanks in advance!
This might work:
_.flatMap(names, (n)=> [{name: n.first_name}, {name: n.given_name}]);
Use _.flatMap combined with _.map:
_.flatMap(names, (nameObj) => _.map(nameObj, (objVal) => { return { name: objVal }; }));

How to flatten an array before applying arrayOf('schema') in normalizrJS

Suppose a user has favorited many cats and the api endpoint gives a list of cats when asked for user's favorites:
const catsList = [{id:1,name:"catA"},{id:2,name:"catB"},{id:3,name:"catC"}];
// ex: GET http://app.com/api/favorites => results in following json
{favorites:[{id:2,name:"catB"},{id:3,name:"catC"}]}
// the result is the list of cats
//and normalizr schema
const cats = new Schema('cats');
const favorites = new Schema('favorites');
now my question is, how will I normalize these entities so that I have the following result,
entities.favorites=[2,3]; //2 and 3 are cats
entities.cats=[{id:1,name:"a"},{id:2,name:"b"},{id:3,name:"c"}];
how will I accomplish this with normalizr ?
Something like
const cats = new Schema('cats');
const response = {
favorites: [{
id: 2,
name: "catB"
}, {
id: 3,
name: "catC"
}]
}
const normalized = normalize(response, {
favorites: arrayOf(cats)
})
The normalized value will be like
{
result: {
favorites: [2, 3]
},
entities: {
cats: {
2: {
id: 2,
name: "catB"
}, {
id: 3,
name: "catC"
}
}
}
}
It’s actually pretty important that normalized.entities is an object rather than an array—otherwise it would be slow to query by ID.
At this point you are free to transform this representation anyhow you like.

Categories