// Copyright 2014 The Bazel Authors. All rights reserved. // // 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.devtools.build.lib.graph; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toCollection; import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; /** *

The DFS class encapsulates a depth-first search visitation, including * the order in which nodes are to be visited relative to their successors * (PREORDER/POSTORDER), whether the forward or transposed graph is to be * used, and which nodes have been seen already.

* *

A variety of common uses of DFS are offered through methods of * Digraph; however clients can use this class directly for maximum * flexibility. See the implementation of * Digraph.getStronglyConnectedComponents() for an example.

* *

Clients should not modify the enclosing Digraph instance of a DFS * while a traversal is in progress.

*/ public class DFS { // (Preferred over a boolean to avoid parameter confusion.) public enum Order { PREORDER, POSTORDER } private final Order order; // = (PREORDER|POSTORDER) private final Comparator> edgeOrder; private final boolean transpose; private final Set> marked = CompactHashSet.create(); /** * Constructs a DFS instance for searching over the enclosing Digraph * instance, using the specified visitation parameters. * * @param order PREORDER or POSTORDER, determines node visitation order * @param edgeOrder an ordering in which the edges originating from the same * node should be visited (if null, the order is unspecified) * @param transpose iff true, the graph is implicitly transposed during * visitation. */ public DFS(Order order, final Comparator edgeOrder, boolean transpose) { this.order = order; this.transpose = transpose; this.edgeOrder = (edgeOrder == null) ? null : comparing(Node::getLabel, edgeOrder::compare); } public DFS(Order order, boolean transpose) { this(order, null, transpose); } /** * Returns the (immutable) set of nodes visited so far. */ public Set> getMarked() { return Collections.unmodifiableSet(marked); } public void visit(Node node, GraphVisitor visitor) { if (!marked.add(node)) { return; } if (order == Order.PREORDER) { visitor.visitNode(node); } Collection> edgeTargets = transpose ? node.getPredecessors() : node.getSuccessors(); if (edgeOrder != null) { List> mutableNodeList = edgeTargets.stream().sorted(edgeOrder).collect(toCollection(ArrayList::new)); edgeTargets = mutableNodeList; } for (Node v: edgeTargets) { visit(v, visitor); } if (order == Order.POSTORDER) { visitor.visitNode(node); } } }