问题
I have a list of elements, each has an ID and a parent ID. What I want to do is detect when there is a loop in this 'hierarchy', and show which ID starts the loop.
list = [
{
id: '1',
parent: '2'
},
{
id: '2',
parent: '3'
},
{
id: '3',
parent: '4'
},
{
//This id is causing the loop
id: '4',
parent: '1'
}
]
I have tried this to build the tree, which works when there's no loop, but does not work with a loop:
function treeify(list, idAttr, parentAttr, childrenAttr) {
if (!idAttr) idAttr = 'id';
if (!parentAttr) parentAttr = 'parent';
if (!childrenAttr) childrenAttr = 'children';
var treeList = [];
var lookup = {};
list.forEach(function(obj) {
lookup[obj[idAttr]] = obj;
obj[childrenAttr] = [];
});
list.forEach(function(obj) {
if (obj[parentAttr] != null) {
lookup[obj[parentAttr]][childrenAttr].push(obj);
} else {
treeList.push(obj);
}
});
return treeList;
};
I also cannot detect when there is a loop.
I'd like to return the ID of the element that caused the loop to allow me to fix the data behind it.
回答1:
You can apply white-grey-black coloring to detect nodes that are (re)visited while visiting their descendants (I've simplified your graph to a list of parent-child pairs):
graph = [
[2, 1],
[3, 2],
[1300023, 3],
[1, 1300023],
];
colors = {}
function visit(vertex) {
if (colors[vertex] === 'black') {
// black = ok
return;
}
if (colors[vertex] === 'grey') {
// grey = visited while its children are being visited
// cycle!
console.log('cycle', colors);
return;
}
// mark as being visited
colors[vertex] = 'grey';
// visit children
graph.forEach(edge => {
if (edge[0] === vertex)
visit(edge[1]);
});
// mark as visited and ok
colors[vertex] = 'black'
}
visit(1)
A nice illustration of this approach: https://algorithms.tutorialhorizon.com/graph-detect-cycle-in-a-directed-graph-using-colors/
回答2:
You could collect all nodes and the childrens in object and filter all nodes by taking an array of visited nodes.
The infinite array contains all nodes which causes a circular reference.
function isCircular(id, visited = []) {
return visited.includes(id)
|| Object.keys(links[id]).some(k => isCircular(k, visited.concat(id)));
}
var list = [{ id: '1', parent: '2' }, { id: '2', parent: '3' }, { id: '3', parent: '4' }, { id: '4', parent: '1' }],
links = {},
infinite = [];
list.forEach(({ id, parent }) => {
links[parent] = links[parent] || {};
links[parent][id] = true;
});
infinite = list.filter(({ id }) => isCircular(id));
console.log(links);
console.log(infinite);
.as-console-wrapper { max-height: 100% !important; top: 0; }
来源:https://stackoverflow.com/questions/55338929/how-to-detect-a-loop-in-a-hierarchy-of-javascript-elements