javascript map two nested arrays and modify the existing by lookups - javascript

I have a kids object that looks like the following:
const kids = {
name: 'john',
extra: {
city: 'London',
hobbies: [
{
id: 'football',
team: 'ABC',
},
{
id: 'basketball',
team: 'DEF',
},
],
},
};
and i have the following object that contains all sports and extra info for each.
const sports = [
{
name: 'volleyball',
coach: 'tom',
},
{
name: 'waterpolo',
coach: 'jack',
},
{
name: 'swimming',
coach: 'kate',
},
{
name: 'football',
coach: 'sara',
},
];
I want to get the list of all ids in the hobbies array and go through each of the sports items in the sports array, and found, add an extra field to that object available and give a value of true, so the result will look like:
const result = [
{
name: 'volleyball',
coach: 'tom',
},
{
name: 'waterpolo',
coach: 'jack',
},
{
name: 'swimming',
coach: 'kate',
},
{
name: 'football',
coach: 'sara',
available: true
},
];
by the way, here is my attempt:
const result = kids.extra.hobbies.map(a => a.id);
for (var key in sports) {
console.log(sports[key].name);
const foundIndex = result.indexOf(sports[key].name);
if ( foundIndex > -1) {
sports[key].available = true;
}
}
console.log(sports)
but this is too long... i am looking one liner looking code and robust logic.

This can be done many ways; however, an easy was is to divide the problem into two steps:
We can first flatten the kid's hobbies into an array by using the Array.map() function:
const hobbies = kids.extra.hobbies.map(hobby => hobby.id);
Then, we can iterate through the sports array and add an active property to any object which is present in the new hobbies array:
const result = sports.map(sport => {
if (hobbies.indexOf(sport.name) !== -1) {
sport.available = true;
}
return sport;
})
Complete Solution
const kids = {
name: 'john',
extra: {
city: 'London',
hobbies: [{
id: 'football',
team: 'ABC',
},
{
id: 'basketball',
team: 'DEF',
},
],
},
};
const sports = [{
name: 'volleyball',
coach: 'tom',
},
{
name: 'waterpolo',
coach: 'jack',
},
{
name: 'swimming',
coach: 'kate',
},
{
name: 'football',
coach: 'sara',
},
];
const hobbies = kids.extra.hobbies.map(hobby => hobby.id);
const result = sports.map(sport => {
if (hobbies.indexOf(sport.name) !== -1) {
sport.available = true;
}
return sport;
})
console.log(result);

Firstly, I would change my data structures to objects. Any time you have a list of things with unique ids, objects will make your life much easier than arrays. With that in mind, if you must use arrays, you could do the following:
const hobbies = kids.extra.hobbies
sports.forEach(s => s.available = hobbies.some(h => h.id === s.name))
Note that this mutates the original sports object (change to map for new), and also adds false/true instead of just true.

Build an array of the found sports first, then map while checking to see if the sports object's name is in it:
const kids = {name:'john',extra:{city:'London',hobbies:[{id:'football',team:'ABC',},{id:'basketball',team:'DEF',},],},}
const sports = [{name:'volleyball',coach:'tom',},{name:'waterpolo',coach:'jack',},{name:'swimming',coach:'kate',},{name:'football',coach:'sara',},];
const sportsInHobbies = kids.extra.hobbies.map(({ id }) => id);
const result = sports.map((sportObj) => {
const available = sportsInHobbies.includes(sportObj.name);
return available ? {...sportObj, available } : { ...sportObj };
});
console.log(result);

Related

Compare two arrays of objects and merge some fields

I'm working with Angular and RxJs and I have two arrays of objects. I need to change one specific field of the first array, if the second one has the field with the same value (all of the four fields has different names). I did it with nested loops, but I need to find a better solution, my code is down below
My solution is working, but it's not the best, because arrays can be really large - so the code will work slow. If there's 1000 items in each array, it will be 1000000 iterations - that's why I need to find a better solution. I got advice to use multiple consecutive loops, but I don't really get how to use it here
this.api
.getFirstArray()
.pipe(
mergeMap((firstArray) =>
this._secondApi.getSecondArray().pipe(
map((secondArray) => {
for (const item2 of secondArray) {
for (const item1 of firstArray) {
if (item1.someField === item2.otherField)
item1.someOtherField = item2.anotherField;
}
}
return firstArray;
}),
),
),
)
.subscribe((value) => {
this.gridApi?.setRowData(value);
});
So for example my data is
firstArray: [
{ id: 445; name: 'test' },
{ id: 4355; name: 'test1' },
{ id: 234_234; name: 'test2' },
];
secondArray: [
{ firstName: 'test3'; newId: 445 },
{ firstName: 'test5'; newId: 2 },
{ firstName: 'test6'; newId: 234_234 },
];
And the result should be
result: [{ id: 445; name: 'test3' }, { id: 4355; name: 'test1' }, { id: 234_234; name: 'test6' }];
Note: the ids of the first array objects may be repeated - all of the objects names need to be updated
here is the working example of your problem, may be it will help you.
let firstArray = [
{ id: 445, name: 'test' },
{ id: 4355, name: 'test1' },
{ id: '234_234', name: 'test2' },
];
let secondArray = [
{ firstName: 'test3', newId: 445 },
{ firstName: 'test5', newId: 2 },
{ firstName: 'test6', newId: '234_234' },
];
secondArray.forEach(sec => {
let see = firstArray.findIndex(first => first.id === sec.newId);
if (see > -1) {
firstArray[see].name = sec.firstName
}
})
console.log(firstArray)
You still end up with O(N²) complexity (there are two nested loops that he wants to avoid).
Instead, You can use map
const firstArray = [
{ id: 445, name: 'test' },
{ id: 4355, name: 'test1' },
{ id: '234_234', name: 'test2' },
];
const secondArray = [
{ firstName: 'test3', newId: 445 },
{ firstName: 'test5', newId: 2 },
{ firstName: 'test6', newId: '234_234' },
];
const secondMap = new Map();
secondArray.forEach((item) => {
secondMap.set(item.newId, item.firstName);
});
for (const item of firstArray) {
if (secondMap.has(item.id)) {
item.name = secondMap.get(item.id);
}
}
console.log(firstArray)

How to manipulate object of array with properties to different names and return the result in a single object using JavaScript or ecmascript-6

I have a request data - array of object like below:
const requestData = [
{
name: 'Test1',
address: 'FL',
},
{
name: 'Test2',
address: 'AL',
},
{
name: 'Test3',
address: 'AK',
},
];
I want to manipulate the object properties based on the index (that mean if index 0 change name property to USER_NAME, if index 1 means change to EMP_NAME) and convert it into final object as below:
const finalResult = {
USER_NAME: 'Test1',
USER_ADDRESS: 'FL',
EMP_NAME: 'Test2',
EMP_ADDRESS: 'AL',
CUST_NAME: 'Test3',
CUST_ADDRESS: 'AK',
};
Using reduce() and an extra map array can do it
let data = [{
name: 'Test1',
address: 'FL',
},
{
name: 'Test2',
address: 'AL',
},
{
name: 'Test3',
address: 'AK',
},
]
let mdata =['USER_','EMP_','CUST_']
let result = data.reduce((a,v,i) =>{
let key = mdata[i]
a[key+'NAME'] = v.name
a[key+'ADDRESS'] = v.address
return a
},{})
console.log(result)
You could destructure and construct your final result:
const [ user, emp, cust ] = requestData;
const finalResult = {
USER_NAME: user.name,
USER_ADDRESS: user.address,
EMP_NAME: emp.name,
EMP_ADDRESS: emp.address,
CUST_NAME: cust.name,
CUST_ADDRESS: cust.address
};
Alternatively, you could reduce, but the code winds up being a bit more complicated than is necessary for this particular example:
const keys = [ "USER", "EMP", "CUST" ];
const finalResult = requestData.reduce(( out, { name, address }, index ) => {
out[ `${ keys[ index ] }_NAME` ] = name;
out[ `${ keys[ index ] }_ADDRESS` ] = address;
return out;
}, {});

How to add value in array which is present inside objects array?

here, I want to update data in such way that if same person with same company comes then it's value of client should come under one array.
input data :
const employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
Expected output:
[
{
name: 'John',
company: 'abc',
client: ['BMW', 'Mercedes']
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
}
]
Here is a way to do it in O(n) time. If you're fine with O(n^2) time, I'd suggest using reduce (as Mina's answer shows) since it's more straightforward.
const inputs = [{
name: 'John',
company: 'abc',
client: 'BMW'
}, {
name: 'Jack',
company: 'abc',
client: 'Volvo'
}, {
name: 'John',
company: 'abc',
client: 'Mercedes'
}];
const itemsByNameCompany = {};
for (let input of inputs) {
const nameCompany = `${input.name},${input.company}`;
itemsByNameCompany[nameCompany] = itemsByNameCompany[nameCompany] || [];
itemsByNameCompany[nameCompany].push(input);
}
const outputs = [];
for (let key in itemsByNameCompany) {
const items = itemsByNameCompany[key];
const clients = items.map(item => item.client);
const item = {
...items[0],
client: clients.length === 1 ? clients[0] : clients
};
outputs.push(item);
}
console.log(outputs);
There are lots of way we can do that, Reduce, map, forOf or you can use that as well:
let employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
];
let employees = {};
employee.forEach(employee => {
if(employees[employee.name]){
employees[employee.name].client = [...employees[employee.name].client, employee.client];
} else {
employees[employee.name] = { ...employee, client: [employee.client] };
}
});
console.log(employees); // Simple Object group by name
console.log(Object.values(employees)); // Convert to Array
Hope it will help you to solve.
You will need a function that does this:
Check if the same name and company combination exists
If it exists, then update the client property
If the combination doesn't exist, add it to the same array
const result = [];
function _addData(obj){
const index = result.findIndex((item) =>
item.name === obj.name && item.company === obj.company);
if(index!=-1){
const client = result[index].client;
if(Array.isArray(client)){
client.push(obj.client);
} else{
result[index].client = [client, obj.client];
}
} else{
result.push(obj);
}
}
You need to add a function that checks if the client already is an array. If it is then you can just push the new client. If its a string then you can take the previous value and the new value and write both of them into a new array.
You can do it like this:
const employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
function addClient(index, client) {
if (Array.isArray(employee[index].client)) {
employee[index].client.push(client);
} else {
employee[index].client = [employee[index].client, client];
}
}
addClient(0, 'New Client')
addClient(0, 'New Client2')
addClient(1, 'New Client3')
console.log(employee)
You can use reduce method to group the employees together it they have equal name and company.
const employees = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
const result = employees.reduce((acc, item) => {
const itemInAcc = acc.find(i => i.name === item.name && i.company === item.company);
if (itemInAcc) {
let client = itemInAcc.client
itemInAcc.client = [...(Array.isArray(client) ? client : [client]), item.client];
} else {
acc.push(item)
}
return acc;
}, [])
console.log(result)
Hi you can use reduce to group
employee.reduce((grouped, next) => {
if (grouped.some(gItem => gItem.name === next.name && gItem.company === next.company)) {
grouped = grouped.map(gItem => {
if (gItem.name === next.name && gItem.company === next.company) {
gItem.client = Array.isArray(gItem.client) ? [...gItem.client, next.client] : [gItem.client, next.client]
}
return gItem
})
} else {
grouped.push(next)
}
return grouped
}, [])
welcome to stackoverflow Akshay Kamble
in you case you need to create new JSON object to collect filtered data,
this is my simple code just using forEach
const employee = [{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
const newEmployee = [];
employee.forEach(e => {
let obj = newEmployee.find(o => o.name === e.name);
if (obj) {
if (obj.client instanceof Array) {
obj.client.push(e.client)
} else {
obj.client = [e.client]
}
} else {
e.client = [e.client];
newEmployee.push(e);
}
})
console.log(newEmployee)

Grouping Arrays By Nested Arrays

I have the following array that I'd like to transform into an Object with unique hobbies as the keys
const arr = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] },
]
I use lodash's handy groupBy function but it groups the multiple array elements into single keys like so
{
'skating,biking,music': [
{ name: 'Joe' }
],
'fishing,biking,karate': [
{ name: 'Kim' }
],
'surfing': [
{ name: 'Ben' }
],
}
What I need is the following output (note the objects are repeated for each of their respective hobbies)
{
biking: [
{ name: 'Joe' },
{ name: 'Kim' }
],
skating: [
{ name: 'Joe' }
],
karate: [
{ name: 'Kim' }
],
surfing: [
{ name: 'Ben' }
],
...
}
Is there a simple way to group this array without looping through each array element, splitting them up and regrouping? Would like to avoid this if there's better utility method out there I'm unaware of
You can iterate each item and each hobbie and then add it to a result object:
const arr = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] }
]
const result = {};
arr.forEach(item =>
item.hobbies.forEach(hobbie =>
result[hobbie] = (result[hobbie] || []).concat({name: item.name})
)
)
console.log(result);
const arr = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] }
]
const result = {};
arr.forEach(item =>
item.hobbies.forEach(hobbie =>
result[hobbie] = result[hobbie]?[...result[hobbie],{name: item.name}]: [{name: item.name}]
)
)
console.log(result);
I've renamed arr to people for better understanding.
const people = [
{ name: 'Joe', hobbies: ['skating', 'biking', 'music'] },
{ name: 'Kim', hobbies: ['fishing', 'biking', 'karate'] },
{ name: 'Ben', hobbies: ['surfing'] },
];
function transform(people) {
// get all hobbies and remove duplicates
const hobbies = [... new Set(
people.reduce((hobbies, person) => hobbies.concat(person.hobbies), [])
)];
const res = {};
// take a hobby and use it as key
for (let hobby of hobbies) {
res[hobby] = people
.filter((person) => person.hobbies.includes(hobby))
.map((person) => { return { name: person.name }; });
}
return res;
}
console.log(transform(people));

How to create a new array of objects form object without duplicates ? (ES6)

I would like to create an array of all "department" from the "users" array without duplicate in ES6.
I've tried with forEach, reduce, filter, without success...
Users array:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
]
Result expected:
// Expected
departments = [{
id: 1,
name: 'hello'
},
{
id: 2,
name: 'hello2'
}
] */
My own experiment:
let departments = []
users.forEach(user => {
console.log('-------------------')
console.log(departments)
console.log(user)
console.log(user.department)
console.log(departments.includes(user.department))
if (!departments.includes(user.department)) {
departments.push(user.department)
}
console.log(departments)
})
console.log(departments)
Thanks for your help!
Problem:
Your problem is that you are checking for departments with Array#includes() which is rather used with primitives such as Number and string and doesn't compare objects, try not to use it as it's not compatible with IE also.
Solution:
You can do it using Array#map() and Array#filter() methods:
var deps = users.map(u => u.department);
let results = deps.filter((item, pos) => {
return deps.map(v => v.id).indexOf(item.id) == pos;
});
First map the items to keep only the department object.
Then filter the departments to exclude the ones that has the same id.
Demo:
This is a working demo:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
];
var deps = users.map(u => u.department);
let results = deps.filter((item, pos) => {
return deps.map(v => v.id).indexOf(item.id) == pos;
});
console.log(results);
Just map to the departments, then filter out based on the id:
const ids = new Set;
const result = users
.map(user => user.department)
.filter(({ id }) => !ids.has(id) && ids.add(id));
(This is O(n) as Set lookup / insertion is O(1))
You can use Array.reduce() for that:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
];
let departments = users.reduce((acc, obj)=>{
let exist = acc.find(({id}) => id === obj.department.id);
if(!exist){
acc.push({id:obj.department.id, name: obj.department.name});
}
return acc;
}, []);
console.log(departments);

Categories