Reputation: 166
I have 2 arrays of objects and I need to add data to them based on matching ids. I can check every value in both arrays, check if their id matches and use them. One parent can have many children, but child can have only one parent. Code example:
const parents = [
{id: 1, name: 'John', kids: []},
{id: 2, name: 'Caren', kids: []}
];
const children = [
{parent_id: 2, name: 'Huey'},
{parent_id: 1, name: 'Dewey'},
{parent_id: 1, name: 'Louie'}
];
for (let i = 0; i < children.length; i++) {
for (let j = 0; j < parents.length; j++) {
if (parents[j].id == children[i].parent_id) {
parents[j].kids.push(children[i]);
break;
}
}
}
console.log(parents);
How can I improve it in any way?
Upvotes: 0
Views: 176
Reputation: 13431
Like with a lot of use cases, similar to the one provided by the OP, a reduce
based approach combined with a generically implemented callback assures a versatile usage.
One wants to operate the child list, since this list most probably comes with more items than the parent list. Of course this is just an assumption (best guess).
In order to not iterate the parent list again and again (via e.g. find
) for each iteration step of a child list item, one programmatically builds a look-up table called index
which is a member of the collector
object that is passed into each next iteration and also needs to be provided as the reduce method's start value.
In order to demonstrate the flexibility of the approach, its example code comes with two different use cases, a non mutating one that leaves the original parent
array untouched, but creates a new one matching the desired result, and a second one which does mutate the parent
array ...
function pushItemIntoRelatedParentChildList(collector, item) {
const { index, list } = collector;
const { parent_id } = item;
// make use of `index` for a faster parent lookup.
let parent = index[parent_id];
if (!parent) {
// programmatically build the parent `index`.
parent = index[parent_id] = list.find(({ id }) => id === parent_id);
}
// be defensive in case of child parent relationship does not exist.
if (parent && parent.kids) {
parent.kids.push(item);
}
return collector;
}
const parents = [
{id: 1, name: 'John', kids: []},
{id: 2, name: 'Caren', kids: []}
];
const children = [
{parent_id: 2, name: 'Huey'},
{parent_id: 1, name: 'Dewey'},
{parent_id: 1, name: 'Louie'}
];
// non `parents` mutating usage.
const newParentList = children.reduce(pushItemIntoRelatedParentChildList, {
index: {},
list: parents.map(({ kids, ...rest }) => ({ ...rest, kids: [] }))
}).list;
console.log('(parents === newParentList) ?', (parents === newParentList));
console.log('parents :', parents);
console.log('newParentList :', newParentList);
// `parents` mutating usage.
children.reduce(pushItemIntoRelatedParentChildList, {
index: {},
list: parents
});
console.log('parents :', parents);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Upvotes: 1
Reputation: 36794
If it works, great! Although for...of
may be of some interest to you:
for (const child of children) {
for (const parent of parents) {
if (parent.id == child.parent_id) {
parent.kids.push(child);
break;
}
}
}
Other than that..
This is also achievable through some fairly simple mapping and filtering:
const parents = [{
id: 1,
name: 'John',
kids: []
}, {
id: 2,
name: 'Caren',
kids: []
}];
const children = [{
parent_id: 2,
name: 'Huey'
},
{
parent_id: 1,
name: 'Dewey'
},
{
parent_id: 1,
name: 'Louie'
}
];
// Remap our parents to an array of new objects
const withChildren = parents.map(parent => ({
// which includes the parent data
...parent,
// but also provides the kids
kids: children
// filter the kids based on whether or not the parent_id matches the currently mapped parent
.filter(({ parent_id }) => parent_id === parent.id)
}));
console.log(withChildren);
Upvotes: 1
Reputation: 817
You could use an Hashtable and save the parent_id as key and the children in an array as value.
So you need to loop only once through the parents and once through the children.
But you need extra space for the object.
const parents = [{ id: 1, name: 'John', kids: [] }, { id: 2, name: 'Caren', kids: [] }]
const children = [
{ parent_id: 2, name: 'Huey' },
{ parent_id: 1, name: 'Dewey' },
{ parent_id: 1, name: 'Louie' }
]
const childrenMap = {}
children.forEach(child => {
childrenMap[child.parent_id]
? childrenMap[child.parent_id].push(child)
: childrenMap[child.parent_id] = [child]
})
parents.forEach(parent => childrenMap[parent.id] && parent.kids.push(...childrenMap[parent.id]))
console.log('Parents', JSON.stringify(parents, null, 2))
Upvotes: 0
Reputation: 875
Try this
const parents = [{id: 1, name: 'John', kids: []}, {id: 2, name: 'Caren', kids: []}];
const children = [
{parent_id: 2, name: 'Huey'},
{parent_id: 1, name: 'Dewey'},
{parent_id: 1, name: 'Louie'}
];
let result = [];
parents.map(v=>result[v.id]=v);
children.map(v=>(result[v.parent_id] && result[v.parent_id].kids.push(v)));
let result = result.filter(v=>!!v);
Upvotes: 0
Reputation: 24211
Personally I like for loops, mainly because they work well with async code too, but you could replace with forEach & find.
const parents = [
{id: 1, name: 'John', kids: []},
{id: 2, name: 'Caren', kids: []}];
const children = [
{parent_id: 2, name: 'Huey'},
{parent_id: 1, name: 'Dewey'},
{parent_id: 1, name: 'Louie'}
];
children.forEach(child => {
const parent = parents.find(
parent => parent.id == child.parent_id);
if (parent) parent.kids.push(child);
});
console.log(parents);
Upvotes: 2