blob: ca196b1e028ee67cfd60b9eee57850a81bbef415 [file] [log] [blame]
// 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 <cstddef>
#include <iterator>
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>
#include <vector>
#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
*
* 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.
*
* After a top-level DFS has completed, the SCC finder should be carrying no
* state. This can be verified by calling Empty.
*/
template <typename Node, typename Hash = std::hash<Node>>
class SCC {
public:
bool Empty() const {
return open_.empty() && is_open_.empty() && root_index_.empty();
}
std::optional<size_t> 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<Node> Close(size_t ix) {
std::vector<Node> 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<Node> open_; // index to node
std::unordered_map<Node, size_t, Hash> is_open_; // node to index
std::vector<size_t> root_index_;
};
} // namespace stg
#endif // STG_SCC_H_