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

com.google.common.graph.Traverser Maven / Gradle / Ivy

Go to download

Guava is a suite of core and expanded libraries that include utility classes, google's collections, io classes, and much much more.

There is a newer version: 33.1.0-jre
Show newest version
/*
 * 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 com.google.common.graph;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.Beta;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.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;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * An object that can traverse the nodes that are reachable from a specified (set of) start node(s)
 * using a specified {@link SuccessorsFunction}.
 *
 * 

There are two entry points for creating a {@code Traverser}: {@link * #forTree(SuccessorsFunction)} and {@link #forGraph(SuccessorsFunction)}. You should choose one * based on your answers to the following questions: * *

    *
  1. Is there only one path to any node that's reachable from any start node? (If so, the graph * to be traversed is a tree or forest even if it is a subgraph of a graph which is neither.) *
  2. Are the node objects' implementations of {@code equals()}/{@code hashCode()} recursive? *
* *

If your answers are: * *

    *
  • (1) "no" and (2) "no", use {@link #forGraph(SuccessorsFunction)}. *
  • (1) "yes" and (2) "yes", use {@link #forTree(SuccessorsFunction)}. *
  • (1) "yes" and (2) "no", you can use either, but {@code forTree()} will be more efficient. *
  • (1) "no" and (2) "yes", neither will work, but if you transform your node * objects into a non-recursive form, you can use {@code forGraph()}. *
* * @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}. * *

Traversers created using this method are guaranteed to visit each node reachable from the * start node(s) at most once. * *

If you know that no node in {@code graph} is reachable by more than one path from the start * node(s), 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. (See the * notes on element objects for more information.) *
  • 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(s) to any node reachable from the start node(s), and has no paths from any start node to * any other start node, such as a tree or forest. * *

{@code forTree()} is especially useful (versus {@code forGraph()}) in cases where the data * structure being traversed is, in addition to being a tree/forest, also defined recursively. * This is because the {@code forTree()}-based implementations don't keep track of visited nodes, * and therefore don't need to call `equals()` or `hashCode()` on the node objects; this saves * both time and space versus traversing the same graph using {@code forGraph()}. * *

Providing a graph to be traversed for which there is more than one path from the start * node(s) to any node may lead to: * *

    *
  • Traversal not terminating (if the graph has cycles) *
  • Nodes being visited multiple times (if multiple paths exist from any start node to any * node reachable from any start node) *
* *

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 (all edges are directed facing downwards) * *

The graph below would be valid input with start nodes of {@code a, f, c}. However, if {@code * b} were also a start node, then there would be multiple paths to reach {@code e} and * {@code h}. * *

{@code
   *    a     b      c
   *   / \   / \     |
   *  /   \ /   \    |
   * d     e     f   g
   *       |
   *       |
   *       h
   * }
* *

. * *

The graph below would be a valid input with start nodes of {@code a, f}. However, if {@code * b} were a start node, there would be multiple paths to {@code f}. * *

{@code
   *    a     b
   *   / \   / \
   *  /   \ /   \
   * c     d     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. * * @throws IllegalArgumentException if {@code startNode} is not an element of the graph */ public abstract Iterable breadthFirst(N startNode); /** * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code * startNodes}, in the order of a breadth-first traversal. This is equivalent to a breadth-first * traversal of a graph with an additional root node whose successors are the listed {@code * startNodes}. * * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph * @see #breadthFirst(Object) * @since 24.1 */ public abstract Iterable breadthFirst(Iterable startNodes); /** * 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. * * @throws IllegalArgumentException if {@code startNode} is not an element of the graph */ public abstract Iterable depthFirstPreOrder(N startNode); /** * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code * startNodes}, in the order of a depth-first pre-order traversal. This is equivalent to a * depth-first pre-order traversal of a graph with an additional root node whose successors are * the listed {@code startNodes}. * * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph * @see #depthFirstPreOrder(Object) * @since 24.1 */ public abstract Iterable depthFirstPreOrder(Iterable startNodes); /** * 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. * * @throws IllegalArgumentException if {@code startNode} is not an element of the graph */ public abstract Iterable depthFirstPostOrder(N startNode); /** * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code * startNodes}, in the order of a depth-first post-order traversal. This is equivalent to a * depth-first post-order traversal of a graph with an additional root node whose successors are * the listed {@code startNodes}. * * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph * @see #depthFirstPostOrder(Object) * @since 24.1 */ public abstract Iterable depthFirstPostOrder(Iterable startNodes); // Avoid subclasses outside of this class private Traverser() {} 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) { checkNotNull(startNode); return breadthFirst(ImmutableSet.of(startNode)); } @Override public Iterable breadthFirst(final Iterable startNodes) { checkNotNull(startNodes); if (Iterables.isEmpty(startNodes)) { return ImmutableSet.of(); } for (N startNode : startNodes) { checkThatNodeIsInGraph(startNode); } return new Iterable() { @Override public Iterator iterator() { return new BreadthFirstIterator(startNodes); } }; } @Override public Iterable depthFirstPreOrder(final N startNode) { checkNotNull(startNode); return depthFirstPreOrder(ImmutableSet.of(startNode)); } @Override public Iterable depthFirstPreOrder(final Iterable startNodes) { checkNotNull(startNodes); if (Iterables.isEmpty(startNodes)) { return ImmutableSet.of(); } for (N startNode : startNodes) { checkThatNodeIsInGraph(startNode); } return new Iterable() { @Override public Iterator iterator() { return new DepthFirstIterator(startNodes, Order.PREORDER); } }; } @Override public Iterable depthFirstPostOrder(final N startNode) { checkNotNull(startNode); return depthFirstPostOrder(ImmutableSet.of(startNode)); } @Override public Iterable depthFirstPostOrder(final Iterable startNodes) { checkNotNull(startNodes); if (Iterables.isEmpty(startNodes)) { return ImmutableSet.of(); } for (N startNode : startNodes) { checkThatNodeIsInGraph(startNode); } return new Iterable() { @Override public Iterator iterator() { return new DepthFirstIterator(startNodes, Order.POSTORDER); } }; } @SuppressWarnings("CheckReturnValue") private void checkThatNodeIsInGraph(N startNode) { // successors() throws an IllegalArgumentException for nodes that are not an element of the // graph. graph.successors(startNode); } private final class BreadthFirstIterator extends UnmodifiableIterator { private final Queue queue = new ArrayDeque<>(); private final Set visited = new HashSet<>(); BreadthFirstIterator(Iterable roots) { for (N root : roots) { // add all roots to the queue, skipping duplicates if (visited.add(root)) { queue.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(Iterable roots, Order order) { stack.push(new NodeAndSuccessors(null, roots)); this.order = order; } @Override protected N computeNext() { while (true) { if (stack.isEmpty()) { return endOfData(); } NodeAndSuccessors nodeAndSuccessors = stack.getFirst(); boolean firstVisit = visited.add(nodeAndSuccessors.node); boolean lastVisit = !nodeAndSuccessors.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 = nodeAndSuccessors.successorIterator.next(); if (!visited.contains(successor)) { stack.push(withSuccessors(successor)); } } if (produceNode && nodeAndSuccessors.node != null) { return nodeAndSuccessors.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 @Nullable N node; final Iterator successorIterator; NodeAndSuccessors(@Nullable N node, Iterable 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) { checkNotNull(startNode); return breadthFirst(ImmutableSet.of(startNode)); } @Override public Iterable breadthFirst(final Iterable startNodes) { checkNotNull(startNodes); if (Iterables.isEmpty(startNodes)) { return ImmutableSet.of(); } for (N startNode : startNodes) { checkThatNodeIsInTree(startNode); } return new Iterable() { @Override public Iterator iterator() { return new BreadthFirstIterator(startNodes); } }; } @Override public Iterable depthFirstPreOrder(final N startNode) { checkNotNull(startNode); return depthFirstPreOrder(ImmutableSet.of(startNode)); } @Override public Iterable depthFirstPreOrder(final Iterable startNodes) { checkNotNull(startNodes); if (Iterables.isEmpty(startNodes)) { return ImmutableSet.of(); } for (N node : startNodes) { checkThatNodeIsInTree(node); } return new Iterable() { @Override public Iterator iterator() { return new DepthFirstPreOrderIterator(startNodes); } }; } @Override public Iterable depthFirstPostOrder(final N startNode) { checkNotNull(startNode); return depthFirstPostOrder(ImmutableSet.of(startNode)); } @Override public Iterable depthFirstPostOrder(final Iterable startNodes) { checkNotNull(startNodes); if (Iterables.isEmpty(startNodes)) { return ImmutableSet.of(); } for (N startNode : startNodes) { checkThatNodeIsInTree(startNode); } return new Iterable() { @Override public Iterator iterator() { return new DepthFirstPostOrderIterator(startNodes); } }; } @SuppressWarnings("CheckReturnValue") private void checkThatNodeIsInTree(N startNode) { // successors() throws an IllegalArgumentException for nodes that are not an element of the // graph. tree.successors(startNode); } private final class BreadthFirstIterator extends UnmodifiableIterator { private final Queue queue = new ArrayDeque<>(); BreadthFirstIterator(Iterable roots) { for (N root : roots) { 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(Iterable roots) { stack.addLast(roots.iterator()); } @Override public boolean hasNext() { return !stack.isEmpty(); } @Override public N next() { Iterator iterator = stack.getLast(); // throws NoSuchElementException if empty N result = checkNotNull(iterator.next()); if (!iterator.hasNext()) { stack.removeLast(); } Iterator 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(Iterable roots) { stack.addLast(new NodeAndChildren(null, roots)); } @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(); if (top.node != null) { 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 @Nullable N node; final Iterator childIterator; NodeAndChildren(@Nullable N node, Iterable children) { this.node = node; this.childIterator = children.iterator(); } } } } private enum Order { PREORDER, POSTORDER } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy