001/*
002 * Copyright (C) 2017 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.graph;
018
019import static com.google.common.base.Preconditions.checkArgument;
020import static com.google.common.base.Preconditions.checkNotNull;
021import static java.util.Objects.requireNonNull;
022
023import com.google.common.collect.AbstractIterator;
024import com.google.common.collect.ImmutableSet;
025import com.google.errorprone.annotations.DoNotMock;
026import java.util.ArrayDeque;
027import java.util.Deque;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.Set;
031import org.jspecify.annotations.Nullable;
032
033/**
034 * An object that can traverse the nodes that are reachable from a specified (set of) start node(s)
035 * using a specified {@link SuccessorsFunction}.
036 *
037 * <p>There are two entry points for creating a {@code Traverser}: {@link
038 * #forTree(SuccessorsFunction)} and {@link #forGraph(SuccessorsFunction)}. You should choose one
039 * based on your answers to the following questions:
040 *
041 * <ol>
042 *   <li>Is there only one path to any node that's reachable from any start node? (If so, the graph
043 *       to be traversed is a tree or forest even if it is a subgraph of a graph which is neither.)
044 *   <li>Are the node objects' implementations of {@code equals()}/{@code hashCode()} <a
045 *       href="https://github.com/google/guava/wiki/GraphsExplained#non-recursiveness">recursive</a>?
046 * </ol>
047 *
048 * <p>If your answers are:
049 *
050 * <ul>
051 *   <li>(1) "no" and (2) "no", use {@link #forGraph(SuccessorsFunction)}.
052 *   <li>(1) "yes" and (2) "yes", use {@link #forTree(SuccessorsFunction)}.
053 *   <li>(1) "yes" and (2) "no", you can use either, but {@code forTree()} will be more efficient.
054 *   <li>(1) "no" and (2) "yes", <b><i>neither will work</i></b>, but if you transform your node
055 *       objects into a non-recursive form, you can use {@code forGraph()}.
056 * </ul>
057 *
058 * @author Jens Nyman
059 * @param <N> Node parameter type
060 * @since 23.1
061 */
062@DoNotMock(
063    "Call forGraph or forTree, passing a lambda or a Graph with the desired edges (built with"
064        + " GraphBuilder)")
065public abstract class Traverser<N> {
066  private final SuccessorsFunction<N> successorFunction;
067
068  private Traverser(SuccessorsFunction<N> successorFunction) {
069    this.successorFunction = checkNotNull(successorFunction);
070  }
071
072  /**
073   * Creates a new traverser for the given general {@code graph}.
074   *
075   * <p>Traversers created using this method are guaranteed to visit each node reachable from the
076   * start node(s) at most once.
077   *
078   * <p>If you know that no node in {@code graph} is reachable by more than one path from the start
079   * node(s), consider using {@link #forTree(SuccessorsFunction)} instead.
080   *
081   * <p><b>Performance notes</b>
082   *
083   * <ul>
084   *   <li>Traversals require <i>O(n)</i> time (where <i>n</i> is the number of nodes reachable from
085   *       the start node), assuming that the node objects have <i>O(1)</i> {@code equals()} and
086   *       {@code hashCode()} implementations. (See the <a
087   *       href="https://github.com/google/guava/wiki/GraphsExplained#elements-must-be-useable-as-map-keys">
088   *       notes on element objects</a> for more information.)
089   *   <li>While traversing, the traverser will use <i>O(n)</i> space (where <i>n</i> is the number
090   *       of nodes that have thus far been visited), plus <i>O(H)</i> space (where <i>H</i> is the
091   *       number of nodes that have been seen but not yet visited, that is, the "horizon").
092   * </ul>
093   *
094   * @param graph {@link SuccessorsFunction} representing a general graph that may have cycles.
095   */
096  public static <N> Traverser<N> forGraph(SuccessorsFunction<N> graph) {
097    return new Traverser<N>(graph) {
098      @Override
099      Traversal<N> newTraversal() {
100        return Traversal.inGraph(graph);
101      }
102    };
103  }
104
105  /**
106   * Creates a new traverser for a directed acyclic graph that has at most one path from the start
107   * node(s) to any node reachable from the start node(s), and has no paths from any start node to
108   * any other start node, such as a tree or forest.
109   *
110   * <p>{@code forTree()} is especially useful (versus {@code forGraph()}) in cases where the data
111   * structure being traversed is, in addition to being a tree/forest, also defined <a
112   * href="https://github.com/google/guava/wiki/GraphsExplained#non-recursiveness">recursively</a>.
113   * This is because the {@code forTree()}-based implementations don't keep track of visited nodes,
114   * and therefore don't need to call {@code equals()} or {@code hashCode()} on the node objects;
115   * this saves both time and space versus traversing the same graph using {@code forGraph()}.
116   *
117   * <p>Providing a graph to be traversed for which there is more than one path from the start
118   * node(s) to any node may lead to:
119   *
120   * <ul>
121   *   <li>Traversal not terminating (if the graph has cycles)
122   *   <li>Nodes being visited multiple times (if multiple paths exist from any start node to any
123   *       node reachable from any start node)
124   * </ul>
125   *
126   * <p><b>Performance notes</b>
127   *
128   * <ul>
129   *   <li>Traversals require <i>O(n)</i> time (where <i>n</i> is the number of nodes reachable from
130   *       the start node).
131   *   <li>While traversing, the traverser will use <i>O(H)</i> space (where <i>H</i> is the number
132   *       of nodes that have been seen but not yet visited, that is, the "horizon").
133   * </ul>
134   *
135   * <p><b>Examples</b> (all edges are directed facing downwards)
136   *
137   * <p>The graph below would be valid input with start nodes of {@code a, f, c}. However, if {@code
138   * b} were <i>also</i> a start node, then there would be multiple paths to reach {@code e} and
139   * {@code h}.
140   *
141   * {@snippet :
142   *    a     b      c
143   *   / \   / \     |
144   *  /   \ /   \    |
145   * d     e     f   g
146   *       |
147   *       |
148   *       h
149   * }
150   *
151   * <p>.
152   *
153   * <p>The graph below would be a valid input with start nodes of {@code a, f}. However, if {@code
154   * b} were a start node, there would be multiple paths to {@code f}.
155   *
156   * {@snippet :
157   *    a     b
158   *   / \   / \
159   *  /   \ /   \
160   * c     d     e
161   *        \   /
162   *         \ /
163   *          f
164   * }
165   *
166   * <p><b>Note on binary trees</b>
167   *
168   * <p>This method can be used to traverse over a binary tree. Given methods {@code
169   * leftChild(node)} and {@code rightChild(node)}, this method can be called as
170   *
171   * {@snippet :
172   * Traverser.forTree(node -> ImmutableList.of(leftChild(node), rightChild(node)));
173   * }
174   *
175   * @param tree {@link SuccessorsFunction} representing a directed acyclic graph that has at most
176   *     one path between any two nodes
177   */
178  public static <N> Traverser<N> forTree(SuccessorsFunction<N> tree) {
179    if (tree instanceof BaseGraph) {
180      checkArgument(((BaseGraph<?>) tree).isDirected(), "Undirected graphs can never be trees.");
181    }
182    if (tree instanceof Network) {
183      checkArgument(((Network<?, ?>) tree).isDirected(), "Undirected networks can never be trees.");
184    }
185    return new Traverser<N>(tree) {
186      @Override
187      Traversal<N> newTraversal() {
188        return Traversal.inTree(tree);
189      }
190    };
191  }
192
193  /**
194   * Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
195   * the order of a breadth-first traversal. That is, all the nodes of depth 0 are returned, then
196   * depth 1, then 2, and so on.
197   *
198   * <p><b>Example:</b> The following graph with {@code startNode} {@code a} would return nodes in
199   * the order {@code abcdef} (assuming successors are returned in alphabetical order).
200   *
201   * {@snippet :
202   * b ---- a ---- d
203   * |      |
204   * |      |
205   * e ---- c ---- f
206   * }
207   *
208   * <p>The behavior of this method is undefined if the nodes, or the topology of the graph, change
209   * while iteration is in progress.
210   *
211   * <p>The returned {@code Iterable} can be iterated over multiple times. Every iterator will
212   * compute its next element on the fly. It is thus possible to limit the traversal to a certain
213   * number of nodes as follows:
214   *
215   * {@snippet :
216   * Iterables.limit(Traverser.forGraph(graph).breadthFirst(node), maxNumberOfNodes);
217   * }
218   *
219   * <p>See <a href="https://en.wikipedia.org/wiki/Breadth-first_search">Wikipedia</a> for more
220   * info.
221   *
222   * @throws IllegalArgumentException if {@code startNode} is not an element of the graph
223   */
224  public final Iterable<N> breadthFirst(N startNode) {
225    return breadthFirst(ImmutableSet.of(startNode));
226  }
227
228  /**
229   * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
230   * startNodes}, in the order of a breadth-first traversal. This is equivalent to a breadth-first
231   * traversal of a graph with an additional root node whose successors are the listed {@code
232   * startNodes}.
233   *
234   * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph
235   * @see #breadthFirst(Object)
236   * @since 24.1
237   */
238  public final Iterable<N> breadthFirst(Iterable<? extends N> startNodes) {
239    ImmutableSet<N> validated = validate(startNodes);
240    return () -> newTraversal().breadthFirst(validated.iterator());
241  }
242
243  /**
244   * Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
245   * the order of a depth-first pre-order traversal. "Pre-order" implies that nodes appear in the
246   * {@code Iterable} in the order in which they are first visited.
247   *
248   * <p><b>Example:</b> The following graph with {@code startNode} {@code a} would return nodes in
249   * the order {@code abecfd} (assuming successors are returned in alphabetical order).
250   *
251   * {@snippet :
252   * b ---- a ---- d
253   * |      |
254   * |      |
255   * e ---- c ---- f
256   * }
257   *
258   * <p>The behavior of this method is undefined if the nodes, or the topology of the graph, change
259   * while iteration is in progress.
260   *
261   * <p>The returned {@code Iterable} can be iterated over multiple times. Every iterator will
262   * compute its next element on the fly. It is thus possible to limit the traversal to a certain
263   * number of nodes as follows:
264   *
265   * {@snippet :
266   * Iterables.limit(Traverser.forGraph(graph).depthFirstPreOrder(node), maxNumberOfNodes);
267   * }
268   *
269   * <p>See <a href="https://en.wikipedia.org/wiki/Depth-first_search">Wikipedia</a> for more info.
270   *
271   * @throws IllegalArgumentException if {@code startNode} is not an element of the graph
272   */
273  public final Iterable<N> depthFirstPreOrder(N startNode) {
274    return depthFirstPreOrder(ImmutableSet.of(startNode));
275  }
276
277  /**
278   * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
279   * startNodes}, in the order of a depth-first pre-order traversal. This is equivalent to a
280   * depth-first pre-order traversal of a graph with an additional root node whose successors are
281   * the listed {@code startNodes}.
282   *
283   * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph
284   * @see #depthFirstPreOrder(Object)
285   * @since 24.1
286   */
287  public final Iterable<N> depthFirstPreOrder(Iterable<? extends N> startNodes) {
288    ImmutableSet<N> validated = validate(startNodes);
289    return () -> newTraversal().preOrder(validated.iterator());
290  }
291
292  /**
293   * Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
294   * the order of a depth-first post-order traversal. "Post-order" implies that nodes appear in the
295   * {@code Iterable} in the order in which they are visited for the last time.
296   *
297   * <p><b>Example:</b> The following graph with {@code startNode} {@code a} would return nodes in
298   * the order {@code fcebda} (assuming successors are returned in alphabetical order).
299   *
300   * {@snippet :
301   * b ---- a ---- d
302   * |      |
303   * |      |
304   * e ---- c ---- f
305   * }
306   *
307   * <p>The behavior of this method is undefined if the nodes, or the topology of the graph, change
308   * while iteration is in progress.
309   *
310   * <p>The returned {@code Iterable} can be iterated over multiple times. Every iterator will
311   * compute its next element on the fly. It is thus possible to limit the traversal to a certain
312   * number of nodes as follows:
313   *
314   * {@snippet :
315   * Iterables.limit(Traverser.forGraph(graph).depthFirstPostOrder(node), maxNumberOfNodes);
316   * }
317   *
318   * <p>See <a href="https://en.wikipedia.org/wiki/Depth-first_search">Wikipedia</a> for more info.
319   *
320   * @throws IllegalArgumentException if {@code startNode} is not an element of the graph
321   */
322  public final Iterable<N> depthFirstPostOrder(N startNode) {
323    return depthFirstPostOrder(ImmutableSet.of(startNode));
324  }
325
326  /**
327   * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
328   * startNodes}, in the order of a depth-first post-order traversal. This is equivalent to a
329   * depth-first post-order traversal of a graph with an additional root node whose successors are
330   * the listed {@code startNodes}.
331   *
332   * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph
333   * @see #depthFirstPostOrder(Object)
334   * @since 24.1
335   */
336  public final Iterable<N> depthFirstPostOrder(Iterable<? extends N> startNodes) {
337    ImmutableSet<N> validated = validate(startNodes);
338    return () -> newTraversal().postOrder(validated.iterator());
339  }
340
341  abstract Traversal<N> newTraversal();
342
343  @SuppressWarnings("CheckReturnValue")
344  private ImmutableSet<N> validate(Iterable<? extends N> startNodes) {
345    ImmutableSet<N> copy = ImmutableSet.copyOf(startNodes);
346    for (N node : copy) {
347      successorFunction.successors(node); // Will throw if node doesn't exist
348    }
349    return copy;
350  }
351
352  /**
353   * Abstracts away the difference between traversing a graph vs. a tree. For a tree, we just take
354   * the next element from the next non-empty iterator; for graph, we need to loop through the next
355   * non-empty iterator to find first unvisited node.
356   */
357  private abstract static class Traversal<N> {
358    final SuccessorsFunction<N> successorFunction;
359
360    Traversal(SuccessorsFunction<N> successorFunction) {
361      this.successorFunction = successorFunction;
362    }
363
364    static <N> Traversal<N> inGraph(SuccessorsFunction<N> graph) {
365      Set<N> visited = new HashSet<>();
366      return new Traversal<N>(graph) {
367        @Override
368        @Nullable N visitNext(Deque<Iterator<? extends N>> horizon) {
369          Iterator<? extends N> top = horizon.getFirst();
370          while (top.hasNext()) {
371            N element = top.next();
372            // requireNonNull is safe because horizon contains only graph nodes.
373            /*
374             * TODO(cpovirk): Replace these two statements with one (`N element =
375             * requireNonNull(top.next())`) once our checker supports it.
376             *
377             * (The problem is likely
378             * https://github.com/jspecify/jspecify-reference-checker/blob/61aafa4ae52594830cfc2d61c8b113009dbdb045/src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java#L896)
379             */
380            requireNonNull(element);
381            if (visited.add(element)) {
382              return element;
383            }
384          }
385          horizon.removeFirst();
386          return null;
387        }
388      };
389    }
390
391    static <N> Traversal<N> inTree(SuccessorsFunction<N> tree) {
392      return new Traversal<N>(tree) {
393        @Override
394        @Nullable N visitNext(Deque<Iterator<? extends N>> horizon) {
395          Iterator<? extends N> top = horizon.getFirst();
396          if (top.hasNext()) {
397            return checkNotNull(top.next());
398          }
399          horizon.removeFirst();
400          return null;
401        }
402      };
403    }
404
405    final Iterator<N> breadthFirst(Iterator<? extends N> startNodes) {
406      return topDown(startNodes, InsertionOrder.BACK);
407    }
408
409    final Iterator<N> preOrder(Iterator<? extends N> startNodes) {
410      return topDown(startNodes, InsertionOrder.FRONT);
411    }
412
413    /**
414     * In top-down traversal, an ancestor node is always traversed before any of its descendant
415     * nodes. The traversal order among descendant nodes (particularly aunts and nieces) are
416     * determined by the {@code InsertionOrder} parameter: nieces are placed at the FRONT before
417     * aunts for pre-order; while in BFS they are placed at the BACK after aunts.
418     */
419    private Iterator<N> topDown(Iterator<? extends N> startNodes, InsertionOrder order) {
420      Deque<Iterator<? extends N>> horizon = new ArrayDeque<>();
421      horizon.add(startNodes);
422      return new AbstractIterator<N>() {
423        @Override
424        protected @Nullable N computeNext() {
425          do {
426            N next = visitNext(horizon);
427            if (next != null) {
428              Iterator<? extends N> successors = successorFunction.successors(next).iterator();
429              if (successors.hasNext()) {
430                // BFS: horizon.addLast(successors)
431                // Pre-order: horizon.addFirst(successors)
432                order.insertInto(horizon, successors);
433              }
434              return next;
435            }
436          } while (!horizon.isEmpty());
437          return endOfData();
438        }
439      };
440    }
441
442    final Iterator<N> postOrder(Iterator<? extends N> startNodes) {
443      Deque<N> ancestorStack = new ArrayDeque<>();
444      Deque<Iterator<? extends N>> horizon = new ArrayDeque<>();
445      horizon.add(startNodes);
446      return new AbstractIterator<N>() {
447        @Override
448        protected @Nullable N computeNext() {
449          for (N next = visitNext(horizon); next != null; next = visitNext(horizon)) {
450            Iterator<? extends N> successors = successorFunction.successors(next).iterator();
451            if (!successors.hasNext()) {
452              return next;
453            }
454            horizon.addFirst(successors);
455            ancestorStack.push(next);
456          }
457          // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker.
458          if (!ancestorStack.isEmpty()) {
459            return ancestorStack.pop();
460          }
461          return endOfData();
462        }
463      };
464    }
465
466    /**
467     * Visits the next node from the top iterator of {@code horizon} and returns the visited node.
468     * Null is returned to indicate reaching the end of the top iterator.
469     *
470     * <p>For example, if horizon is {@code [[a, b], [c, d], [e]]}, {@code visitNext()} will return
471     * {@code [a, b, null, c, d, null, e, null]} sequentially, encoding the topological structure.
472     * (Note, however, that the callers of {@code visitNext()} often insert additional iterators
473     * into {@code horizon} between calls to {@code visitNext()}. This causes them to receive
474     * additional values interleaved with those shown above.)
475     */
476    abstract @Nullable N visitNext(Deque<Iterator<? extends N>> horizon);
477  }
478
479  /** Poor man's method reference for {@code Deque::addFirst} and {@code Deque::addLast}. */
480  private enum InsertionOrder {
481    FRONT {
482      @Override
483      <T> void insertInto(Deque<T> deque, T value) {
484        deque.addFirst(value);
485      }
486    },
487    BACK {
488      @Override
489      <T> void insertInto(Deque<T> deque, T value) {
490        deque.addLast(value);
491      }
492    };
493
494    abstract <T> void insertInto(Deque<T> deque, T value);
495  }
496}