// Copyright (c) 2019, QuantStack and Mamba Contributors // // Distributed under the terms of the BSD 3-Clause License. // // The full license is in the file LICENSE, distributed with this software. #ifndef MAMBA_UTIL_GRAPH_HPP #define MAMBA_UTIL_GRAPH_HPP #include #include #include #include #include #include #include #include "flat_set.hpp" namespace mamba::util { // Simplified implementation of a directed graph template class DiGraphBase { public: using node_t = Node; using node_id = std::size_t; using node_map = std::map; using node_id_list = flat_set; using adjacency_list = std::vector; node_id add_node(const node_t& value); node_id add_node(node_t&& value); bool add_edge(node_id from, node_id to); bool remove_edge(node_id from, node_id to); bool remove_node(node_id id); bool empty() const; std::size_t number_of_nodes() const noexcept; std::size_t number_of_edges() const noexcept; std::size_t in_degree(node_id id) const noexcept; std::size_t out_degree(node_id id) const noexcept; const node_map& nodes() const; const node_t& node(node_id id) const; node_t& node(node_id id); const node_id_list& successors(node_id id) const; const adjacency_list& successors() const; const node_id_list& predecessors(node_id id) const; const adjacency_list& predecessors() const; bool has_node(node_id id) const; bool has_edge(node_id from, node_id to) const; // TODO C++20 better to return a range since this search cannot be interupted from the // visitor template UnaryFunc for_each_node_id(UnaryFunc func) const; template BinaryFunc for_each_edge_id(BinaryFunc func) const; template UnaryFunc for_each_leaf_id(UnaryFunc func) const; template UnaryFunc for_each_leaf_id_from(node_id source, UnaryFunc func) const; template UnaryFunc for_each_root_id(UnaryFunc func) const; template UnaryFunc for_each_root_id_from(node_id source, UnaryFunc func) const; protected: using derived_t = Derived; DiGraphBase() = default; DiGraphBase(const DiGraphBase&) = default; DiGraphBase(DiGraphBase&&) = default; DiGraphBase& operator=(const DiGraphBase&) = default; DiGraphBase& operator=(DiGraphBase&&) = default; ~DiGraphBase() = default; node_id number_of_node_id() const noexcept; Derived& derived_cast(); const Derived& derived_cast() const; private: template node_id add_node_impl(V&& value); // Source of truth for exsising nodes node_map m_node_map; // May contains empty slots after `remove_node` adjacency_list m_predecessors; // May contains empty slots after `remove_node` adjacency_list m_successors; std::size_t m_number_of_edges = 0; }; // TODO C++20 better to return a range since this search cannot be interupted from the // visitor // TODO should let user implement reverse with a reverse view when available template void dfs_raw(const Graph& graph, Visitor&& visitor, typename Graph::node_id start, bool reverse = false); template void dfs_raw(const Graph& graph, Visitor&& visitor, bool reverse = false); template void dfs_preorder_nodes_for_each_id( const Graph& graph, UnaryFunc&& func, typename Graph::node_id start, bool reverse = false ); template void dfs_preorder_nodes_for_each_id(const Graph& graph, UnaryFunc&& func, bool reverse = false); template void dfs_postorder_nodes_for_each_id( const Graph& graph, UnaryFunc&& func, typename Graph::node_id start, bool reverse = false ); template void dfs_postorder_nodes_for_each_id(const Graph& graph, UnaryFunc&& func, bool reverse = false); // TODO C++20 rather than providing an empty visitor, use a concept to detect the presence // of member function. // @warning Inheriting publicly from this class risks calling into the empty overloaded // function. template class EmptyVisitor { public: using graph_t = Graph; using node_id = typename graph_t::node_id; void start_node(node_id, const graph_t&) { } void finish_node(node_id, const graph_t&) { } void start_edge(node_id, node_id, const graph_t&) { } void tree_edge(node_id, node_id, const graph_t&) { } void back_edge(node_id, node_id, const graph_t&) { } void forward_or_cross_edge(node_id, node_id, const graph_t&) { } void finish_edge(node_id, node_id, const graph_t&) { } }; template auto is_reachable(const Graph& graph, typename Graph::node_id source, typename Graph::node_id target) -> bool; template void topological_sort_for_each_node_id(const Graph& graph, UnaryFunc&& func); template class DiGraph : private DiGraphBase> { public: using Base = DiGraphBase>; using typename Base::adjacency_list; using typename Base::node_id; using typename Base::node_id_list; using typename Base::node_map; using typename Base::node_t; using edge_t = Edge; using edge_id = std::pair; using edge_map = std::map; using Base::empty; using Base::has_edge; using Base::has_node; using Base::in_degree; using Base::node; using Base::nodes; using Base::number_of_edges; using Base::number_of_nodes; using Base::out_degree; using Base::predecessors; using Base::successors; using Base::for_each_edge_id; using Base::for_each_leaf_id; using Base::for_each_leaf_id_from; using Base::for_each_node_id; using Base::for_each_root_id; using Base::for_each_root_id_from; using Base::add_node; bool add_edge(node_id from, node_id to, const edge_t& data); bool add_edge(node_id from, node_id to, edge_t&& data); bool remove_edge(node_id from, node_id to); bool remove_node(node_id id); const edge_map& edges() const; const edge_t& edge(node_id from, node_id to) const; const edge_t& edge(edge_id edge) const; edge_t& edge(node_id from, node_id to); edge_t& edge(edge_id edge); private: friend class DiGraphBase>; // required for private CRTP template bool add_edge_impl(node_id from, node_id to, T&& data); edge_map m_edges; }; template class DiGraph : public DiGraphBase> { }; /******************************** * DiGraphBase Implementation * ********************************/ template bool DiGraphBase::empty() const { return number_of_nodes() == 0; } template auto DiGraphBase::number_of_nodes() const noexcept -> std::size_t { return m_node_map.size(); } template auto DiGraphBase::number_of_edges() const noexcept -> std::size_t { return m_number_of_edges; } template auto DiGraphBase::in_degree(node_id id) const noexcept -> std::size_t { return m_predecessors[id].size(); } template auto DiGraphBase::out_degree(node_id id) const noexcept -> std::size_t { return m_successors[id].size(); } template auto DiGraphBase::nodes() const -> const node_map& { return m_node_map; } template auto DiGraphBase::node(node_id id) const -> const node_t& { return m_node_map.at(id); } template auto DiGraphBase::node(node_id id) -> node_t& { return m_node_map.at(id); } template auto DiGraphBase::successors(node_id id) const -> const node_id_list& { return m_successors[id]; } template auto DiGraphBase::successors() const -> const adjacency_list& { return m_successors; } template auto DiGraphBase::predecessors(node_id id) const -> const node_id_list& { return m_predecessors[id]; } template auto DiGraphBase::predecessors() const -> const adjacency_list& { return m_predecessors; } template auto DiGraphBase::has_node(node_id id) const -> bool { return nodes().count(id) > 0; } template auto DiGraphBase::has_edge(node_id from, node_id to) const -> bool { return has_node(from) && successors(from).contains(to); } template auto DiGraphBase::add_node(const node_t& value) -> node_id { return add_node_impl(value); } template auto DiGraphBase::add_node(node_t&& value) -> node_id { return add_node_impl(std::move(value)); } template template auto DiGraphBase::add_node_impl(V&& value) -> node_id { const node_id id = number_of_node_id(); m_node_map.emplace(id, std::forward(value)); m_successors.push_back(node_id_list()); m_predecessors.push_back(node_id_list()); return id; } template bool DiGraphBase::remove_node(node_id id) { if (!has_node(id)) { return false; } const auto succs = successors(id); // Cannot iterate on object being modified for (const auto& to : succs) { remove_edge(id, to); } const auto preds = predecessors(id); // Cannot iterate on object being modified for (const auto& from : preds) { remove_edge(from, id); } m_node_map.erase(id); return true; } template bool DiGraphBase::add_edge(node_id from, node_id to) { if (has_edge(from, to)) { return false; } m_successors[from].insert(to); m_predecessors[to].insert(from); ++m_number_of_edges; return true; } template bool DiGraphBase::remove_edge(node_id from, node_id to) { if (!has_edge(from, to)) { return false; } m_successors[from].erase(to); m_predecessors[to].erase(from); --m_number_of_edges; return true; } template template UnaryFunc DiGraphBase::for_each_node_id(UnaryFunc func) const { for (const auto& [i, _] : m_node_map) { func(i); } return func; } template template BinaryFunc DiGraphBase::for_each_edge_id(BinaryFunc func) const { for_each_node_id( [&](node_id i) { for (node_id j : successors(i)) { func(i, j); } } ); return func; } template template UnaryFunc DiGraphBase::for_each_leaf_id(UnaryFunc func) const { for_each_node_id( [&](node_id i) { if (out_degree(i) == 0) { func(i); } } ); return func; } template template UnaryFunc DiGraphBase::for_each_root_id(UnaryFunc func) const { for_each_node_id( [&](node_id i) { if (in_degree(i) == 0) { func(i); } } ); return func; } template template UnaryFunc DiGraphBase::for_each_leaf_id_from(node_id source, UnaryFunc func) const { // Explore the directed graph starting with the given source node. // When we explore a node with no outgoing edge, we know it is a leaf that is also a // descendent of source. // The pre or post order used in the node has no importance. dfs_preorder_nodes_for_each_id( derived_cast(), [&](node_id n) { if (out_degree(n) == 0) { func(n); } }, source ); return func; } template template UnaryFunc DiGraphBase::for_each_root_id_from(node_id source, UnaryFunc func) const { // Explore in reverse (going in the opposite direction of the edges the directed graph // starting with the given source node. // When we explore a node with no incoming edge, we know it is a root that is also an // ascendent of source. // The pre or post order used in the node has no importance. dfs_preorder_nodes_for_each_id( derived_cast(), [&](node_id n) { if (in_degree(n) == 0) { func(n); } }, source, /* reverse= */ true ); return func; } template auto DiGraphBase::number_of_node_id() const noexcept -> node_id { // Not number_of_nodes because due to remove nodes it may be larger return m_successors.size(); } template auto DiGraphBase::derived_cast() -> derived_t& { return static_cast(*this); } template auto DiGraphBase::derived_cast() const -> const derived_t& { return static_cast(*this); } /******************************* * Algorithms implementation * *******************************/ namespace detail { enum struct Visited { yes, ongoing, no }; template void dfs_raw_impl( const Graph& graph, Visitor&& visitor, typename Graph::node_id start, std::vector& status, const typename Graph::adjacency_list& adjacency ) { assert(status.size() == graph.successors().size()); assert(adjacency.size() == graph.successors().size()); assert(start < status.size()); status[start] = Visited::ongoing; visitor.start_node(start, graph); for (auto child : adjacency[start]) { visitor.start_edge(start, child, graph); if (status[child] == Visited::no) { visitor.tree_edge(start, child, graph); dfs_raw_impl(graph, visitor, child, status, adjacency); } else if (status[child] == Visited::ongoing) { visitor.back_edge(start, child, graph); } else { visitor.forward_or_cross_edge(start, child, graph); } visitor.finish_edge(start, child, graph); } status[start] = Visited::yes; visitor.finish_node(start, graph); } } template void dfs_raw(const Graph& graph, Visitor&& visitor, typename Graph::node_id start, bool reverse) { if (!graph.empty()) { auto& adjacency = reverse ? graph.predecessors() : graph.successors(); auto status = std::vector(adjacency.size(), detail::Visited::no); detail::dfs_raw_impl(graph, std::forward(visitor), start, status, adjacency); } } template void dfs_raw(const Graph& graph, Visitor&& visitor, bool reverse) { if (graph.empty()) { return; } using node_id = typename Graph::node_id; auto& adjacency = reverse ? graph.predecessors() : graph.successors(); const auto max_node_id = adjacency.size(); auto status = std::vector(max_node_id, detail::Visited::no); // Iterating over all ids, which may be a super set of the valid ids since some ids // could have been removed. // Hence we need to check ``graph.has_node``. for (node_id n = 0; n < max_node_id; ++n) { if (graph.has_node(n) && (status[n] == detail::Visited::no)) { detail::dfs_raw_impl(graph, std::forward(visitor), n, status, adjacency); } } } namespace detail { template class PreorderVisitor : public EmptyVisitor { public: using node_id = typename Graph::node_id; template PreorderVisitor(UnaryFuncU&& func) : m_func{ std::forward(func) } { } void start_node(node_id n, const Graph&) { m_func(n); } private: UnaryFunc m_func; }; template class PostorderVisitor : public EmptyVisitor { public: using node_id = typename Graph::node_id; template PostorderVisitor(UnaryFuncU&& func) : m_func{ std::forward(func) } { } void finish_node(node_id n, const Graph&) { m_func(n); } private: UnaryFunc m_func; }; } template void dfs_preorder_nodes_for_each_id( const Graph& graph, UnaryFunc&& func, typename Graph::node_id start, bool reverse ) { dfs_raw( graph, detail::PreorderVisitor(std::forward(func)), start, reverse ); } template void dfs_preorder_nodes_for_each_id(const Graph& graph, UnaryFunc&& func, bool reverse) { dfs_raw(graph, detail::PreorderVisitor(std::forward(func)), reverse); } template void dfs_postorder_nodes_for_each_id( const Graph& graph, UnaryFunc&& func, typename Graph::node_id start, bool reverse ) { dfs_raw( graph, detail::PostorderVisitor(std::forward(func)), start, reverse ); } template void dfs_postorder_nodes_for_each_id(const Graph& graph, UnaryFunc&& func, bool reverse) { dfs_raw(graph, detail::PostorderVisitor(std::forward(func)), reverse); } template auto is_reachable(const Graph& graph, typename Graph::node_id source, typename Graph::node_id target) -> bool { struct : EmptyVisitor { using node_id = typename Graph::node_id; node_id target; bool target_visited = false; void start_node(node_id node, const Graph&) { target_visited = target_visited || (node == target); } } visitor{ {}, target }; dfs_raw(graph, visitor, source); return visitor.target_visited; } template void topological_sort_for_each_node_id(const Graph& graph, UnaryFunc&& func) { dfs_postorder_nodes_for_each_id(graph, func, /* reverse= */ true); } /********************************* * DiGraph Edge Implementation * *********************************/ template bool DiGraph::add_edge(node_id from, node_id to, const edge_t& data) { return add_edge_impl(from, to, data); } template bool DiGraph::add_edge(node_id from, node_id to, edge_t&& data) { return add_edge_impl(from, to, std::move(data)); } template template bool DiGraph::add_edge_impl(node_id from, node_id to, T&& data) { if (const bool added = Base::add_edge(from, to); added) { auto l_edge_id = std::pair(from, to); m_edges.insert(std::pair(l_edge_id, std::forward(data))); return true; } return false; } template bool DiGraph::remove_edge(node_id from, node_id to) { m_edges.erase({ from, to }); // No-op if edge does not exists return Base::remove_edge(from, to); } template bool DiGraph::remove_node(node_id id) { // No-op if edge does not exists for (const auto& to : successors(id)) { m_edges.erase({ id, to }); } for (const auto& from : predecessors(id)) { m_edges.erase({ from, id }); } return Base::remove_node(id); } template auto DiGraph::edges() const -> const edge_map& { return m_edges; } template auto DiGraph::edge(edge_id edge) const -> const edge_t& { return m_edges.at(edge); } template auto DiGraph::edge(node_id from, node_id to) const -> const edge_t& { return edge({ from, to }); } template auto DiGraph::edge(edge_id edge) -> edge_t& { return m_edges[edge]; } template auto DiGraph::edge(node_id from, node_id to) -> edge_t& { return edge({ from, to }); } } #endif