org.testifyproject.guava.common.graph.Traverser Maven / Gradle / Ivy
/*
* Copyright (C) 2017 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.testifyproject.guava.common.graph;
import static org.testifyproject.guava.common.base.Preconditions.checkArgument;
import static org.testifyproject.guava.common.base.Preconditions.checkNotNull;
import static org.testifyproject.guava.common.collect.Iterators.singletonIterator;
import org.testifyproject.guava.common.annotations.Beta;
import org.testifyproject.guava.common.collect.AbstractIterator;
import org.testifyproject.guava.common.collect.Iterables;
import org.testifyproject.guava.common.collect.UnmodifiableIterator;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
/**
* Provides methods for traversing a graph.
*
* @author Jens Nyman
* @param Node parameter type
* @since 23.1
*/
@Beta
public abstract class Traverser {
/**
* Creates a new traverser for the given general {@code graph}.
*
* If {@code graph} is known to be tree-shaped, consider using {@link
* #forTree(SuccessorsFunction)} instead.
*
*
Performance notes
*
*
* - Traversals require O(n) time (where n is the number of nodes reachable from
* the start node), assuming that the node objects have O(1) {@code equals()} and
* {@code hashCode()} implementations.
*
- While traversing, the traverser will use O(n) space (where n is the number
* of nodes that have thus far been visited), plus O(H) space (where H is the
* number of nodes that have been seen but not yet visited, that is, the "horizon").
*
*
* @param graph {@link SuccessorsFunction} representing a general graph that may have cycles.
*/
public static Traverser forGraph(SuccessorsFunction graph) {
checkNotNull(graph);
return new GraphTraverser<>(graph);
}
/**
* Creates a new traverser for a directed acyclic graph that has at most one path from the start
* node to any node reachable from the start node, such as a tree.
*
* Providing graphs that don't conform to the above description may lead to:
*
*
* - Traversal not terminating (if the graph has cycles)
*
- Nodes being visited multiple times (if multiple paths exist from the start node to any
* node reachable from it)
*
*
* In these cases, use {@link #forGraph(SuccessorsFunction)} instead.
*
* Performance notes
*
*
* - Traversals require O(n) time (where n is the number of nodes reachable from
* the start node).
*
- While traversing, the traverser will use O(H) space (where H is the number
* of nodes that have been seen but not yet visited, that is, the "horizon").
*
*
* Examples
*
*
This is a valid input graph (all edges are directed facing downwards):
*
*
{@code
* a b c
* / \ / \ |
* / \ / \ |
* d e f g
* |
* |
* h
* }
*
* This is not a valid input graph (all edges are directed facing downwards):
*
*
{@code
* a b
* / \ / \
* / \ / \
* c d e
* \ /
* \ /
* f
* }
*
* because there are two paths from {@code b} to {@code f} ({@code b->d->f} and {@code
* b->e->f}).
*
*
Note on binary trees
*
*
This method can be used to traverse over a binary tree. Given methods {@code
* leftChild(node)} and {@code rightChild(node)}, this method can be called as
*
*
{@code
* Traverser.forTree(node -> ImmutableList.of(leftChild(node), rightChild(node)));
* }
*
* @param tree {@link SuccessorsFunction} representing a directed acyclic graph that has at most
* one path between any two nodes
*/
public static Traverser forTree(SuccessorsFunction tree) {
checkNotNull(tree);
if (tree instanceof BaseGraph) {
checkArgument(((BaseGraph>) tree).isDirected(), "Undirected graphs can never be trees.");
}
if (tree instanceof Network) {
checkArgument(((Network, ?>) tree).isDirected(), "Undirected networks can never be trees.");
}
return new TreeTraverser<>(tree);
}
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
* the order of a breadth-first traversal. That is, all the nodes of depth 0 are returned, then
* depth 1, then 2, and so on.
*
* Example: The following graph with {@code startNode} {@code a} would return nodes in
* the order {@code abcdef} (assuming successors are returned in alphabetical order).
*
*
{@code
* b ---- a ---- d
* | |
* | |
* e ---- c ---- f
* }
*
* The behavior of this method is undefined if the nodes, or the topology of the graph, change
* while iteration is in progress.
*
*
The returned {@code Iterable} can be iterated over multiple times. Every iterator will
* compute its next element on the fly. It is thus possible to limit the traversal to a certain
* number of nodes as follows:
*
*
{@code
* Iterables.limit(Traverser.forGraph(graph).breadthFirst(node), maxNumberOfNodes);
* }
*
* See Wikipedia for more
* info.
*/
public abstract Iterable breadthFirst(N startNode);
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
* the order of a depth-first pre-order traversal. "Pre-order" implies that nodes appear in the
* {@code Iterable} in the order in which they are first visited.
*
* Example: The following graph with {@code startNode} {@code a} would return nodes in
* the order {@code abecfd} (assuming successors are returned in alphabetical order).
*
*
{@code
* b ---- a ---- d
* | |
* | |
* e ---- c ---- f
* }
*
* The behavior of this method is undefined if the nodes, or the topology of the graph, change
* while iteration is in progress.
*
*
The returned {@code Iterable} can be iterated over multiple times. Every iterator will
* compute its next element on the fly. It is thus possible to limit the traversal to a certain
* number of nodes as follows:
*
*
{@code
* Iterables.limit(
* Traverser.forGraph(graph).depthFirstPreOrder(node), maxNumberOfNodes);
* }
*
* See Wikipedia for more info.
*/
public abstract Iterable depthFirstPreOrder(N startNode);
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
* the order of a depth-first post-order traversal. "Post-order" implies that nodes appear in the
* {@code Iterable} in the order in which they are visited for the last time.
*
* Example: The following graph with {@code startNode} {@code a} would return nodes in
* the order {@code fcebda} (assuming successors are returned in alphabetical order).
*
*
{@code
* b ---- a ---- d
* | |
* | |
* e ---- c ---- f
* }
*
* The behavior of this method is undefined if the nodes, or the topology of the graph, change
* while iteration is in progress.
*
*
The returned {@code Iterable} can be iterated over multiple times. Every iterator will
* compute its next element on the fly. It is thus possible to limit the traversal to a certain
* number of nodes as follows:
*
*
{@code
* Iterables.limit(
* Traverser.forGraph(graph).depthFirstPostOrder(node), maxNumberOfNodes);
* }
*
* See Wikipedia for more info.
*/
public abstract Iterable depthFirstPostOrder(N startNode);
private static final class GraphTraverser extends Traverser {
private final SuccessorsFunction graph;
GraphTraverser(SuccessorsFunction graph) {
this.graph = checkNotNull(graph);
}
@Override
public Iterable breadthFirst(final N startNode) {
return new Iterable() {
@Override
public Iterator iterator() {
return new BreadthFirstIterator(startNode);
}
};
}
@Override
public Iterable depthFirstPreOrder(final N startNode) {
return new Iterable() {
@Override
public Iterator iterator() {
return new DepthFirstIterator(startNode, Order.PREORDER);
}
};
}
@Override
public Iterable depthFirstPostOrder(final N startNode) {
return new Iterable() {
@Override
public Iterator iterator() {
return new DepthFirstIterator(startNode, Order.POSTORDER);
}
};
}
private final class BreadthFirstIterator extends UnmodifiableIterator {
private final Queue queue = new ArrayDeque<>();
private final Set visited = new HashSet<>();
BreadthFirstIterator(N root) {
queue.add(root);
visited.add(root);
}
@Override
public boolean hasNext() {
return !queue.isEmpty();
}
@Override
public N next() {
N current = queue.remove();
for (N neighbor : graph.successors(current)) {
if (visited.add(neighbor)) {
queue.add(neighbor);
}
}
return current;
}
}
private final class DepthFirstIterator extends AbstractIterator {
private final Deque stack = new ArrayDeque<>();
private final Set visited = new HashSet<>();
private final Order order;
DepthFirstIterator(N root, Order order) {
// our invariant is that in computeNext we call next on the iterator at the top first, so we
// need to start with one additional item on that iterator
stack.push(withSuccessors(root));
this.order = order;
}
@Override
protected N computeNext() {
while (true) {
if (stack.isEmpty()) {
return endOfData();
}
NodeAndSuccessors node = stack.getFirst();
boolean firstVisit = visited.add(node.node);
boolean lastVisit = !node.successorIterator.hasNext();
boolean produceNode =
(firstVisit && order == Order.PREORDER) || (lastVisit && order == Order.POSTORDER);
if (lastVisit) {
stack.pop();
} else {
// we need to push a neighbor, but only if we haven't already seen it
N successor = node.successorIterator.next();
if (!visited.contains(successor)) {
stack.push(withSuccessors(successor));
}
}
if (produceNode) {
return node.node;
}
}
}
NodeAndSuccessors withSuccessors(N node) {
return new NodeAndSuccessors(node, graph.successors(node));
}
/** A simple tuple of a node and a partially iterated {@link Iterator} of its successors. */
private final class NodeAndSuccessors {
final N node;
final Iterator extends N> successorIterator;
NodeAndSuccessors(N node, Iterable extends N> successors) {
this.node = node;
this.successorIterator = successors.iterator();
}
}
}
}
private static final class TreeTraverser extends Traverser {
private final SuccessorsFunction tree;
TreeTraverser(SuccessorsFunction tree) {
this.tree = checkNotNull(tree);
}
@Override
public Iterable breadthFirst(final N startNode) {
return new Iterable() {
@Override
public Iterator iterator() {
return new BreadthFirstIterator(startNode);
}
};
}
@Override
public Iterable depthFirstPreOrder(final N startNode) {
return new Iterable() {
@Override
public Iterator iterator() {
return new DepthFirstPreOrderIterator(startNode);
}
};
}
@Override
public Iterable depthFirstPostOrder(final N startNode) {
return new Iterable() {
@Override
public Iterator iterator() {
return new DepthFirstPostOrderIterator(startNode);
}
};
}
private final class BreadthFirstIterator extends UnmodifiableIterator {
private final Queue queue = new ArrayDeque<>();
BreadthFirstIterator(N root) {
queue.add(root);
}
@Override
public boolean hasNext() {
return !queue.isEmpty();
}
@Override
public N next() {
N current = queue.remove();
Iterables.addAll(queue, tree.successors(current));
return current;
}
}
private final class DepthFirstPreOrderIterator extends UnmodifiableIterator {
private final Deque> stack = new ArrayDeque<>();
DepthFirstPreOrderIterator(N root) {
stack.addLast(singletonIterator(checkNotNull(root)));
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public N next() {
Iterator extends N> iterator = stack.getLast(); // throws NoSuchElementException if empty
N result = checkNotNull(iterator.next());
if (!iterator.hasNext()) {
stack.removeLast();
}
Iterator extends N> childIterator = tree.successors(result).iterator();
if (childIterator.hasNext()) {
stack.addLast(childIterator);
}
return result;
}
}
private final class DepthFirstPostOrderIterator extends AbstractIterator {
private final ArrayDeque stack = new ArrayDeque<>();
DepthFirstPostOrderIterator(N root) {
stack.addLast(withChildren(root));
}
@Override
protected N computeNext() {
while (!stack.isEmpty()) {
NodeAndChildren top = stack.getLast();
if (top.childIterator.hasNext()) {
N child = top.childIterator.next();
stack.addLast(withChildren(child));
} else {
stack.removeLast();
return top.node;
}
}
return endOfData();
}
NodeAndChildren withChildren(N node) {
return new NodeAndChildren(node, tree.successors(node));
}
/** A simple tuple of a node and a partially iterated {@link Iterator} of its children. */
private final class NodeAndChildren {
final N node;
final Iterator extends N> childIterator;
NodeAndChildren(N node, Iterable extends N> children) {
this.node = node;
this.childIterator = children.iterator();
}
}
}
}
private enum Order {
PREORDER,
POSTORDER
}
}