Reputation: 6036
Well, I have an array with objects where some elements depends of others elements.
So, I need to order it by importance (dependency of parent) to store this on a database and replace all the children's parent
property by the respective parent id
.
Example of the array:
[
{
"id": 1,
"email": "[email protected]", // unique
"parent": "[email protected]" // is nullable
},
{
"id": 2,
"email": "[email protected]",
"parent": null
},
{
"id": 3,
"email": "[email protected]",
"parent": "[email protected]"
},
{
"id": 4,
"email": "[email protected]",
"parent": "[email protected]"
},
...
]
Graphical example of dependency:
Expected result: Ordered by dependency (parent):
[
{
"id": 2,
"email": "[email protected]",
"parent": null
},
{
"id": 3,
"email": "[email protected]",
"parent": 2
},
{
"id": 1,
"email": "[email protected]",
"parent": 3
},
{
"id": 4,
"email": "[email protected]",
"parent": 1
},
...
]
To set respective parent id
I'm using (but it no ordering by parent level: parent, children, grandchildren...):
let users = [
{
"id": 1,
"email": "[email protected]", // unique
"parent": "[email protected]" // is nullable
},
{
"id": 2,
"email": "[email protected]",
"parent": null
},
{
"id": 3,
"email": "[email protected]",
"parent": "[email protected]"
},
{
"id": 4,
"email": "[email protected]",
"parent": "[email protected]"
}
];
users = users.map(user => {
user.parent = _.findIndex(users, i => user.parent === i.email);
return user;
});
P.S:
In this case, the importance
concept refers to parent
level.
So, First I need the parents, then the children, grandchildren, and so on...
I am sorry if this thread is poor in explanations, if you have doubts, I will look for the best way to express the idea.
Upvotes: 2
Views: 1151
Reputation: 1123
All the supplied answers are fine, but slow time-complexity-wise (O(n^2)). They are going over all the the nodes, and for each node they are searching for its parent (going over nodes - O(n), and in every iteration looking for the parent - O(n) * O(n) = O(n^2))
A better solution would be creating a tree structure and using pre-order (DFS) for creating a topological sort.
function createTree(nodesWithParentArray) {
const initialTree = nodesWithParentArray.reduce(
(acc, node) => {
acc[node.id] = { data: node, children: [] }
return acc
},
{ null: { children: [] } }
)
const tree = nodesWithParentArray.reduce((acc, node) => {
acc[node.parent].children.push(acc[node.id])
return acc
}, initialTree)
return tree[null].children[0] // root
}
// test it like that:
createTree([{id:1, parent:2},{id:2, parent:null},{id:3, parent:2},{id:4, parent:3}])
The func above would return a nested tree structure, with a pointer to the root node. What left to do is using pre-order traversal to create a topological sort (O(n), as we are going over each node just once):
function topologicalSort(tree) {
const sortedList = []
const queue = [treeRoot]
while (queue.length) {
const curNode = queue.shift()
sortedList.push(curNode.data)
queue.push(...curNode.children)
}
return sortedList
}
Creating the tree above would also be O(n) as it loops over the input array just once, so the final time complexity would be O(n) + O(n) => O(n)
Upvotes: 0
Reputation: 17190
I will approach this by first generating a new input with the replacement of parent email
by the parent id
and a new property of the node level related to the tree they belong. Then we can sort the nodes by this level
property, and on equal level
we sort by the id
.
const input = [
{"id": 1, "email": "[email protected]", "parent": "[email protected]"},
{"id": 2, "email": "[email protected]", "parent": null},
{"id": 3, "email": "[email protected]", "parent": "[email protected]"},
{"id": 4, "email": "[email protected]", "parent": "[email protected]"},
{"id": 5, "email": "[email protected]", "parent": "[email protected]"},
{"id": 6, "email": "[email protected]", "parent": "[email protected]"},
{"id": 7, "email": "[email protected]", "parent": null},
{"id": 8, "email": "[email protected]", "parent": "[email protected]"}
];
const findParent = (mail) => input.find(x => x.email === mail);
const getLevel = (mail, lvl) =>
{
return mail ? getLevel(findParent(mail).parent, lvl + 1) : lvl;
}
let newInput = input.map(({id, email, parent}) =>
{
return {
id: id,
email: email,
parent: findParent(parent) ? findParent(parent).id : null,
lvl: getLevel(parent, 0)
};
});
let sortedInput = newInput.sort((a, b) =>
{
return (a.lvl - b.lvl) ? a.lvl - b.lvl : a.id - b.id;
});
console.log(sortedInput);
Upvotes: 2
Reputation: 5941
Below is an iterative approach (as opposed to the recursive solution provided) that you can use to achieve your result. Basically, start by finding the root element and then iterate over the original array looking for elements that have the current element as it's parent.
To achieve replacing parent email with ID just keep a map of parent names to IDs:
var data = [{
"id": 1,
"email": "[email protected]", // unique
"parent": "[email protected]" // is nullable
}, {
"id": 2,
"email": "[email protected]",
"parent": null
}, {
"id": 3,
"email": "[email protected]",
"parent": "[email protected]"
}, {
"id": 4,
"email": "[email protected]",
"parent": "[email protected]"
}]
//Map email addresses to IDs
var map = data.reduce((accum, el) => {
accum[el.email] = {
id: el.id
}
return accum;
}, {});
var [root] = data.filter(el => !el.parent);
var users = [root];
var cur;
var children;
while (users.length < data.length) {
cur = users[users.length - 1];
//Find elments that have cur as parent
children = data.filter(el => el.parent === cur.email);
children.forEach(el => {
users.push({
id: el.id,
email: el.email,
parent: map[el.parent].id
});
});
}
console.log(users)
Upvotes: 1
Reputation: 17654
you can use a recursive function
const data = [{
"id": 1,
"email": "[email protected]", // unique
"parent": "[email protected]" // is nullable
},
{
"id": 2,
"email": "[email protected]",
"parent": null
},
{
"id": 3,
"email": "[email protected]",
"parent": "[email protected]"
},
{
"id": 4,
"email": "[email protected]",
"parent": "[email protected]"
},
]
const order = (arr, level) => {
const children = arr.filter(e => e.parent === level); // get the elements that have the same parent ( level )
const parent = arr.find(e => e.email === level); // get the parent
return children.length
? [
...children.map(e =>
({ ...e,
parent: parent ? parent.id : null // update the parent to the id instead of email
})),
...order(arr, children[0].email) // call the same function with the email of the first child of the current children array, it will become a parent
]
: children // otherwise return the array
}
const result = order(data, null)
console.log(result)
Upvotes: 2