// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -*- mode: C++ -*- // // Copyright 2020-2022 Google LLC // // Licensed under the Apache License v2.0 with LLVM Exceptions (the // "License"); you may not use this file except in compliance with the // License. You may obtain a copy of the License at // // https://llvm.org/LICENSE.txt // // 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. // // Author: Giuliano Procida #ifndef STG_SCC_H_ #define STG_SCC_H_ #include #include #include #include #include #include #include #include #include "error.h" namespace stg { /* * This is a streamlined Strongly-Connected Component finder for use with * procedurally generated or explored graphs, where the nodes and edges are not * known a priori. * * REQUIREMENTS * * The Node type must be copyable and have a suitable hash function. * * The user code must take the form of a Depth First Search which can be * repeatedly invoked on unvisited nodes until the whole graph has been * traversed. * * The user code must always follow edges to child nodes, even if it knows the * node has already been visited. The SCC finder needs to know about all edges. * * The user code must ensure that nodes are not re-examined once they have been * assigned to an SCC. The finder does not maintain any state for such nodes. * * GUARANTEES * * The SCC finder will ensure each node is examined exactly once. * * The SCCs will be presented in a topological order, leaves first. * * Note that within each SCC, nodes will be presented in DFS traversal order, * roots first. However, this is just an implementation detail, not a guarantee. * * USAGE * * Create an SCC finder with a lifetime bracketing a top-level DFS invocation. * * Before examining a node, check it's not been assigned to an SCC already and * then call Open. If the node is already "open" (i.e., is already waiting to be * assigned to an SCC), this will return an empty optional value and the node * should not be examined. If Open succeeds, a numerical node handle will be * returned and the node will be recorded as waiting to be assigned to an SCC. * * Now examine the node, making recursive calls to follow edges to other nodes. * Information about the node can be stored provisionally, but must NOT be used * to make decisions about whether to revisit it - that is Open's job. * * Once the examination is done, call Close, passing in the handle. If the node * has been identified as the "root" of an SCC, the whole SCC will be returned * as a vector of nodes. If any processing needs to be done (such as recording * the nodes as visited), this should be done now. Otherwise, an empty vector * will be returned. * * On destruction, after a top-level DFS invocation has completed, the SCC * finder will check that it is carrying no state. */ template > class SCC { public: ~SCC() noexcept(false) { if (std::uncaught_exceptions() == 0) { Check(open_.empty() && is_open_.empty() && root_index_.empty()) << "internal error: SCC state broken"; } } std::optional Open(const Node& node) { // Insertion will fail if the node is already open. auto [it, inserted] = is_open_.insert({node, is_open_.size()}); auto ix = it->second; if (!inserted) { // Pop indices to nodes which cannot be the root of their SCC. while (root_index_.back() > ix) { root_index_.pop_back(); } return {}; } // Unvisited, record open node and record root index. open_.push_back(node); root_index_.push_back(ix); return {ix}; } std::vector Close(size_t ix) { std::vector scc; Check(ix < open_.size()) << "internal error: illegal SCC node index"; if (ix == root_index_.back()) { // Close SCC. root_index_.pop_back(); const auto begin = open_.begin() + ix; const auto end = open_.end(); for (auto it = begin; it != end; ++it) { is_open_.erase(*it); } scc.reserve(end - begin); std::move(begin, end, std::back_inserter(scc)); open_.erase(begin, end); } return scc; } private: std::vector open_; // index to node std::unordered_map is_open_; // node to index std::vector root_index_; }; } // namespace stg #endif // STG_SCC_H_