cdc.graphs.core.GraphCycles Maven / Gradle / Ivy
package cdc.graphs.core;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import cdc.graphs.EdgeDirection;
import cdc.graphs.EdgeTip;
import cdc.graphs.GraphAdapter;
import cdc.graphs.NodeConnectivity;
import cdc.graphs.impl.ExplicitSubGraph;
import cdc.graphs.impl.RestrictionSubGraph;
import cdc.util.function.Evaluation;
import cdc.util.function.Evaluator;
import cdc.util.function.Visitor;
/**
* Set of algorithms related to cycles.
*
* @author Damien Carbonne
*
* @param Node class
* @param Edge class
*/
public class GraphCycles extends GraphBase {
public GraphCycles(GraphAdapter adapter) {
super(adapter);
}
/**
* @return whether the graph contains cycles or not.
*/
public boolean containsCycles() {
final CycleDetector internal = new CycleDetector<>(adapter);
return internal.eval();
}
/**
* Returns {@code true} when 2 nodes are connected.
*
* @param source The source node.
* @param target The target node.
* @return {@code true} when {@code source} is connected to {@code target}.
*/
public boolean areConnected(N source,
N target) {
final ConnectionDetector detector = new ConnectionDetector<>(adapter, source, target);
return detector.areConnected();
}
/**
* Returns whether a particular node is member of a cycle or not.
*
* @param node The tested node.
* @return whether node is member of a cycle or not.
*/
public boolean nodeIsCycleMember(N node) {
return areConnected(node, node);
}
public boolean edgeIsCycleMember(E edge) {
final N src = adapter.getTip(edge, EdgeTip.SOURCE);
final N tgt = adapter.getTip(edge, EdgeTip.TARGET);
return areConnected(tgt, src);
}
/**
* Returns the subgraph that contains all nodes and edges that are
* participating to cycles.
*
* @return the subgraph of cycle members.
*/
public ExplicitSubGraph computeCyclesMembers() {
final RestrictionSubGraph subgraph = new RestrictionSubGraph<>(adapter);
final HashSet current = new HashSet<>();
final HashSet next = new HashSet<>();
for (final N node : subgraph.getNodes()) {
if (subgraph.getConnectivity(node) != NodeConnectivity.IN_OUT) {
current.add(node);
}
}
while (!current.isEmpty()) {
// Computes set of nodes that are in relation with nodes of current
next.clear();
for (final N node : current) {
for (final E edge : subgraph.getEdges(node, EdgeDirection.OUTGOING)) {
next.add(subgraph.getTip(edge, EdgeTip.TARGET));
}
for (final E edge : subgraph.getEdges(node, EdgeDirection.INGOING)) {
next.add(subgraph.getTip(edge, EdgeTip.SOURCE));
}
}
// Remove nodes in current
for (final N node : current) {
subgraph.removeNode(node);
}
next.removeAll(subgraph.getRemovedNodes());
current.clear();
for (final N node : next) {
if (subgraph.getConnectivity(node) != NodeConnectivity.IN_OUT) {
current.add(node);
}
}
}
detectNonMemberNodes(subgraph);
detectNonMemberEdges(subgraph);
return subgraph;
}
private void detectNonMemberNodes(RestrictionSubGraph subgraph) {
final Set nonMemberNodes = new HashSet<>();
for (final N node : subgraph.getNodes()) {
if (!nodeIsCycleMember(node)) {
nonMemberNodes.add(node);
}
}
for (final N node : nonMemberNodes) {
subgraph.removeNode(node);
}
}
private void detectNonMemberEdges(RestrictionSubGraph subgraph) {
final Set nonMemberEdges = new HashSet<>();
for (final E edge : subgraph.getEdges()) {
if (!edgeIsCycleMember(edge)) {
nonMemberEdges.add(edge);
}
}
for (final E edge : nonMemberEdges) {
subgraph.removeEdge(edge);
}
}
/**
* Computes the set of nodes and edges that are participating to cycles.
*
* @param nodes Collection of nodes that will be filled with nodes participating to a cycle.
* This collection is cleared on startup.
* @param edges Collection of edges that will be filled with edges participating to a cycle.
* This collection is cleared on startup.
*/
public void computeCyclesMembers(Collection nodes,
Collection edges) {
final ExplicitSubGraph subgraph = computeCyclesMembers();
// All remaining nodes and edges are participating to cycles.
nodes.clear();
for (final N node : subgraph.getNodes()) {
nodes.add(node);
}
edges.clear();
for (final E edge : subgraph.getEdges()) {
edges.add(edge);
}
}
/**
* Class dedicated to detection of cycles in a graph.
*
* @author Damien Carbonne
* @param The node type.
* @param The edge type.
*/
private static class CycleDetector extends GraphBase {
private final Set visited = new HashSet<>();
private final Set critical = new HashSet<>();
public CycleDetector(GraphAdapter adapter) {
super(adapter);
}
public boolean eval() {
visited.clear();
critical.clear();
for (final N node : adapter.getNodes()) {
if (!visited.contains(node)) {
final boolean found = visit(node);
if (found) {
return true;
}
}
}
return false;
}
private final boolean visit(final N node) {
if (critical.contains(node)) {
return true;
} else {
critical.add(node);
for (final E edge : adapter.getEdges(node, EdgeDirection.OUTGOING)) {
final N successor = adapter.getTip(edge, EdgeTip.TARGET);
final boolean found = visit(successor);
if (found) {
return true;
}
}
critical.remove(node);
visited.add(node);
return false;
}
}
}
private static class ConnectionDetector extends GraphBase {
boolean found = false;
private final Evaluator evaluator;
public ConnectionDetector(GraphAdapter adapter,
N source,
N target) {
super(adapter);
this.evaluator = item -> {
if (adapter.hasEdge(item, target)) {
found = true;
return Evaluation.PRUNE;
} else {
return Evaluation.CONTINUE;
}
};
final GraphTraverser traverser = new GraphTraverser<>(adapter);
traverser.traverseDepthFirstPre(source,
EdgeDirection.OUTGOING,
Visitor.ignore(),
evaluator);
}
public final boolean areConnected() {
return found;
}
}
}