All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cdc.graphs.core.SlimGraph Maven / Gradle / Ivy

There is a newer version: 0.71.2
Show newest version
package cdc.graphs.core;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import cdc.graphs.EdgeDirection;
import cdc.graphs.GraphAdapter;
import cdc.graphs.TraversalMethod;
import cdc.graphs.TraversalOrder;
import cdc.util.function.Evaluation;
import cdc.util.function.Evaluator;
import cdc.util.function.Visitor;
import cdc.util.lang.Checks;
import cdc.util.lang.FailureReaction;

/**
 * Set of simple algorithms for slim graphs.
 * 

* In a slim graph, the node type can be any object and there is no edge structure. * However, it is possible to explore neighbor nodes of any node. *

* The following algorithms are provided. *

    *
  • Transitive closure of a node. *
  • Traversal (depth first and breadth first) starting on a node. *
  • Topological sort of the transitive closure of a node. *
  • Are 2 nodes connected? *
* * Static and non-static versions of functions are provided. * * @author Damien Carbonne * @param The node type. */ public final class SlimGraph { private static final Logger LOGGER = LogManager.getLogger(SlimGraph.class); private static final String METHOD = "method"; private static final String NODE = "node"; private static final String ORDER = "order"; private static final String VISITOR = "visitor"; private static final String NEIGHBORS = "neighbors"; private final Function> neighbors; /** * Creates a slim graph. * * @param neighbors The function that returns the neighbors of a node. */ public SlimGraph(Function> neighbors) { Checks.isNotNull(neighbors, NEIGHBORS); this.neighbors = neighbors; } /** * Created a slim graph from a graph adapter. * * @param adapter The graph adapter. * @param direction The edge direction. */ public SlimGraph(GraphAdapter adapter, EdgeDirection direction) { Checks.isNotNull(adapter, "adapter"); this.neighbors = n -> adapter.getConnectedNodes(n, direction); } /** * Computes the transitive closure of a node. * * @param node The starting node. * @return The set of nodes that are directly or indirectly connected to {@code node}. */ public Set computeTransitiveClosure(N node) { return computeTransitiveClosure(node, neighbors); } /** * Most generic traversal function. * * @param node The initial node. * @param method The traversal method. * @param order The traversal order. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code method}, {@code order}, * or {@code visitor} is {@code null}. */ public void traverse(N node, TraversalMethod method, TraversalOrder order, Visitor visitor, Evaluator evaluator) { traverse(node, method, order, visitor, evaluator, neighbors); } /** * Depth first traversal. * * @param node The initial node. * @param order The traversal order. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code order}, {@code visitor} * or {@code neighbors} is {@code null}. */ public void traverseDepthFirst(N node, TraversalOrder order, Visitor visitor, Evaluator evaluator) { traverseDepthFirst(node, order, visitor, evaluator, neighbors); } /** * Depth first pre order traversal. * * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public void traverseDepthFirstPre(N node, Visitor visitor, Evaluator evaluator) { traverseDepthFirstPre(node, visitor, evaluator, neighbors); } /** * Depth first post order traversal. * * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public void traverseDepthFirstPost(N node, Visitor visitor, Evaluator evaluator) { traverseDepthFirstPost(node, visitor, evaluator, neighbors); } /** * Breadth first traversal. * * @param node The initial node. * @param order The traversal order. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code order}, {@code visitor} * or {@code neighbors} is {@code null}. */ public void traverseBreadthFirst(N node, TraversalOrder order, Visitor visitor, Evaluator evaluator) { traverseBreadthFirst(node, order, visitor, evaluator, neighbors); } /** * Breadth first pre order traversal. * * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public void traverseBreadthFirstPre(N node, Visitor visitor, Evaluator evaluator) { traverseBreadthFirstPre(node, visitor, evaluator, neighbors); } /** * Breadth first post order traversal. * * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public void traverseBreadthFirstPost(N node, Visitor visitor, Evaluator evaluator) { traverseBreadthFirstPost(node, visitor, evaluator, neighbors); } /** * Returns a topological sort of nodes connected to a root node. *

* If node {@code A} is connected to node {@code B}, then {@code A} will come before {@code B} in result. * * @param node The root node. * @param reaction The reaction to adopt if the graph contains loops. * @return A topological sort of nodes connected to {@code node} or an empty list. * @throws IllegalArgumentException When {@code node} or {@code neighbors} is {@code null}, * or when there is a loop and {@code reaction} is {@link FailureReaction#FAIL}. */ public List topologicalSort(N node, FailureReaction reaction) { return topologicalSort(node, reaction, neighbors); } /** * Returns a topological sort of nodes connected to a root node. *

* If node {@code A} is connected to node {@code B}, then {@code A} will come before {@code B} in result. * * @param node The root node. * @return A topological sort of nodes connected to {@code node} or an empty list. * @throws IllegalArgumentException When {@code node} or {@code neighbors} is {@code null}, * or when there is a loop. */ public List topologicalSort(N node) { return topologicalSort(node, neighbors); } /** * 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) { return areConnected(source, target, neighbors); } /** * Returns whether a particular node is member of a cycle or not. * * @param node The tested node. * @return whether {@code node} is member of a cycle or not. */ public boolean nodeIsCycleMember(N node) { return nodeIsCycleMember(node, neighbors); } /** * Computes the transitive closure of a node. * * @param The node type. * @param node The starting node. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @return The set of nodes that are directly or indirectly connected to {@code node}. */ public static Set computeTransitiveClosure(N node, Function> neighbors) { Checks.isNotNull(node, NODE); Checks.isNotNull(neighbors, NEIGHBORS); final Set done = new HashSet<>(); final Set todo = new HashSet<>(); todo.add(node); while (!todo.isEmpty()) { // Extract next node to process final N next = todo.iterator().next(); todo.remove(next); // next was not already processed if (!done.contains(next)) { done.add(next); for (final N n : neighbors.apply(next)) { if (!done.contains(n)) { todo.add(n); } } } } return done; } /** * Most generic traversal function. * * @param The node type. * @param node The initial node. * @param method The traversal method. * @param order The traversal order. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code method}, {@code order}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverse(N node, TraversalMethod method, TraversalOrder order, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(method, METHOD); if (method == TraversalMethod.BREADTH_FIRST) { traverseBreadthFirst(node, order, visitor, evaluator, neighbors); } else { traverseDepthFirst(node, order, visitor, evaluator, neighbors); } } /** * Depth first traversal. * * @param The node type. * @param node The initial node. * @param order The traversal order. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code order}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverseDepthFirst(N node, TraversalOrder order, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(order, ORDER); if (order == TraversalOrder.POST_ORDER) { traverseDepthFirstPost(node, visitor, evaluator, neighbors); } else { traverseDepthFirstPre(node, visitor, evaluator, neighbors); } } /** * Depth first pre order traversal. * * @param The node type. * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverseDepthFirstPre(N node, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(node, NODE); Checks.isNotNull(visitor, VISITOR); Checks.isNotNull(neighbors, NEIGHBORS); final DepthFirstTraverser traverser = new DepthFirstTraverser<>(visitor, evaluator, neighbors); traverser.traversePre(node); } /** * Depth first post order traversal. * * @param The node type. * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverseDepthFirstPost(N node, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(node, NODE); Checks.isNotNull(visitor, VISITOR); Checks.isNotNull(neighbors, NEIGHBORS); final DepthFirstTraverser traverser = new DepthFirstTraverser<>(visitor, evaluator, neighbors); traverser.traversePost(node); } /** * Breadth first traversal. * * @param The node type. * @param node The initial node. * @param order The traversal order. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code order}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverseBreadthFirst(N node, TraversalOrder order, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(order, ORDER); if (order == TraversalOrder.POST_ORDER) { traverseBreadthFirstPost(node, visitor, evaluator, neighbors); } else { traverseBreadthFirstPre(node, visitor, evaluator, neighbors); } } /** * Breadth first pre order traversal. * * @param The node type. * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverseBreadthFirstPre(N node, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(node, NODE); Checks.isNotNull(visitor, VISITOR); Checks.isNotNull(neighbors, NEIGHBORS); final BreadthFirstTraverser traverser = new BreadthFirstTraverser<>(evaluator, neighbors); traverser.traversePre(node, visitor); } /** * Breadth first post order traversal. * * @param The node type. * @param node The initial node. * @param visitor Visitor that will be invoked for each visited node. * @param evaluator An optional evaluator used to control traversal termination. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @throws IllegalArgumentException When {@code node}, {@code visitor} * or {@code neighbors} is {@code null}. */ public static void traverseBreadthFirstPost(N node, Visitor visitor, Evaluator evaluator, Function> neighbors) { Checks.isNotNull(node, NODE); Checks.isNotNull(visitor, VISITOR); Checks.isNotNull(neighbors, NEIGHBORS); final BreadthFirstTraverser traverser = new BreadthFirstTraverser<>(evaluator, neighbors); traverser.traversePost(node, visitor); } /** * Returns a topological sort of nodes connected to a root node. *

* If node {@code A} is connected to node {@code B}, then {@code A} will come before {@code B} in result. * * @param The node type. * @param node The root node. * @param reaction The reaction to adopt if the graph contains loops. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @return A topological sort of nodes connected to {@code node} or an empty list. * @throws IllegalArgumentException When {@code node} or {@code neighbors} is {@code null}, * or when there is a loop and {@code reaction} is {@link FailureReaction#FAIL}. */ public static List topologicalSort(N node, FailureReaction reaction, Function> neighbors) { Checks.isNotNull(node, NODE); Checks.isNotNull(neighbors, NEIGHBORS); final TopologicalSorter sorter = new TopologicalSorter<>(neighbors, node); if (sorter.hasLoop) { return FailureReaction.onError("Graph contains loops", LOGGER, reaction, Collections.emptyList(), IllegalArgumentException::new); } else { return sorter.sorted; } } /** * Returns a topological sort of nodes connected to a root node. *

* If node {@code A} is connected to node {@code B}, then {@code A} will come before {@code B} in result. * * @param The node type. * @param node The root node. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @return A topological sort of nodes connected to {@code node} or an empty list. * @throws IllegalArgumentException When {@code node} or {@code neighbors} is {@code null}, * or when there is a loop. */ public static List topologicalSort(N node, Function> neighbors) { return topologicalSort(node, FailureReaction.FAIL, neighbors); } /** * Returns {@code true} when 2 nodes are connected. * * @param source The source node. * @param target The target node. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @return {@code true} when {@code source} is connected to {@code target}. */ public static boolean areConnected(N source, N target, Function> neighbors) { final ConnectionDetector detector = new ConnectionDetector<>(neighbors, source, target); return detector.areConnected(); } /** * Returns whether a particular node is member of a cycle or not. * * @param node The tested node. * @param neighbors The function that returns the set of nodes that are directly connected to a node. * @return whether {@code node} is member of a cycle or not. */ public static boolean nodeIsCycleMember(N node, Function> neighbors) { return areConnected(node, node, neighbors); } /** * Internal class used to compute depth first traversal. * * @author Damien Carbonne * * @param The node type. */ private static class DepthFirstTraverser { private final Visitor visitor; private final Evaluator evaluator; private final Function> neighbors; private final Set visited = new HashSet<>(); public DepthFirstTraverser(Visitor visitor, Evaluator evaluator, Function> neighbors) { this.visitor = visitor; this.evaluator = evaluator; this.neighbors = neighbors; } public void traversePre(N node) { visitor.visit(node); if (Evaluator.continueTraversal(evaluator, node)) { visited.add(node); for (final N succ : neighbors.apply(node)) { if (!visited.contains(succ)) { traversePre(succ); } } } } void traversePost(N node) { if (Evaluator.continueTraversal(evaluator, node)) { visited.add(node); for (final N succ : neighbors.apply(node)) { if (!visited.contains(succ)) { traversePost(succ); } } } visitor.visit(node); } } /** * Internal class used to compute breadth first traversal. * * @author Damien Carbonne * * @param The node type. */ private static class BreadthFirstTraverser { private final Evaluator evaluator; private final Function> neighbors; public BreadthFirstTraverser(Evaluator evaluator, Function> neighbors) { this.evaluator = evaluator; this.neighbors = neighbors; } public void traversePre(N from, Visitor visitor) { final Deque q = new ArrayDeque<>(); final Set visited = new HashSet<>(); q.addLast(from); visited.add(from); while (!q.isEmpty()) { final N node = q.pollFirst(); visitor.visit(node); if (Evaluator.continueTraversal(evaluator, node)) { for (final N y : neighbors.apply(node)) { if (!visited.contains(y)) { visited.add(y); q.addLast(y); } } } } } public void traversePost(N from, Visitor visitor) { final List visited = new ArrayList<>(); traversePre(from, visited::add); for (int index = visited.size() - 1; index >= 0; index--) { final N node = visited.get(index); visitor.visit(node); } } } /** * Internal class used to compute topological sort. * * @author Damien Carbonne * * @param The node type. */ private static class TopologicalSorter { private final Function> neighbors; private final Set visited = new HashSet<>(); private final Set visitedInThisCallStack = new HashSet<>(); final List sorted = new ArrayList<>(); boolean hasLoop = false; public TopologicalSorter(Function> neighbors, N from) { this.neighbors = neighbors; visit(from); Collections.reverse(sorted); } private void visit(N node) { if (visited.contains(node)) { if (visitedInThisCallStack.contains(node)) { hasLoop = true; } } else { visited.add(node); visitedInThisCallStack.add(node); for (final N next : neighbors.apply(node)) { visit(next); } visitedInThisCallStack.remove(node); sorted.add(node); } } } /** * Internal class used to detect connected nodes. * * @author Damien Carbonne * * @param The node type. */ private static class ConnectionDetector { boolean found = false; // Used to skip visit of source node private boolean passedFirst = false; public ConnectionDetector(Function> neighbors, N source, N target) { final Evaluator evaluator = n -> { if (n == target && passedFirst) { found = true; passedFirst = true; return Evaluation.PRUNE; } else { passedFirst = true; return Evaluation.CONTINUE; } }; traverseDepthFirstPre(source, Visitor.ignore(), evaluator, neighbors); } public final boolean areConnected() { return found; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy