Until now, we have implemented topological sorting in db/cql_type_parser.cc but it is specific to its usage. Now we want to use topological sorting in another place, so generic sorting algoritm provides one implementation to be reused in several places.
68 lines
2.0 KiB
C++
68 lines
2.0 KiB
C++
/*
|
|
* Copyright (C) 2024-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <seastar/core/future.hh>
|
|
#include "seastar/coroutine/maybe_yield.hh"
|
|
#include "utils/stall_free.hh"
|
|
|
|
namespace utils {
|
|
|
|
/**
|
|
* Topological sort a DAG using Kahn's algorithm.
|
|
*
|
|
* @param vertices Contains all vertices of the graph
|
|
* @param adjacency_map Entry {k, v} means there is an k->v edge in the graph
|
|
* @return a vector of topologically sorted vertices.
|
|
If there is an edge k->v, then vertex k will be before vertex v.
|
|
* @throws std::runtime_error when a graph has any cycle.
|
|
*/
|
|
template<typename T, typename Compare = std::less<T>>
|
|
seastar::future<std::vector<T>> topological_sort(const std::vector<T>& vertices, const std::multimap<T, T, Compare>& adjacency_map) {
|
|
std::map<T, size_t, Compare> ref_count_map; // Contains counters how many edges point (reference) to a vertex
|
|
std::vector<T> sorted;
|
|
sorted.reserve(vertices.size());
|
|
|
|
for (auto& edge: adjacency_map) {
|
|
ref_count_map[edge.second]++;
|
|
}
|
|
|
|
// Push to result vertices which reference counter == 0
|
|
for (auto& v: vertices) {
|
|
if (!ref_count_map.contains(v)) {
|
|
sorted.emplace_back(v);
|
|
}
|
|
}
|
|
|
|
// Iterate over already sorted vertices,
|
|
// decrease counter of vertices to which sorted vertices points,
|
|
// push to the end of `sorted` vector vertices which counter == 0.
|
|
for (size_t i = 0; i < sorted.size(); i++) {
|
|
auto [it_begin, it_end] = adjacency_map.equal_range(sorted[i]);
|
|
|
|
while (it_begin != it_end) {
|
|
auto d = it_begin->second;
|
|
if (--ref_count_map[d] == 0) {
|
|
sorted.push_back(d);
|
|
}
|
|
++it_begin;
|
|
}
|
|
co_await seastar::coroutine::maybe_yield();
|
|
}
|
|
co_await utils::clear_gently(ref_count_map);
|
|
|
|
if (sorted.size() != vertices.size()) {
|
|
throw std::runtime_error("Detected cycle in the graph.");
|
|
}
|
|
co_return sorted;
|
|
}
|
|
|
|
}
|