简体   繁体   中英

Build a tree from a list of nodes and links

I have a graphical editor that allows to draw a d3 force directed graph (a questionnaire with multiple choices and outcome, for example, if you answer yes to question 1, go to question 2, else go to question 1b).

It has a root node, and nodes can have multiple parents, and multiple children.

Internally, it is represented as a list of nodes and a list of links.

Example input

const nodes = [
    {id:1, data: {display: "start"}},
    {id:2, data: {display: "question 1"}},
    {id:3, data: {display: "question 2"}},
    {id:4, data: {display: "question 1b"}},
    {id:5, data: {display: "End"}},
];
   
const links = [
    {source:1,target:2},
    {source:2,target:3},
    {source:2,target:4},
    {source:4,target:3},
    {source:3,target:5},
];

Expected output

I need to generate a tree showing all possible paths, like so:

{
    Id:1
    Data: {display: "start"},
    Children: [
        {
            Id:2,
            Data: {display: "question 1"},
            Children:[
                {
                    Id:3,
                    Data: {display: "question 2"},
                    Children:[
                        {
                            Id:5
                            Data: "End",
                            Children:[]
                        }
                    ]
                },
                {
                    Id:4,
                    Data: {display: "question 1 b"},
                    Children:[
                        {
                            Id:3,
                            Data: {display: "question 2"},
                            Children:[
                                {
                                    Id:5
                                    Data: "End",
                                    Children:[]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

Attempt

Here is my (very naive) try at it so far:

 const nodes = [ {id:1, data: {display: "start"}}, {id:2, data: {display: "question 1"}}, {id:3, data: {display: "question 2"}}, {id:4, data: {display: "question 1b"}}, {id:5, data: {display: "End"}}, ]; const links = [ {source:1,target:2}, {source:2,target:3}, {source:2,target:4}, {source:4,target:3}, {source:3,target:5}, ] const startNode = nodes.find(n => n.id == 1); const jsonTree = { id: startNode.id, data: startNode.data, children: [], } const nodeList = []; nodeList.push(startNode); while (nodeList.length > 0) { const currentNode = nodeList.shift(); if (!currentNode) continue; currentNode.visited = true; const childNodes = this.findChildrenOfNode(currentNode, nodes, links); const position = this.findPositionInJson(jsonTree, currentNode); position.data = currentNode.data; position.id = currentNode.id childNodes.forEach((c) => { if (!c.visited) { c.visited = true; nodeList.push(c); } const child = { id:c.id, data: c.data, children: [], }; position.children.push(child); }); } function findChildrenOfNode(node, nodes, links) { const childLinksTargetIds = []; links.forEach((l) => { if (node.id === l.source) childLinksTargetIds.push(l.target); }); return nodes.filter((n) => childLinksTargetIds.includes(n.id)); } function findPositionInJson(jsonObject, node) { if (!jsonObject) return null; if (jsonObject.hasOwnProperty('data')) { if (jsonObject.id === node.id) return jsonObject; } const keys = Object.keys(jsonObject); for (let i = 0; i < keys.length; i += 1) { const key = jsonObject[keys[i]]; if (typeof key === 'object') { const o = this.findPositionInJson(key, node); if (o != null) return o; } } return null; } console.log(jsonTree)

Question

My code will populate the direct children of "1b", but not recursively (The child of "1b" is "2", whose child should be "end")

I suppose there is an algorithm for doing exactly what I'm trying to do. How can it be done?

First create an map for mapping id values to the corresponding objects. Add to each of those objects an empty children array property. Then populate the children arrays from the links information, each time looking up the source and target identifiers in the map. Finally return the map entry for id 1:

 function buildTree(nodes, links) { let map = new Map(nodes.map(o => [o.id, {...o, children: []}])); for (let {source, target} of links) map.get(source).children.push(map.get(target)); return map.get(1); } // Demo const nodes = [ {id:1, data: {display: "start"}}, {id:2, data: {display: "question 1"}}, {id:3, data: {display: "question 2"}}, {id:4, data: {display: "question 1b"}}, {id:5, data: {display: "End"}}, ]; const links = [ {source:1,target:2}, {source:2,target:3}, {source:2,target:4}, {source:4,target:3}, {source:3,target:5}, ] console.log(buildTree(nodes, links));

You can use recursion for a shorter solution:

 const nodes = [ {id:1, data: {display: "start"}}, {id:2, data: {display: "question 1"}}, {id:3, data: {display: "question 2"}}, {id:4, data: {display: "question 1b"}}, {id:5, data: {display: "End"}}, ]; const links = [ {source:1,target:2}, {source:2,target:3}, {source:2,target:4}, {source:4,target:3}, {source:3,target:5}, ]; var nds = Object.fromEntries(nodes.map(x => [x.id, x.data])) function to_tree(n = 1){ return {Id:n, data:nds[n], children:links.filter(x => x.source === n).map(({source:_, target:t}) => to_tree(t))} } console.log(to_tree())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM