javascriptarrayssortinglodashtopological-sort

Sort by parent (topological sort) and importance of element on array


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": "a@b.com", // unique
        "parent": "c@b.com" // is nullable
    },
    {
        "id": 2,
        "email": "b@b.com",
        "parent": null
    },
    {
        "id": 3,
        "email": "c@b.com",
        "parent": "b@b.com"
    },
    {
        "id": 4,
        "email": "d@b.com",
        "parent": "a@b.com"
    },
    ...
]

Graphical example of dependency:

enter image description here

Expected result: Ordered by dependency (parent):

[
    {
        "id": 2,
        "email": "b@b.com",
        "parent": null
    },
    {
        "id": 3,
        "email": "c@b.com",
        "parent": 2
    },
    {
        "id": 1,
        "email": "a@b.com",
        "parent": 3 
    },
    {
        "id": 4,
        "email": "d@b.com",
        "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": "a@b.com", // unique
    "parent": "c@b.com" // is nullable
},
{
    "id": 2,
    "email": "b@b.com",
    "parent": null
},
{
    "id": 3,
    "email": "c@b.com",
    "parent": "b@b.com"
},
{
    "id": 4,
    "email": "d@b.com",
    "parent": "a@b.com"
}
];

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.


Solution

  • 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": "a@b.com", "parent": "c@b.com"},
        {"id": 2, "email": "b@b.com", "parent": null},
        {"id": 3, "email": "c@b.com", "parent": "b@b.com"},
        {"id": 4, "email": "d@b.com", "parent": "a@b.com"},
        {"id": 5, "email": "x@b.com", "parent": "b@b.com"},
        {"id": 6, "email": "z@b.com", "parent": "x@b.com"},
        {"id": 7, "email": "y@b.com", "parent": null},
        {"id": 8, "email": "m@b.com", "parent": "y@b.com"}
    ];
    
    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);