类似于How to find if a graph has a cycle?,但更多适用于标准Java Set
我有一个具有必备技能的班级技能。
@Data
@Entity
public class Skill {
@Id
@GeneratedValue
private UUID id;
@OneToMany
private Set<Skill> prerequisites = new HashSet<>();
private String name;
}
我想确保前提条件中没有循环。
这是我刚开始时使用的,因为我只真正处理了自拍,所以这是行不通的。
@UtilityClass
public class CycleChecks {
/**
* Take a root object and a function to get its edges to see if there are any cycles.
*
* @param root root object
* @param edges a function that would take an object and get its edges.
* @param <T> an object that has edges
* @return has a cycle.
*/
public <T> boolean isCyclic(T root, Function<T, Iterable<T>> edges) {
final Set<T> visited = new HashSet<>();
return doIsCyclic(root, edges, visited);
}
private <T> boolean doIsCyclic(T vertex, Function<T, Iterable<T>> edges, Set<T> visited) {
if (visited.contains(vertex)) {
return true;
}
visited.add(vertex);
for (T edgeTarget : edges.apply(vertex)) {
if (doIsCyclic(edgeTarget, edges, visited)) {
return true;
}
}
return false;
}
}
答案 0 :(得分:1)
如下所示的情况很好,没有对它进行彻底的测试。仅使用一个保留ID的列表,我们可能会遇到以下情况:多个单独的技能具有相同的先决条件,并且被错误地检测为一个周期。例如,在这里发生这种情况:keycloak aggregated policies.,因此使用第二个递归列表。
您用以下名称调用:hasCycle(yourFirstSkill, new ArrayList<>(), new ArrayList<>());
public static boolean hasCycle(Skill entry, List<UUID> visited, List<UUID> recursion) {
UUID currentId = entry.getId();
if (recursion.contains(currentId))
return true;
if (visited.contains(currentId))
return false;
visited.add(currentId);
recursion.add(currentId);
for (final Skill prerequisite : entry.getPrerequisites()) {
if (hasCycle(prerequisite, visited, recursion)) {
return true;
}
}
recursion.remove(currentId);
return false;
}
答案 1 :(得分:0)
基于@Shadov的答案,我的最终解决方案。做了一些调整使其通用,我将Set
和visited
的{{1}}而不是列表中使用。
recursion
通过非科学方法消除了递归,将速度提高了5ms。
@UtilityClass
public class CycleChecks {
/**
* Take a root object and a function to get its edges to see if there are any cycles.
*
* @param root root object
* @param adjacentFunction a function that would take an object and return an iterable of objects that are adjacent to it.
* @param <T> an object that has edges
* @return has a cycle.
*/
public <T> boolean isCyclic(T root, Function<T, Iterable<T>> adjacentFunction) {
final Set<T> visited = new HashSet<>();
final Set<T> recursion = new HashSet<>();
return doIsCyclic(root, adjacentFunction, visited, recursion);
}
private <T> boolean doIsCyclic(T current, Function<T, Iterable<T>> adjacentFunction, Set<T> visited, Set<T> recursion) {
if (recursion.contains(current)) {
return true;
}
if (visited.contains(current)) {
return false;
}
visited.add(current);
recursion.add(current);
for (T adjacent : adjacentFunction.apply(current)) {
if (doIsCyclic(adjacent, adjacentFunction, visited, recursion)) {
return true;
}
}
recursion.remove(current);
return false;
}
}