diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c358886..54b4e5da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ if (CPP_GL_IS_TOP_LEVEL_PROJECT AND BUILD_TESTS) add_subdirectory(tests) endif() -# Include test directory if cpp-gl is a top level project +# Include benchmarks directory if cpp-gl is a top level project if (CPP_GL_IS_TOP_LEVEL_PROJECT AND BUILD_BENCHMARKS) add_subdirectory(benchmarks) endif() diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index fb4c969e..1d5a1909 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -87,11 +87,11 @@ void bm_hgl_backward_bfs(benchmark::State& state) { /// @brief Executes a Backward BFS equivalent on an Incidence Graph. template bool incidence_backward_bfs( - const IncidenceGraph& ig, - const std::vector& roots, - const typename IncidenceGraph::id_type original_n_vertices + IncidenceGraph&& ig, + const std::vector>& roots, + const gl::size_type original_n_vertices ) { - using id_type = typename IncidenceGraph::id_type; + using id_type = gl::id_t; std::vector visited_v(original_n_vertices, false); auto tail_unvisited = diff --git a/docs/hgl/architecture.md b/docs/hgl/architecture.md index 5295cba1..f057bc84 100644 --- a/docs/hgl/architecture.md +++ b/docs/hgl/architecture.md @@ -281,10 +281,10 @@ The complexities of topological queries is identical for standard (`list_t`) and | Query Operation | Bidirectional | Vertex-Major | Hyperedge-Major | | :--- | :--- | :--- | :--- | | **Check Incidence** $(v, e)$ | $O(\log(\min(deg(v), \vert e \vert)))$ | $O(\log(deg(v)))$ | $O(\log(\vert e \vert))$ | -| **Iterate Incident Hyperedges** of $v$ | $O(deg(v))$ | $O(deg(v))$ | $O(\vert V \vert + I)$ (Scan All) | -| **Iterate Incident Vertices** of $e$ | $O(\vert e \vert)$ | $O(\vert E \vert + I)$ (Scan All) | $O(\vert e \vert)$ | -| **Get Degree** of $v$ | $O(1)$ | $O(1)$ | $O(\vert V \vert + I)$ | -| **Get Size** of $e$ | $O(1)$ | $O(\vert E \vert + I)$ | $O(1)$ | +| **Iterate Incident Hyperedges** of $v$ | $O(deg(v))$ | $O(deg(v))$ | $O(\vert E \vert + I)$ (Scan All) | +| **Iterate Incident Vertices** of $e$ | $O(\vert e \vert)$ | $O(\vert V \vert + I)$ (Scan All) | $O(\vert e \vert)$ | +| **Get Degree** of $v$ | $O(1)$ | $O(1)$ | $O(\vert E \vert + I)$ | +| **Get Size** of $e$ | $O(1)$ | $O(\vert V \vert + I)$ | $O(1)$ | **Structural Mutations (Standard Incidence List, `list_t`):** @@ -328,8 +328,8 @@ The complexities of topological queries is identical for standard (`matrix_t`) a | Mutation Operation | `matrix_t` (Standard) | `flat_matrix_t` (Flat) | | :--- | :--- | :--- | -| **Add Vertex** | $O(\vert E \vert)$ amortized if `vertex_major_t`
$O(\vert V \vert \cdot \vert E \vert)$ if `hyperedge_major_t` | $O(\vert V \vert \cdot \vert E \vert)$ | -| **Add Hyperedge** | $O(\vert V \vert \cdot \vert E \vert)$ if `vertex_major_t`
$O(\vert V \vert)$ amortized if `hyperedge_major_t` | $O(\vert V \vert \cdot \vert E \vert)$ | +| **Add Vertex** | $O(\vert E \vert)$ amortized | $O(\vert V \vert \cdot \vert E \vert)$ | +| **Add Hyperedge** | $O(\vert V \vert)$ amortized | $O(\vert V \vert \cdot \vert E \vert)$ | | **Remove Vertex / Hyperedge** | $O(\vert V \vert \cdot \vert E \vert)$ (Shift rows/cols) | $O(\vert V \vert \cdot \vert E \vert)$ | | **Add / Remove Incidence** (Bind/Unbind) | $O(1)$ | $O(1)$ | diff --git a/include/gl/algorithm/core.hpp b/include/gl/algorithm/core.hpp index 731505fc..8fc434ec 100644 --- a/include/gl/algorithm/core.hpp +++ b/include/gl/algorithm/core.hpp @@ -122,15 +122,15 @@ using non_void_result_type = /// @ingroup GL-Algorithm /// @brief Maps a vertex ID to its predecessor's ID in a traversal tree. /// @tparam GraphType The type of the graph being traversed. -template -using predecessors_map = std::vector; +template +using predecessors_map = std::vector>; /// @ingroup GL-Algorithm /// @brief Represents an active node in a search container (e.g., a BFS queue or DFS stack). /// @tparam GraphType The type of the graph being searched. -template +template struct search_node { - using id_type = typename GraphType::id_type; + using id_type = id_t; /// @brief Constructs a search node acting as a root (predecessor is itself). /// @param vertex_id The ID of the vertex. diff --git a/include/gl/algorithm/pathfinding/dijkstra.hpp b/include/gl/algorithm/pathfinding/dijkstra.hpp index f811aacb..7917ca0d 100644 --- a/include/gl/algorithm/pathfinding/dijkstra.hpp +++ b/include/gl/algorithm/pathfinding/dijkstra.hpp @@ -25,7 +25,7 @@ namespace gl::algorithm { /// | VertexDistanceType | The numeric type used to represent accumulated path weights/distances. | Must satisfy the [**c_arithmetic**](gl_concepts.md#gl-traits-c-arithmetic) concept. | template struct paths_descriptor { - using id_type = typename G::id_type; + using id_type = id_t; using distance_type = VertexDistanceType; /// @brief Constructs a descriptor sized for the given number of vertices. @@ -43,7 +43,7 @@ struct paths_descriptor { /// @brief An alias for @ref gl::algorithm::paths_descriptor "paths_descriptor" that automatically deduces the appropriate distance type for the graph. /// @tparam G The type of the graph. template -using paths_descriptor_type = paths_descriptor>; +using paths_descriptor_type = paths_descriptor>; /// @ingroup GL-Algorithm /// @brief Factory function to create an initialized paths descriptor sized for the given graph. @@ -66,11 +66,11 @@ template template struct dijkstra_search_node { /// @brief The type of the vertex ID. - using id_type = typename G::id_type; + using id_type = id_t; id_type vertex_id; ///< @brief The ID of the vertex represented by this node. id_type pred_id; ///< The ID of the predecessor vertex used to reach this node. - vertex_distance_type + vertex_distance_t distance; ///< The accumulated distance from the source to this vertex at the time of enqueueing. }; @@ -126,17 +126,14 @@ struct dijkstra_search_node { /// @hideparams template < traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> [[nodiscard]] paths_descriptor_type dijkstra_shortest_paths( - const G& graph, - typename G::id_type source_id, - PreVisitCallback pre_visit = {}, - PostVisitCallback post_visit = {} + G&& graph, id_t source_id, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - using id_type = typename G::id_type; - using edge_type = typename G::edge_type; - using distance_type = vertex_distance_type; + using id_type = id_t; + using edge_type = edge_t; + using distance_type = vertex_distance_t; auto paths = make_paths_descriptor(graph); diff --git a/include/gl/algorithm/spanning_tree/prim_mst.hpp b/include/gl/algorithm/spanning_tree/prim_mst.hpp index c234c98a..4dcd0d25 100644 --- a/include/gl/algorithm/spanning_tree/prim_mst.hpp +++ b/include/gl/algorithm/spanning_tree/prim_mst.hpp @@ -22,11 +22,11 @@ namespace gl::algorithm { template struct mst_descriptor { /// @brief The type of the graph. - using graph_type = G; + using graph_type = graph_val_t; /// @brief The type of the edges stored in the graph. - using edge_type = typename graph_type::edge_type; + using edge_type = edge_t; /// @brief The numeric type used to represent accumulated tree weights. - using weight_type = vertex_distance_type; + using weight_type = vertex_distance_t; /// @brief Constructs a descriptor sized to hold the resulting tree edges. /// @param n_vertices The total number of vertices in the graph. @@ -77,9 +77,9 @@ struct mst_descriptor { /// - @ref gl::algorithm::vertex_heap_prim_mst "vertex_heap_prim_mst" For the vertex-heap variant of the Prim's MST finding algorithm. /// @hideparams template -[[nodiscard]] mst_descriptor edge_heap_prim_mst(const G& graph, typename G::id_type root_id) { +[[nodiscard]] mst_descriptor edge_heap_prim_mst(G&& graph, id_t root_id) { // type definitions - using edge_type = typename G::edge_type; + using edge_type = edge_t; struct edge_comparator { [[nodiscard]] gl_attr_force_inline bool operator()( @@ -160,7 +160,7 @@ template /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | -/// | G | The type of the undirected graph being traversed. | Must satisfy the [**c_undirected_graph**](gl_concepts.md#gl-traits-c-undirected-graph) concept and its @ref gl::vertex_distance_type "distance type" must satisfy [**c_has_numeric_limits_max**](gl_concepts.md#gl-traits-c-has-numeric-limits-max). +/// | G | The type of the undirected graph being traversed. | Must satisfy the [**c_undirected_graph**](gl_concepts.md#gl-traits-c-undirected-graph) concept and its @ref gl::vertex_distance_t "distance type" must satisfy [**c_has_numeric_limits_max**](gl_concepts.md#gl-traits-c-has-numeric-limits-max). /// /// @param graph The undirected graph to evaluate. /// @param root_id The starting vertex ID for the MST calculation. Defaults to the graph's `initial_id` if `invalid_id` is passed. @@ -169,12 +169,12 @@ template /// - @ref gl::algorithm::edge_heap_prim_mst "edge_heap_prim_mst" For the vertex-heap variant of the Prim's MST finding algorithm. /// @hideparams template -requires(traits::c_has_numeric_limits_max>) -[[nodiscard]] mst_descriptor vertex_heap_prim_mst(const G& graph, typename G::id_type root_id) { +requires(traits::c_has_numeric_limits_max>) +[[nodiscard]] mst_descriptor vertex_heap_prim_mst(G&& graph, id_t root_id) { // type definitions - using id_type = typename G::id_type; - using edge_type = typename G::edge_type; - using distance_type = vertex_distance_type; + using id_type = id_t; + using edge_type = edge_t; + using distance_type = vertex_distance_t; // Prepare the necessary utility const auto n_vertices = graph.n_vertices(); diff --git a/include/gl/algorithm/templates/bfs.hpp b/include/gl/algorithm/templates/bfs.hpp index 9e91c4fb..61a79be1 100644 --- a/include/gl/algorithm/templates/bfs.hpp +++ b/include/gl/algorithm/templates/bfs.hpp @@ -75,15 +75,13 @@ namespace gl::algorithm { template < traits::c_graph G, traits::c_forward_range_of> InitQueueRangeType = std::vector>, - traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate VisitCallback = - empty_callback, - traits::c_decision_predicate - EnqueueNodePred = empty_callback, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_predicate> VisitVertexPredicate = empty_callback, + traits::c_optional_predicate, id_t> VisitCallback = empty_callback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> bool bfs( - const G& graph, + G&& graph, const InitQueueRangeType& initial_queue_content, VisitVertexPredicate visit_vertex_pred = {}, VisitCallback visit = {}, @@ -101,7 +99,7 @@ bool bfs( // search the graph while (not q.empty()) { - const search_node node = q.front(); + const auto node = q.front(); q.pop(); if constexpr (not traits::c_empty_callback) diff --git a/include/gl/algorithm/templates/dfs.hpp b/include/gl/algorithm/templates/dfs.hpp index 3a659157..12779f75 100644 --- a/include/gl/algorithm/templates/dfs.hpp +++ b/include/gl/algorithm/templates/dfs.hpp @@ -74,15 +74,13 @@ namespace gl::algorithm { template < traits::c_graph G, traits::c_forward_range_of> InitStackRangeType = std::vector>, - traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate VisitCallback = - empty_callback, - traits::c_decision_predicate - EnqueueNodePred = empty_callback, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_predicate> VisitVertexPredicate = empty_callback, + traits::c_optional_predicate, id_t> VisitCallback = empty_callback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> bool dfs( - const G& graph, + G&& graph, const InitStackRangeType& initial_stack_content, VisitVertexPredicate visit_vertex_pred = {}, VisitCallback visit = {}, @@ -187,15 +185,15 @@ bool dfs( /// @hideparams template < traits::c_graph G, - traits::c_optional_predicate VisitVertexPredicate, - traits::c_optional_predicate VisitCallback, - traits::c_decision_predicate EnqueueNodePred, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_predicate> VisitVertexPredicate, + traits::c_optional_predicate, id_t> VisitCallback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred, + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> void r_dfs( - const G& graph, - const typename G::id_type vertex_id, - const typename G::id_type pred_id, + G&& graph, + const id_t vertex_id, + const id_t pred_id, VisitVertexPredicate visit_vertex_pred, VisitCallback visit, EnqueueNodePred enqueue_node_pred, diff --git a/include/gl/algorithm/templates/pfs.hpp b/include/gl/algorithm/templates/pfs.hpp index b44b5956..8ab3ad76 100644 --- a/include/gl/algorithm/templates/pfs.hpp +++ b/include/gl/algorithm/templates/pfs.hpp @@ -90,20 +90,15 @@ template < typename InitQueueRangeType = std::vector>, typename NodeType = std::ranges::range_value_t, traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate VisitCallback = + traits::c_optional_predicate, id_t> VisitCallback = empty_callback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_callback, id_t, const edge_t&> MakeNodeCallback = empty_callback, - traits::c_decision_predicate - EnqueueNodePred = empty_callback, - traits::c_optional_callback< - NodeType, - typename G::id_type, - typename G::id_type, - const typename G::edge_type&> MakeNodeCallback = empty_callback, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> requires traits::c_predicate bool pfs( - const G& graph, + G&& graph, const PQCmp& pq_cmp, const InitQueueRangeType& initial_queue_content, VisitVertexPredicate visit_vertex_pred = {}, @@ -152,7 +147,7 @@ bool pfs( } else { static_assert( - std::constructible_from, + std::constructible_from, id_t>, "[gl::algorithm::pfs] Custom NodeType provided without a MakeNodeCallback. " "The NodeType must be constructible from (target_id, pred_id), or you must " "provide a MakeNodeCallback!" diff --git a/include/gl/algorithm/topology/coloring.hpp b/include/gl/algorithm/topology/coloring.hpp index 644c5592..bc8f5af7 100644 --- a/include/gl/algorithm/topology/coloring.hpp +++ b/include/gl/algorithm/topology/coloring.hpp @@ -62,13 +62,11 @@ using bicoloring_type = std::vector; /// @hideparams template < traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> [[nodiscard]] std::optional bipartite_coloring( - const G& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} + G&& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - using edge_type = typename G::edge_type; - bicoloring_type coloring(graph.n_vertices(), binary_color::value::unset); for (const auto root_id : graph.vertex_ids()) { if (coloring[root_id].is_set()) @@ -82,7 +80,7 @@ template < init_node_range(root_id), empty_callback{}, // visit predicate empty_callback{}, // visit callback - [&coloring](typename G::id_type vertex_id, const edge_type& in_edge) + [&coloring](id_t vertex_id, const edge_t& in_edge) -> decision { // enqueue predicate if (in_edge.is_loop()) return decision::abort; // graph is not bipartite @@ -119,7 +117,7 @@ template < /// @return `true` if the graph is bipartite (2-colorable), `false` otherwise. /// ### See Also /// - @ref gl::algorithm::apply_coloring "apply_coloring" -[[nodiscard]] gl_attr_force_inline bool is_bipartite(const traits::c_graph auto& graph) { +[[nodiscard]] gl_attr_force_inline bool is_bipartite(traits::c_graph auto&& graph) { return bipartite_coloring(graph).has_value(); } @@ -129,7 +127,7 @@ template < /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | -/// | G | The type of the graph to modify. Must have compatible color properties. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept, and its properties must satisfy [**c_binary_color_properties_type**](gl_concepts.md#gl-traits-c-binary-color-properties-type). | +/// | G | The type of the graph to modify. Must have compatible color properties. | Must satisfy the [**c_mut_graph**](gl_concepts.md#gl-traits-c-mut-graph) concept, and its properties must satisfy [**c_binary_color_properties_type**](gl_concepts.md#gl-traits-c-binary-color-properties-type). | /// | ColorRange | The type of the range containing the computed colors. | Must satisfy the [**c_sized_range_of**](gl_concepts.md#gl-traits-c-sized-range-of) concept for `binary_color`. | /// /// @param graph The mutable graph instance whose properties will be updated. @@ -138,8 +136,8 @@ template < /// ### See Also /// - @ref gl::algorithm::bipartite_coloring "bipartite_coloring" /// - @ref gl::algorithm::is_bipartite "is_bipartite" -template ColorRange> -requires(traits::c_binary_color_properties_type) +template ColorRange> +requires(traits::c_binary_color_properties_type>) bool apply_coloring(G& graph, const ColorRange& color_range) { if (std::ranges::size(color_range) != graph.n_vertices()) return false; diff --git a/include/gl/algorithm/topology/topological_sort.hpp b/include/gl/algorithm/topology/topological_sort.hpp index 1134631d..554f8a73 100644 --- a/include/gl/algorithm/topology/topological_sort.hpp +++ b/include/gl/algorithm/topology/topological_sort.hpp @@ -61,13 +61,13 @@ namespace gl::algorithm { /// @hideparams template < traits::c_directed_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> -[[nodiscard]] std::optional> topological_sort( - const G& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> +[[nodiscard]] std::optional>> topological_sort( + G&& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - using id_type = typename G::id_type; - using edge_type = typename G::edge_type; + using id_type = id_t; + using edge_type = edge_t; // prepare the vertex in degree map std::vector in_degree_map = graph.in_degree_map(); diff --git a/include/gl/algorithm/traversal/breadth_first_search.hpp b/include/gl/algorithm/traversal/breadth_first_search.hpp index 6ab6fc2d..2e30d72b 100644 --- a/include/gl/algorithm/traversal/breadth_first_search.hpp +++ b/include/gl/algorithm/traversal/breadth_first_search.hpp @@ -70,16 +70,16 @@ namespace gl::algorithm { template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> result_type> breadth_first_search( - const G& graph, - const typename G::id_type root_vertex_id = no_root, + G&& graph, + const id_t root_vertex_id = no_root, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { std::vector visited(graph.n_vertices(), false); - std::vector sources(graph.n_vertices()); + std::vector> sources(graph.n_vertices()); auto pred_map = init_predecessors_map(graph); diff --git a/include/gl/algorithm/traversal/depth_first_search.hpp b/include/gl/algorithm/traversal/depth_first_search.hpp index cd05c937..d181b725 100644 --- a/include/gl/algorithm/traversal/depth_first_search.hpp +++ b/include/gl/algorithm/traversal/depth_first_search.hpp @@ -78,16 +78,16 @@ namespace gl::algorithm { template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> result_type> depth_first_search( - const G& graph, - const typename G::id_type root_vertex_id = no_root, + G&& graph, + const id_t root_vertex_id = no_root, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { std::vector visited(graph.n_vertices(), false); - std::vector sources(graph.n_vertices()); + std::vector> sources(graph.n_vertices()); auto pred_map = init_predecessors_map(graph); @@ -178,16 +178,16 @@ result_type> depth_first_search( template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> result_type> recursive_depth_first_search( - const G& graph, - const typename G::id_type root_vertex_id = no_root, + G&& graph, + const id_t root_vertex_id = no_root, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { std::vector visited(graph.n_vertices(), false); - std::vector sources(graph.n_vertices()); + std::vector> sources(graph.n_vertices()); auto pred_map = init_predecessors_map(graph); diff --git a/include/gl/algorithm/util.hpp b/include/gl/algorithm/util.hpp index b7bfde13..76c7b417 100644 --- a/include/gl/algorithm/util.hpp +++ b/include/gl/algorithm/util.hpp @@ -53,8 +53,7 @@ template template < traits::c_graph G, traits::c_forward_range_of> InitRangeType = std::vector>> -[[nodiscard]] gl_attr_force_inline InitRangeType init_node_range(typename G::id_type root_vertex_id -) { +[[nodiscard]] gl_attr_force_inline InitRangeType init_node_range(id_t root_vertex_id) { return InitRangeType{search_node{root_vertex_id}}; } @@ -78,7 +77,7 @@ template [[nodiscard]] gl_attr_force_inline auto default_visit_callback( std::vector& visited, non_void_result_type>& pred_map ) { - using id_type = typename G::id_type; + using id_type = id_t; return [&](id_type vertex_id, id_type pred_id) { const auto vertex_idx = to_idx(vertex_id); visited[vertex_idx] = true; @@ -97,7 +96,7 @@ template template [[nodiscard]] gl_attr_force_inline auto default_enqueue_node_predicate(std::vector& visited) { using return_t = std::conditional_t; - return [&](typename G::id_type vertex_id, const typename G::edge_type&) -> return_t { + return [&](id_t vertex_id, const edge_t&) -> return_t { return not visited[to_idx(vertex_id)]; }; } diff --git a/include/gl/conversion.hpp b/include/gl/conversion.hpp index c27fdeb8..0caa5c6a 100644 --- a/include/gl/conversion.hpp +++ b/include/gl/conversion.hpp @@ -169,7 +169,7 @@ struct to_impl { /// | Parameter | Description | Constraints | /// | :------------ | :---------- | :---------- | /// | TargetImplTag | The representation tag of the desired target representation (e.g., `gl::repr::flat_list_t`) | [**c_graph_repr_tag**](gl_concepts.md#gl-traits-c-graph-repr-tag) | -/// | Graph | The type of the source graph, which will be automatically deduced from the function argument. | [**c_graph**](gl_concepts.md#gl-traits-c-graph) | +/// | Graph | The type of the source graph, which will be automatically deduced from the function argument. | [**c_graph**](gl_concepts.md#gl-traits-c-graph) and must **NOT** be an Lvalue reference | /// /// @param source The graph to convert. After the operation it will be left in a valid, empty state. /// @return A new graph containing the moved data, structured according to `TargetImplTag`. @@ -177,6 +177,7 @@ struct to_impl { /// ### See Also /// - @ref gl::traits::swap_repr_tag "swap_repr_tag" : For the trait used to resolve the target graph type with the swapped representation tag. template +requires(not std::is_lvalue_reference_v) [[nodiscard]] auto to(Graph&& source) { using source_traits = typename Graph::traits_type; using source_impl_tag = typename source_traits::representation_tag; @@ -192,7 +193,7 @@ template target._n_edges = std::exchange(source._n_edges, 0uz); target._vertex_properties = std::move(source._vertex_properties); target._edge_properties = std::move(source._edge_properties); - source._impl = typename Graph::implementation_type(); + source._impl = typename Graph::implementation_type{}; return target; } diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index a567cf12..56fad0a4 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -105,6 +105,18 @@ class edge_descriptor final { requires(traits::c_non_empty_properties) : _id(id), _vertices(source, target), _properties(properties) {} + /// @brief Implicit converting constructor from a non-const descriptor to a const descriptor. + /// @tparam NonConstProps The non-const property type. + /// @param other The edge descriptor to convert from. + template + requires(std::same_as) + edge_descriptor(const edge_descriptor& other) noexcept + : _id(other.id()), _vertices(other._vertices) { + if constexpr (traits::c_non_empty_properties) { + this->_properties = other.properties(); + } + } + /// @brief Returns an invalid edge descriptor (for empty properties). /// @return An `edge_descriptor` holding `invalid_id` for edge and vertex IDs. [[nodiscard]] gl_attr_force_inline static edge_descriptor invalid() noexcept @@ -135,23 +147,29 @@ class edge_descriptor final { /// @brief Destructor. ~edge_descriptor() = default; - /// @brief Equality comparison operator for directed edges. + /// @brief Cross-type equality comparison operator for directed edges. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The edge descriptor to compare against. /// @return `true` if IDs and exact endpoint pairs match, `false` otherwise. - [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept - requires(traits::c_directed_edge) - { - return this->_id == other._id and (this->_vertices == other._vertices); + template + requires(traits::c_directed_edge and std::same_as, std::remove_cv_t>) + [[nodiscard]] bool operator==( + const edge_descriptor& other + ) const noexcept { + return this->_id == other.id() and (this->_vertices == other.incident_vertices()); } - /// @brief Equality comparison operator for undirected edges. + /// @brief Cross-type equality comparison operator for undirected edges. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The edge descriptor to compare against. /// @return `true` if IDs and endpoint pairs match (order independent), `false` otherwise. - [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept - requires(traits::c_undirected_edge) - { - return this->_id == other._id - and (this->_vertices == other._vertices + template + requires(traits::c_undirected_edge and std::same_as, std::remove_cv_t>) + [[nodiscard]] bool operator==( + const edge_descriptor& other + ) const noexcept { + return this->_id == other.id() + and (this->_vertices == other.incident_vertices() or (this->_vertices == other.incident_vertices_r())); } diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index d8830d7b..bee8a7ca 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -33,32 +33,91 @@ namespace traits { /// @ingroup GL-Traits /// @brief Concept checking if a type is an instantiation of the generic @ref "gl::graph" graph class. template -concept c_graph = c_instantiation_of; +concept c_graph = c_instantiation_of, graph>; + +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a constant type instantiation of the generic @ref "gl::graph" graph class. +template +concept c_const_graph = c_graph and std::is_const_v>; + +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable type instantiation of the generic @ref "gl::graph" graph class. +template +concept c_mut_graph = c_graph and not std::is_const_v>; + +} // namespace traits + +/// @ingroup GL-Core +/// @brief Extracts the underlying unqualified graph type by removing reference and cv-qualifiers. +template +using graph_val_t = std::remove_cvref_t; + +/// @ingroup GL-Core +/// @brief Resolves the identifier type associated with the given graph type. +template +using id_t = typename graph_val_t::id_type; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate vertex descriptor type (mutable or const) based on the graph's constness. +template +using vertex_t = std::conditional_t< + std::is_const_v>, + typename graph_val_t::const_vertex_type, + typename graph_val_t::vertex_type>; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate vertex properties type (mutable or const) based on the graph's constness. +template +using vertex_properties_t = std::conditional_t< + std::is_const_v>, + const typename graph_val_t::vertex_properties_type, + typename graph_val_t::vertex_properties_type>; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate edge descriptor type (mutable or const) based on the graph's constness. +template +using edge_t = std::conditional_t< + std::is_const_v>, + typename graph_val_t::const_edge_type, + typename graph_val_t::edge_type>; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate edge properties type (mutable or const) based on the graph's constness. +template +using edge_properties_t = std::conditional_t< + std::is_const_v>, + const typename graph_val_t::edge_properties_type, + typename graph_val_t::edge_properties_type>; + +namespace traits { /// @ingroup GL-Traits /// @brief Concept checking if a graph is directed. /// @see gl::directed_t "directed_t" : For the directional tag used to specify directed graph configuration. template -concept c_directed_graph = c_graph and c_directed_edge; +concept c_directed_graph = + c_graph and std::same_as::directional_tag, directed_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph is undirected. /// @see gl::undirected_t "undirected_t" : For the directional tag used to specify undirected graph configuration. template -concept c_undirected_graph = c_graph and c_undirected_edge; +concept c_undirected_graph = + c_graph and std::same_as::directional_tag, undirected_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the standard adjacency list representation. /// @see gl::repr::list_t "list_t" : For the representation tag used to specify the standard adjacency list representation. template -concept c_list_graph = c_graph and std::same_as; +concept c_list_graph = + c_graph and std::same_as::representation_tag, repr::list_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the flattened adjacency list representation. /// @see gl::repr::flat_list_t "flat_list_t" : For the representation tag used to specify the flattened adjacency list representation. template concept c_flat_list_graph = - c_graph and std::same_as; + c_graph and std::same_as::representation_tag, repr::flat_list_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes any list-based adjacency representation. @@ -73,14 +132,14 @@ concept c_adjacency_list_graph = c_list_graph or c_flat_list_graph; /// @see gl::repr::matrix_t "matrix_t" : For the representation tag used to specify the standard adjacency matrix representation. template concept c_matrix_graph = - c_graph and std::same_as; + c_graph and std::same_as::representation_tag, repr::matrix_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the flattened adjacency matrix representation. /// @see gl::repr::flat_matrix_t "flat_matrix_t" : For the representation tag used to specify the flattened adjacency matrix representation. template concept c_flat_matrix_graph = - c_graph and std::same_as; + c_graph and std::same_as::representation_tag, repr::flat_matrix_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes any matrix-based adjacency representation. @@ -90,12 +149,37 @@ concept c_flat_matrix_graph = template concept c_adjacency_matrix_graph = c_matrix_graph or c_flat_matrix_graph; +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable or immutable vertex descriptor associated with the given graph. +/// @tparam V The type of the vertex descriptor. +/// @tparam G The type of the graph. +template +concept c_graph_vertex = + c_graph + and c_one_of< + std::remove_cvref_t, + typename graph_val_t::vertex_type, + typename graph_val_t::const_vertex_type>; + +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable or immutable edge descriptor associated with the given graph. +/// @tparam E The type of the edge descriptor. +/// @tparam G The type of the graph. +template +concept c_graph_edge = + c_graph + and c_one_of< + std::remove_cvref_t, + typename graph_val_t::edge_type, + typename graph_val_t::const_edge_type>; + } // namespace traits template [[nodiscard]] Graph clone(const Graph& source); template +requires(not std::is_lvalue_reference_v) [[nodiscard]] auto to(Graph&& source); namespace detail { @@ -175,7 +259,7 @@ struct to_impl; /// ### API Design: IDs vs. Descriptors /// The `graph` class exposes a dual API to accommodate different performance and ergonomic needs: /// -/// - **Inputs**: Most query methods are overloaded to accept either a raw `id_type` or a `vertex_type`/`edge_type` descriptor. They are functionally identical. +/// - **Inputs**: Most query methods are overloaded to accept either a raw `id_type` or a vertex/edge descriptor types. They are functionally identical. /// - **Outputs**: Methods ending in `_ids` (e.g., `neighbor_ids`) return views of raw integral IDs. Methods without this suffix (e.g., `neighbors`) automatically map those IDs to the proper descriptor objects. /// - **Performance**: Descriptor-returning methods incur a slight overhead if the graph utilizes properties, as the property reference must be fetched and bound to each descriptor. If you only need topology, prefer the `_ids` variants. /// @@ -221,34 +305,38 @@ class graph final { /// @brief Type tag indicating the underlying representation model. using representation_tag = typename traits_type::representation_tag; - /// @brief The underlying representation type matching the directional tag. - using implementation_type = typename representation_tag::template type; - friend implementation_type; - /// @brief Integral type used to identify vertices and edges. using id_type = typename traits_type::id_type; - /// @brief The descriptor type representing a vertex. + /// @brief The descriptor type representing a vertex of the graph. using vertex_type = typename traits_type::vertex_type; + /// @brief The descriptor type representing an immutable vertex of the graph. + using const_vertex_type = typename traits_type::const_vertex_type; /// @brief Type representing the properties attached to a vertex. using vertex_properties_type = typename traits_type::vertex_properties_type; - /// @brief Type mapping vertex IDs to their respective properties. - using vertex_properties_map_type = std::conditional_t< - traits::c_empty_properties, - empty_properties_map, - std::vector>; - /// @brief The descriptor type representing an edge. + /// @brief The descriptor type representing an edge of the graph. using edge_type = typename traits_type::edge_type; + /// @brief The descriptor type representing an immutable edge of the graph. + using const_edge_type = typename traits_type::const_edge_type; /// @brief Type representing the properties attached to an edge. using edge_properties_type = typename traits_type::edge_properties_type; - /// @brief Type mapping edge IDs to their respective properties. +private: + using implementation_type = typename representation_tag::template type; + friend implementation_type; + + using vertex_properties_map_type = std::conditional_t< + traits::c_empty_properties, + empty_properties_map, + std::vector>; + using edge_properties_map_type = std::conditional_t< traits::c_empty_properties, empty_properties_map, std::vector>; +public: /// @brief Default constructor creates an empty graph. graph() = default; @@ -294,9 +382,9 @@ class graph final { const auto new_vertex_id = static_cast(this->_n_vertices++); if constexpr (traits::c_non_empty_properties) - return vertex_descriptor{new_vertex_id, this->_vertex_properties.emplace_back()}; + return vertex_type{new_vertex_id, this->_vertex_properties.emplace_back()}; else - return vertex_descriptor{new_vertex_id}; + return vertex_type{new_vertex_id}; } /// @brief Adds a new vertex with the given properties to the graph. @@ -307,7 +395,7 @@ class graph final { requires(traits::c_non_empty_properties) { this->_impl.add_vertex(); - return vertex_descriptor{ + return vertex_type{ static_cast(this->_n_vertices++), this->_vertex_properties.emplace_back(std::move(properties)) }; @@ -358,7 +446,7 @@ class graph final { /// @param vertex The descriptor of the vertex to remove. /// @throws std::invalid_argument If the vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() - gl_attr_force_inline void remove_vertex(vertex_type vertex) { + gl_attr_force_inline void remove_vertex(traits::c_graph_vertex auto vertex) { this->remove_vertex(vertex.id()); } @@ -367,25 +455,26 @@ class graph final { /// @throws std::invalid_argument If any vertex ID in the range is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() void remove_vertices(const traits::c_forward_range_of auto& vertex_id_rng) { - // TODO: optimize - // sorts the ids in a descending order and removes duplicate ids - std::set> vertex_id_set( - std::ranges::begin(vertex_id_rng), std::ranges::end(vertex_id_rng) - ); - if (not vertex_id_set.empty()) - this->_verify_vertex_id(*vertex_id_set.begin()); + auto vertex_ids = vertex_id_rng | std::ranges::to(); + // Sort in descending order and remove duplicates to prevent index shifting bugs during erasure + std::ranges::sort(vertex_ids, std::greater<>{}); + vertex_ids.erase(std::ranges::unique(vertex_ids).begin(), vertex_ids.end()); + + if (not vertex_ids.empty()) + this->_verify_vertex_id(vertex_ids.front()); - for (auto vertex_id : vertex_id_set) + for (auto vertex_id : vertex_ids) this->_remove_vertex_impl(vertex_id); } /// @brief Removes a range of vertices using their descriptors. + /// @tparam VertexRng A forward range type containing graph's vertex descriptors. /// @param vertex_rng A sized range containing the descriptors of vertices to remove. /// @throws std::invalid_argument If any vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() - gl_attr_force_inline void remove_vertices( - const traits::c_forward_range_of auto& vertex_rng - ) { + template + requires(traits::c_forward_range and traits::c_graph_vertex, graph>) + gl_attr_force_inline void remove_vertices(const VertexRng& vertex_rng) { this->remove_vertices( vertex_rng | std::views::transform([](const auto& v) { return v.id(); }) ); @@ -403,7 +492,8 @@ class graph final { /// @brief Checks if the given vertex descriptor is valid in the graph. /// @param vertex The vertex descriptor to check. /// @return `true` if it exists, `false` otherwise. - [[nodiscard]] gl_attr_force_inline bool has_vertex(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline bool has_vertex(traits::c_graph_vertex auto vertex + ) const { return this->has_vertex(vertex.id()); } @@ -411,17 +501,19 @@ class graph final { /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// @throws std::invalid_argument If the ID is invalid. - [[nodiscard]] vertex_type vertex(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - return this->vertex_unchecked(vertex_id); + template + [[nodiscard]] vertex_t vertex(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); + return self.vertex_unchecked(vertex_id); } /// @brief Returns a vertex descriptor bounds-checked by ID (alias for `vertex`). /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// @throws std::invalid_argument If the ID is invalid. - [[nodiscard]] gl_attr_force_inline vertex_type at(const id_type vertex_id) const { - return this->vertex(vertex_id); + template + [[nodiscard]] gl_attr_force_inline vertex_t at(this Self& self, const id_type vertex_id) { + return self.vertex(vertex_id); } /// @brief Returns a vertex descriptor without bounds checking. @@ -431,12 +523,14 @@ class graph final { /// > [!WARNING] Undefined Behavior /// > /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. - [[nodiscard]] gl_attr_force_inline vertex_type vertex_unchecked(const id_type vertex_id - ) const noexcept { + template + [[nodiscard]] gl_attr_force_inline vertex_t vertex_unchecked( + this Self& self, const id_type vertex_id + ) { if constexpr (traits::c_non_empty_properties) - return vertex_descriptor{vertex_id, this->_vertex_properties[vertex_id]}; + return vertex_t{vertex_id, self._vertex_properties[vertex_id]}; else - return vertex_descriptor{vertex_id}; + return vertex_t{vertex_id}; } /// @brief Returns a vertex descriptor without bounds checking (array access style). @@ -446,15 +540,17 @@ class graph final { /// > [!WARNING] Undefined Behavior /// > /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. - [[nodiscard]] gl_attr_force_inline vertex_type operator[](const id_type vertex_id - ) const noexcept { - return this->vertex_unchecked(vertex_id); + template + [[nodiscard]] gl_attr_force_inline vertex_t operator[]( + this Self& self, const id_type vertex_id + ) noexcept { + return self.vertex_unchecked(vertex_id); } /// @brief Returns a lazily evaluated view of all vertex descriptors in the graph. /// @return A view yielding descriptors for every vertex. - [[nodiscard]] gl_attr_force_inline auto vertices() const noexcept { - return this->vertex_ids() | std::views::transform(this->_create_vertex_descriptor()); + [[nodiscard]] gl_attr_force_inline auto vertices(this auto& self) noexcept { + return self.vertex_ids() | std::views::transform(self._create_vertex_descriptor()); } /// @brief Returns a lazily evaluated view of all active vertex IDs in the graph. @@ -463,14 +559,14 @@ class graph final { return std::views::iota(initial_id_v, this->_n_vertices); } - /// @brief Retrieves the neighbor vertex IDs for a specific vertex. + /// @brief Retrieves the neighbor vertex descriptors for a specific vertex. /// @copydetails detail::graph_doc_anchors::neighbors() /// @param vertex_id The ID of the source vertex. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbors(const id_type vertex_id) const { - return this->neighbor_ids(vertex_id) - | std::views::transform(this->_create_vertex_descriptor()); + [[nodiscard]] gl_attr_force_inline auto neighbors(this auto& self, const id_type vertex_id) { + return self.neighbor_ids(vertex_id) + | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the neighbor vertex descriptors for a specific vertex. @@ -478,8 +574,10 @@ class graph final { /// @param vertex The source vertex descriptor. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbors(vertex_type vertex) const { - return this->neighbors(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto neighbors( + this auto& self, traits::c_graph_vertex auto vertex + ) { + return self.neighbors(vertex.id()); } /// @brief Retrieves the neighbor vertex IDs for a specific vertex. @@ -497,18 +595,19 @@ class graph final { /// @param vertex The source vertex descriptor. /// @return A view of all adjacent vertex IDs. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbor_ids(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(traits::c_graph_vertex auto vertex + ) const { return this->neighbor_ids(vertex.id()); } - /// @brief Retrieves the predecessor vertex IDs for a vertex. + /// @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex. /// @copydetails detail::graph_doc_anchors::predecessors() /// @param vertex_id The ID of the target vertex. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessors(const id_type vertex_id) const { - return this->predecessor_ids(vertex_id) - | std::views::transform(this->_create_vertex_descriptor()); + [[nodiscard]] gl_attr_force_inline auto predecessors(this auto& self, const id_type vertex_id) { + return self.predecessor_ids(vertex_id) + | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex. @@ -516,8 +615,10 @@ class graph final { /// @param vertex The target vertex descriptor. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessors(vertex_type vertex) const { - return this->predecessors(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto predecessors( + this auto& self, traits::c_graph_vertex auto vertex + ) { + return self.predecessors(vertex.id()); } /// @brief Retrieves the predecessor vertex IDs for a vertex. @@ -535,18 +636,20 @@ class graph final { /// @param vertex The target vertex descriptor. /// @return A view of all predecessor vertex IDs. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessor_ids(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + traits::c_graph_vertex auto vertex + ) const { return this->predecessor_ids(vertex.id()); } - /// @brief Retrieves the successor vertex IDs for a vertex. + /// @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex. /// @copydetails detail::graph_doc_anchors::successors() /// @param vertex_id The ID of the source vertex. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto successors(const id_type vertex_id) const { - return this->successor_ids(vertex_id) - | std::views::transform(this->_create_vertex_descriptor()); + [[nodiscard]] gl_attr_force_inline auto successors(this auto& self, const id_type vertex_id) { + return self.successor_ids(vertex_id) + | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex. @@ -554,8 +657,10 @@ class graph final { /// @param vertex The source vertex descriptor. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto successors(vertex_type vertex) const { - return this->successors(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto successors( + this auto& self, traits::c_graph_vertex auto vertex + ) { + return self.successors(vertex.id()); } /// @brief Retrieves the successor vertex IDs for a vertex. @@ -573,7 +678,8 @@ class graph final { /// @param vertex The source vertex descriptor. /// @return A view of all successor vertex IDs. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto successor_ids(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto successor_ids(traits::c_graph_vertex auto vertex + ) const { return this->successor_ids(vertex.id()); } @@ -581,20 +687,22 @@ class graph final { /// @param id The ID of the vertex. /// @return A reference to the properties attached to the vertex. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline vertex_properties_type& vertex_properties(const id_type id - ) const + template + [[nodiscard]] gl_attr_force_inline vertex_properties_t& vertex_properties( + this Self& self, const id_type id + ) requires(traits::c_non_empty_properties) { - this->_verify_vertex_id(id); - return this->_vertex_properties[id]; + self._verify_vertex_id(id); + return self._vertex_properties[id]; } /// @brief Retrieves a random-access view over all vertex properties in the graph. /// @return A view mapping each active vertex index to its property. - [[nodiscard]] gl_attr_force_inline auto vertex_properties_map() const noexcept + [[nodiscard]] gl_attr_force_inline auto vertex_properties_map(this auto& self) noexcept requires(traits::c_non_empty_properties) { - return std::views::all(this->_vertex_properties); + return std::views::all(self._vertex_properties); } // --- degree getters --- @@ -614,7 +722,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return The total degree. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline size_type degree(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline size_type degree(traits::c_graph_vertex auto vertex + ) const { return this->degree(vertex.id()); } @@ -639,7 +748,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return The in-degree. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline size_type in_degree(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline size_type in_degree(traits::c_graph_vertex auto vertex + ) const { return this->in_degree(vertex.id()); } @@ -664,7 +774,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return The out-degree. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline size_type out_degree(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(traits::c_graph_vertex auto vertex) const { return this->out_degree(vertex.id()); } @@ -730,7 +841,7 @@ class graph final { /// @return A descriptor representing the newly created edge. /// @throws std::invalid_argument If either vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::add_edge_note() - gl_attr_force_inline edge_type add_edge(vertex_type source, vertex_type target) { + gl_attr_force_inline edge_type add_edge(traits::c_graph_vertex auto source, traits::c_graph_vertex auto target) { return this->add_edge(source.id(), target.id()); } @@ -742,7 +853,7 @@ class graph final { /// @throws std::invalid_argument If either vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::add_edge_note() gl_attr_force_inline edge_type add_edge_with( - vertex_type source, vertex_type target, const edge_properties_type& properties + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target, const edge_properties_type& properties ) requires(traits::c_non_empty_properties) { @@ -776,13 +887,16 @@ class graph final { } /// @brief Dispatches multiple edge insertions connecting one source to many targets. + /// @tparam TargetRng A sized range type containing graph's vertex descriptors. /// @param source The source vertex descriptor. /// @param target_rng A sized range of target vertex descriptors. /// @throws std::invalid_argument If any vertex ID is invalid. /// @copydetails detail::graph_doc_anchors::add_edge_note() - void add_edges_from( - vertex_type source, const traits::c_sized_range_of auto& target_rng - ) { + template + requires(traits::c_sized_range and traits::c_graph_vertex, graph>) + void add_edges_from(traits::c_graph_vertex auto source, const TargetRng& target_rng) { + using rng_vertex_type = std::ranges::range_value_t; + this->_verify_vertex_id(source.id()); for (auto target : target_rng) this->_verify_vertex_id(target.id()); @@ -792,7 +906,7 @@ class graph final { this->_impl.add_edges_from( std::views::iota(static_cast(prev_n_edges), this->_n_edges), source.id(), - target_rng | std::views::transform(&vertex_type::id) + target_rng | std::views::transform(&rng_vertex_type::id) ); @@ -865,33 +979,42 @@ class graph final { /// @param target The target vertex descriptor. /// @return `true` if an edge exists, `false` otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline bool has_edge(vertex_type source, vertex_type target) const { + [[nodiscard]] gl_attr_force_inline bool has_edge( + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target + ) const { return this->has_edge(source.id(), target.id()); } /// @brief Retrieves an edge (if it exists) connecting the source to the target. /// @param source_id The source vertex ID. /// @param target_id The target vertex ID. - /// @return `true` if an edge exists, `false` otherwise. + /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex ID is invalid. - [[nodiscard]] std::optional edge(const id_type source_id, const id_type target_id) - const { - this->_verify_vertex_ids(source_id, target_id); + template + [[nodiscard]] std::optional> edge( + this Self& self, const id_type source_id, const id_type target_id + ) { + self._verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.edge(source_id, target_id, this->_edge_properties); + return self._impl.template edge>( + source_id, target_id, self._edge_properties + ); else - return this->_impl.edge(source_id, target_id); + return self._impl.template edge>(source_id, target_id); } /// @brief Retrieves an edge (if it exists) connecting the source to the target. /// @param source The source vertex descriptor. /// @param target The target vertex descriptor. - /// @return `true` if an edge exists, `false` otherwise. + /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline std::optional edge( - vertex_type source, vertex_type target - ) const { - return this->edge(source.id(), target.id()); + template + [[nodiscard]] gl_attr_force_inline std::optional> edge( + this Self& self, + traits::c_graph_vertex auto source, + traits::c_graph_vertex auto target + ) { + return self.edge(source.id(), target.id()); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -899,14 +1022,17 @@ class graph final { /// @param target_id The target vertex ID. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex ID is invalid. - [[nodiscard]] inline std::vector edges( - const id_type source_id, const id_type target_id - ) const { - this->_verify_vertex_ids(source_id, target_id); + template + [[nodiscard]] inline std::vector> edges( + this Self& self, const id_type source_id, const id_type target_id + ) { + self._verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.edges(source_id, target_id, this->_edge_properties); + return self._impl.template edges>( + source_id, target_id, self._edge_properties + ); else - return this->_impl.edges(source_id, target_id); + return self._impl.template edges>(source_id, target_id); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -914,91 +1040,108 @@ class graph final { /// @param target The target vertex descriptor. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline std::vector edges( - vertex_type source, vertex_type target - ) const { - return this->edges(source.id(), target.id()); + template + [[nodiscard]] gl_attr_force_inline std::vector> edges( + this Self& self, + traits::c_graph_vertex auto source, + traits::c_graph_vertex auto target + ) { + return self.edges(source.id(), target.id()); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex_id The vertex ID. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto incident_edges(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.incident_edges(vertex_id, this->_edge_properties); + return self._impl.template incident_edges>( + vertex_id, self._edge_properties + ); else - return this->_impl.incident_edges(vertex_id); + return self._impl.template incident_edges>(vertex_id); } - /// @brief Retrieves all incident with a vertex. + /// @brief Retrieves all edges incident with a vertex. /// @param vertex The vertex descriptor. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto incident_edges(vertex_type vertex) const { - return this->incident_edges(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto incident_edges( + this Self& self, traits::c_graph_vertex auto vertex + ) { + return self.incident_edges(vertex.id()); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto in_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto in_edges(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.in_edges(vertex_id, this->_edge_properties); + return self._impl.template in_edges>(vertex_id, self._edge_properties); else - return this->_impl.in_edges(vertex_id); + return self._impl.template in_edges>(vertex_id); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto in_edges(vertex_type vertex) const { - return this->in_edges(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto in_edges( + this Self& self, traits::c_graph_vertex auto vertex + ) { + return self.in_edges(vertex.id()); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto out_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] auto out_edges(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.out_edges(vertex_id, this->_edge_properties); + return self._impl.template out_edges>(vertex_id, self._edge_properties); else - return this->_impl.out_edges(vertex_id); + return self._impl.template out_edges>(vertex_id); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto out_edges(vertex_type vertex) const { - return this->out_edges(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto out_edges( + this auto& self, traits::c_graph_vertex auto vertex + ) { + return self.out_edges(vertex.id()); } /// @brief Retrieves a mutable reference to an edge's properties. /// @param id The ID of the edge. /// @return A reference to the properties attached to the edge. /// @throws std::invalid_argument If the edge ID is invalid. - [[nodiscard]] edge_properties_type& edge_properties(const id_type id) const + template + [[nodiscard]] edge_properties_t& edge_properties(this Self& self, const id_type id) requires(traits::c_non_empty_properties) { - if (id >= this->_n_edges) + if (id >= self._n_edges) throw std::invalid_argument(std::format("Got invalid edge id [{}]", id)); - return this->_edge_properties[id]; + return self._edge_properties[id]; } /// @brief Retrieves a view over all edge properties in the graph. /// @return A view mapping each active edge index to its property. - [[nodiscard]] gl_attr_force_inline auto edge_properties_map() const noexcept + [[nodiscard]] gl_attr_force_inline auto edge_properties_map(this auto& self) noexcept requires(traits::c_non_empty_properties) { - return std::views::all(this->_edge_properties); + return std::views::all(self._edge_properties); } // --- adjacency and incidence methods --- @@ -1045,8 +1188,9 @@ class graph final { /// @param target The target vertex descriptor. /// @return `true` if the given vertices are adjacent, `false` otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline bool are_adjacent(vertex_type source, vertex_type target) - const { + [[nodiscard]] gl_attr_force_inline bool are_adjacent( + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target + ) const { return this->are_adjacent(source.id(), target.id()); } @@ -1078,7 +1222,9 @@ class graph final { /// @param edge The edge descriptor. /// @return `true` if the vertex is incident to the edge, `false` otherwise. /// @throws std::invalid_argument If either the vertex or the edge descriptor is invalid. - [[nodiscard]] bool are_incident(vertex_type vertex, const edge_type& edge) const { + [[nodiscard]] bool are_incident( + traits::c_graph_vertex auto vertex, const edge_type& edge + ) const { this->_verify_vertex_id(vertex.id()); this->_verify_edge(edge); return edge.is_incident_with(vertex.id()); @@ -1092,8 +1238,9 @@ class graph final { /// @param vertex The vertex descriptor. /// @return `true` if the vertex is incident to the edge, `false` otherwise. /// @throws std::invalid_argument If either the vertex or the edge descriptor is invalid. - [[nodiscard]] gl_attr_force_inline bool are_incident(const edge_type& edge, vertex_type vertex) - const { + [[nodiscard]] gl_attr_force_inline bool are_incident( + const edge_type& edge, traits::c_graph_vertex auto vertex + ) const { return this->are_incident(vertex, edge); } @@ -1155,6 +1302,7 @@ class graph final { /// @brief Friend declaration providing access for graph target conversions. template + requires(not std::is_lvalue_reference_v) friend auto to(Graph&& source); /// @brief Internal friend structure for `to` conversion operations. @@ -1162,8 +1310,6 @@ class graph final { friend struct detail::to_impl; private: - using fmt_traits = io::detail::graph_fmt_traits; - graph(const graph& other) = default; // --- element validation --- @@ -1212,25 +1358,30 @@ class graph final { // --- transformations --- - gl_attr_force_inline auto _create_vertex_descriptor() const noexcept + template + gl_attr_force_inline auto _create_vertex_descriptor(this Self&) noexcept requires(traits::c_empty_properties) { - return [](const id_type id) { return vertex_type{id}; }; + return [](const id_type id) { return vertex_t{id}; }; } - gl_attr_force_inline auto _create_vertex_descriptor() const noexcept + template + gl_attr_force_inline auto _create_vertex_descriptor(this Self& self) noexcept requires(traits::c_non_empty_properties) { - return [&pmap = this->_vertex_properties](const id_type id) { - return vertex_type{id, pmap[to_idx(id)]}; + return [&pmap = self._vertex_properties](const id_type id) { + return vertex_t{id, pmap[to_idx(id)]}; }; } // --- I/O utility --- + using fmt_traits = io::detail::graph_fmt_traits; + + template struct concise_target_formatter { - edge_type edge; - id_type src_id; + edge_t edge; + id_t src_id; bool with_props; friend std::ostream& operator<<(std::ostream& os, const concise_target_formatter& proxy) { @@ -1254,16 +1405,16 @@ class graph final { return os; } - std::ostream& _concise_write(std::ostream& os) const { + template + std::ostream& _concise_write(this Self&& self, std::ostream& os) { using enum io::detail::option_bit; - for (auto src : this->vertices()) { + for (auto src : self.vertices()) { auto tgts = std::views::transform( - this->out_edges(src.id()), - [src_id = src.id(), - with_props = io::is_option_set(os, with_connection_properties)](const auto& edge) { - return concise_target_formatter{edge, src_id, with_props}; - } + self.out_edges(src.id()), + [src_id = src.id(), with_props = io::is_option_set(os, with_connection_properties)]( + const edge_t& edge + ) { return concise_target_formatter{edge, src_id, with_props}; } ); os << src << " : " << io::range_formatter(tgts) << '\n'; } @@ -1400,11 +1551,8 @@ class graph final { implementation_type _impl{}; - /// @todo Replace mutability with proper const-correct getter overloads to ensure thread safety guarantees associated with the const qualifier - [[no_unique_address]] mutable vertex_properties_map_type _vertex_properties{}; - - /// @todo Replace mutability with proper const-correct getter overloads to ensure thread safety guarantees associated with the const qualifier - [[no_unique_address]] mutable edge_properties_map_type _edge_properties{}; + [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; + [[no_unique_address]] edge_properties_map_type _edge_properties{}; }; // --- general graph utility --- @@ -1486,7 +1634,7 @@ using default_vertex_distance_type = std::int64_t; /// @ingroup GL-Core /// @brief Utility trait to resolve the underlying distance or weight numeric type for a graph. -template +template struct vertex_distance { /// @brief Resolves to the default distance type if no specific weight property is detected. using type = default_vertex_distance_type; @@ -1494,31 +1642,31 @@ struct vertex_distance { /// @ingroup GL-Core /// @brief Specialization resolving the specific weight type when edge properties contain weight attributes. -template -requires(traits::c_weight_properties_type) -struct vertex_distance { +template +requires(traits::c_weight_properties_type>) +struct vertex_distance { /// @brief Resolves to the `weight_type` inherently mapped to the graph's edges. - using type = typename GraphType::edge_properties_type::weight_type; + using type = typename edge_properties_t::weight_type; }; /// @ingroup GL-Core /// @brief Convenience alias to retrieve the appropriate distance numeric type from a graph structure. -template -using vertex_distance_type = typename vertex_distance::type; +template +using vertex_distance_t = typename vertex_distance::type; /// @ingroup GL-Core /// @brief Helper utility to safely retrieve the weight payload of an edge. /// /// Automatically returns a constant default if the graph representation carries no attached weight properties. /// -/// @tparam GraphType The underlying type of the graph. +/// @tparam G The underlying type of the graph. /// @param edge The edge descriptor to extract weight from. /// @return The specified edge weight or the unweighted graph default (1). -template -[[nodiscard]] gl_attr_force_inline vertex_distance_type get_weight( - const typename GraphType::edge_type& edge +template +[[nodiscard]] gl_attr_force_inline vertex_distance_t get_weight( + const traits::c_graph_edge auto& edge ) { - if constexpr (traits::c_weight_properties_type) + if constexpr (traits::c_weight_properties_type>) return edge.properties().weight; else return static_cast(1ll); diff --git a/include/gl/graph_traits.hpp b/include/gl/graph_traits.hpp index af50ddd0..6bda6933 100644 --- a/include/gl/graph_traits.hpp +++ b/include/gl/graph_traits.hpp @@ -13,6 +13,8 @@ #include "gl/edge_descriptor.hpp" #include "gl/types/core.hpp" +#include + namespace gl { /// @ingroup GL-Core @@ -62,15 +64,19 @@ struct graph_traits { /// @brief The type of graph element indentifiers (i.e. vertex and edge IDs). using id_type = IdType; - /// @brief The vertex descriptor type associated with this graph, defined based on the specified vertex properties and identifier type. - using vertex_type = vertex_descriptor; /// @brief The type of properties associated with the vertex descriptor. - using vertex_properties_type = typename vertex_type::properties_type; + using vertex_properties_type = std::remove_cvref_t; + /// @brief The descriptor type representing a vertex of a graph. + using vertex_type = vertex_descriptor; + /// @brief The descriptor type representing an immutable vertex of a graph. + using const_vertex_type = vertex_descriptor; - /// @brief The edge descriptor type associated with this graph, defined based on the specified edge properties and identifier type. - using edge_type = edge_descriptor; /// @brief The type of properties associated with the edge descriptor. - using edge_properties_type = typename edge_type::properties_type; + using edge_properties_type = std::remove_cvref_t; + /// @brief The descriptor type representing an edge of a graph. + using edge_type = edge_descriptor; + /// @brief The descriptor type representing an immutable edge of a graph. + using const_edge_type = edge_descriptor; }; /// @ingroup GL-Core diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index f58cb73f..28973f46 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -6,8 +6,8 @@ #include "gl/constants.hpp" #include "gl/graph_traits.hpp" -#include "gl/impl/specialized/adjacency_list.hpp" -#include "gl/impl/specialized/flat_adjacency_list.hpp" +#include "gl/impl/base/adjacency_list.hpp" +#include "gl/impl/base/flat_adjacency_list.hpp" #include "gl/types/core.hpp" #ifdef GL_TESTING @@ -28,16 +28,13 @@ struct to_impl; namespace impl { template -class adjacency_list final { +class adjacency_list final : public adjacency_list_base_t { public: - using representation_tag = typename GraphTraits::representation_tag; - using id_type = typename GraphTraits::id_type; - - using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; - using item_type = specialized::incidence_item; - using adjacency_storage_type = typename specialized::adjacency_list_impl_traits< - adjacency_list>::template storage_type; + using traits_type = GraphTraits; + using base_type = adjacency_list_base_t; + using id_type = typename traits_type::id_type; + using item_type = typename base_type::item_type; + using storage_type = typename base_type::storage_type; adjacency_list() = default; @@ -62,75 +59,47 @@ class adjacency_list final { } std::vector remove_vertex(id_type vertex_id) { - auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + auto removed_edge_ids = this->_remove_vertex_impl(vertex_id); this->_remap_element_ids(vertex_id, removed_edge_ids); return removed_edge_ids; } // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline auto neighbor_ids(id_type vertex_id) const { - return specialized_impl::neighbor_ids(*this, vertex_id); + [[nodiscard]] auto neighbor_ids(id_type vertex_id) const { + if constexpr (traits::c_directed_graph_traits) + return util::concat(this->successor_ids(vertex_id), this->predecessor_ids(vertex_id)); + else + return this->_list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); } - [[nodiscard]] gl_attr_force_inline auto predecessor_ids(id_type vertex_id) const { - return specialized_impl::predecessor_ids(*this, vertex_id); + [[nodiscard]] auto predecessor_ids(id_type vertex_id) const { + if constexpr (traits::c_directed_graph_traits) + return this->_in_edges_impl(vertex_id) | std::views::transform(&item_type::vertex_id); + else + return this->neighbor_ids(vertex_id); } [[nodiscard]] gl_attr_force_inline auto successor_ids(id_type vertex_id) const { - return specialized_impl::successor_ids(*this, vertex_id); - } - - // --- degree getters --- - - [[nodiscard]] gl_attr_force_inline size_type degree(id_type vertex_id) const { - return specialized_impl::degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type in_degree(id_type vertex_id) const { - return specialized_impl::in_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type out_degree(id_type vertex_id) const { - return specialized_impl::out_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { - return specialized_impl::degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return specialized_impl::in_degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return specialized_impl::out_degree_map(*this); + if constexpr (traits::c_directed_graph_traits) + return this->_list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); + else + return this->neighbor_ids(vertex_id); } // --- edge modifiers --- - gl_attr_force_inline void add_edge(id_type id, id_type source_id, id_type target_id) { - specialized_impl::add_edge(*this, id, source_id, target_id); - } - - gl_attr_force_inline void add_edges_from( - const traits::c_forward_range_of auto& edge_ids, - id_type source_id, - const traits::c_forward_range_of auto& target_ids - ) { - specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); - } - - void remove_edge(const edge_type& edge) { - specialized_impl::remove_edge(*this, edge); + void remove_edge(const auto& edge) { + this->_remove_edge_impl(edge); for (auto&& inc : this->_list) for (auto& item : inc) item.edge_id -= static_cast(item.edge_id > edge.id()); } - std::vector remove_edges(const traits::c_range_of auto& edges) { + std::vector remove_edges(const traits::c_range auto& edges) { for (const auto& edge : edges) - specialized_impl::remove_edge(*this, edge); + this->_remove_edge_impl(edge); + auto removed_edge_ids = edges | std::views::transform([](const auto& edge) { return edge.id(); }) | std::ranges::to(); @@ -146,93 +115,118 @@ class adjacency_list final { ); } - [[nodiscard]] gl_attr_force_inline bool has_edge(const edge_type& edge) const { + [[nodiscard]] gl_attr_force_inline bool has_edge(const auto& edge) const { return std::ranges::contains( this->_list[to_idx(edge.source())], item_type{edge.target(), edge.id()} ); } - [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { const auto& out_edges = this->_list[to_idx(source_id)]; const auto item_it = std::ranges::find(out_edges, target_id, &item_type::vertex_id); if (item_it == out_edges.cend()) return std::nullopt; - return std::make_optional(item_it->edge_id, source_id, target_id); + return std::make_optional(item_it->edge_id, source_id, target_id); } - [[nodiscard]] std::optional edge( + template + [[nodiscard]] std::optional edge( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { const auto& out_edges = this->_list[to_idx(source_id)]; - const auto item_it = - std::ranges::find(out_edges, target_id, [](auto item) { return item.vertex_id; }); + const auto item_it = std::ranges::find(out_edges, target_id, &item_type::vertex_id); if (item_it == out_edges.cend()) return std::nullopt; - return std::make_optional( + return std::make_optional( item_it->edge_id, source_id, target_id, edge_properties_map[to_idx(item_it->edge_id)] ); } - [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { return this->_list[source_id] | std::views::filter([&target_id](auto item) { return item.vertex_id == target_id; }) | std::views::transform([source_id](auto item) { - return edge_type{item.edge_id, source_id, item.vertex_id}; + return EdgeType{item.edge_id, source_id, item.vertex_id}; }) | std::ranges::to(); } - [[nodiscard]] std::vector edges( + template + [[nodiscard]] std::vector edges( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return this->_list[source_id] + return this->_list[to_idx(source_id)] | std::views::filter([&target_id](auto item) { return item.vertex_id == target_id; }) | std::views::transform([source_id, &edge_properties_map](auto item) { - return edge_type{ - item.edge_id, source_id, item.vertex_id, edge_properties_map[item.edge_id] + return EdgeType{ + item.edge_id, + source_id, + item.vertex_id, + edge_properties_map[to_idx(item.edge_id)] }; }) | std::ranges::to(); } + template [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id), + this->template out_edges(vertex_id) + ); + } + else { + return this->template out_edges(vertex_id); + } } + template [[nodiscard]] gl_attr_force_inline auto incident_edges( id_type vertex_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id, edge_properties_map), + this->template out_edges(vertex_id, edge_properties_map) + ); + } + else { + return this->template out_edges(vertex_id, edge_properties_map); + } } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { - return specialized_impl::in_edges(*this, vertex_id) - | std::views::transform([vertex_id](auto item) { - return edge_type{item.edge_id, item.vertex_id, vertex_id}; + return this->_in_edges_impl(vertex_id) | std::views::transform([vertex_id](auto item) { + return EdgeType{item.edge_id, item.vertex_id, vertex_id}; }); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return specialized_impl::in_edges(*this, vertex_id) + return this->_in_edges_impl(vertex_id) | std::views::transform([vertex_id, &edge_properties_map](auto item) { - return edge_type{ + return EdgeType{ item.edge_id, item.vertex_id, vertex_id, @@ -241,21 +235,23 @@ class adjacency_list final { }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return this->_list[to_idx(vertex_id)] | std::views::transform([vertex_id](auto item) { - return edge_type{item.edge_id, vertex_id, item.vertex_id}; + return EdgeType{item.edge_id, vertex_id, item.vertex_id}; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_list[to_idx(vertex_id)] | std::views::transform([vertex_id, &edge_properties_map](auto item) { - return edge_type{ + return EdgeType{ item.edge_id, vertex_id, item.vertex_id, @@ -270,6 +266,8 @@ class adjacency_list final { // --- friend declarations --- + friend base_type; + template friend struct gl::detail::to_impl; @@ -278,9 +276,6 @@ class adjacency_list final { #endif private: - using specialized_impl = typename specialized::adjacency_list_impl_traits::type; - friend specialized_impl; - void _remap_element_ids(id_type removed_vertex_id, std::vector& removed_edge_ids) { std::ranges::sort(removed_edge_ids); removed_edge_ids.erase( @@ -302,7 +297,7 @@ class adjacency_list final { } } - adjacency_storage_type _list{}; + storage_type _list{}; }; } // namespace impl diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 2c3204ff..a7637f88 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -6,8 +6,8 @@ #include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" -#include "gl/impl/specialized/adjacency_matrix.hpp" -#include "gl/impl/specialized/flat_adjacency_matrix.hpp" +#include "gl/impl/base/adjacency_matrix.hpp" +#include "gl/impl/base/flat_adjacency_matrix.hpp" #include "gl/types/core.hpp" #ifdef GL_TESTING @@ -28,21 +28,22 @@ struct to_impl; namespace impl { template -class adjacency_matrix final { +class adjacency_matrix final : public adjacency_matrix_base_t { public: - using representation_tag = typename GraphTraits::representation_tag; - using id_type = typename GraphTraits::id_type; + using traits_type = GraphTraits; + using base_type = adjacency_matrix_base_t; + using representation_tag = typename traits_type::representation_tag; + using id_type = typename traits_type::id_type; - using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; - using adjacency_storage_type = typename specialized::adjacency_matrix_impl_traits< - adjacency_matrix>::template storage_type; + using storage_type = typename base_type::storage_type; adjacency_matrix() = default; explicit adjacency_matrix(size_type n_vertices) { - specialized_impl::init(*this, n_vertices); + this->_init(n_vertices); } adjacency_matrix(const adjacency_matrix&) = default; @@ -56,84 +57,32 @@ class adjacency_matrix final { // --- vertex modifiers --- gl_attr_force_inline void add_vertex() { - specialized_impl::add_vertex(*this); + this->_add_vertex_impl(); } gl_attr_force_inline void add_vertices(size_type n) { - specialized_impl::add_vertices(*this, n); + this->_add_vertices_impl(n); } std::vector remove_vertex(id_type vertex_id) { - auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + auto removed_edge_ids = this->_remove_vertex_impl(vertex_id); this->_remap_element_ids(removed_edge_ids); return removed_edge_ids; } - // --- vertex getters --- - - [[nodiscard]] gl_attr_force_inline auto neighbor_ids(id_type vertex_id) const { - return specialized_impl::neighbor_ids(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline auto predecessor_ids(id_type vertex_id) const { - return specialized_impl::predecessor_ids(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline auto successor_ids(id_type vertex_id) const { - return specialized_impl::successor_ids(*this, vertex_id); - } - - // --- degree getters --- - - [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { - return specialized_impl::degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline size_type degree(id_type vertex_id) const { - return specialized_impl::degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type in_degree(id_type vertex_id) const { - return specialized_impl::in_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type out_degree(id_type vertex_id) const { - return specialized_impl::out_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return specialized_impl::in_degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return specialized_impl::out_degree_map(*this); - } - // --- edge modifiers --- - gl_attr_force_inline void add_edge(id_type id, id_type source_id, id_type target_id) { - specialized_impl::add_edge(*this, id, source_id, target_id); - } - - gl_attr_force_inline void add_edges_from( - const traits::c_forward_range_of auto& edge_ids, - id_type source_id, - const traits::c_forward_range_of auto& target_ids - ) { - specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); - } - - void remove_edge(const edge_type& edge) { - specialized_impl::remove_edge(*this, edge); + void remove_edge(const auto& edge) { + this->_remove_edge_impl(edge); for (auto&& row : this->_matrix) for (auto& edge_id : row) if (edge_id != invalid_id and edge_id > edge.id()) edge_id--; } - std::vector remove_edges(const traits::c_range_of auto& edges) { + std::vector remove_edges(const traits::c_range auto& edges) { for (const auto& edge : edges) - specialized_impl::remove_edge(*this, edge); + this->_remove_edge_impl(edge); auto removed_edge_ids = edges | std::views::transform([](const auto& edge) { return edge.id(); }) | std::ranges::to(); @@ -144,107 +93,130 @@ class adjacency_matrix final { // --- edge getters --- [[nodiscard]] gl_attr_force_inline bool has_edge(id_type source_id, id_type target_id) const { - return specialized_impl::get_entry(*this, source_id, target_id) != invalid_id; + return this->_get_entry_impl(source_id, target_id) != invalid_id; } - [[nodiscard]] bool has_edge(const edge_type& edge) const { - return specialized_impl::get_entry(*this, edge.source(), edge.target()) == edge.id(); + [[nodiscard]] bool has_edge(const auto& edge) const { + return this->_get_entry_impl(edge.source(), edge.target()) == edge.id(); } - [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional(edge_id, source_id, target_id); + return std::make_optional(edge_id, source_id, target_id); } - [[nodiscard]] std::optional edge( + template + [[nodiscard]] std::optional edge( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional( + return std::make_optional( edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)] ); } - [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - edge_type{edge_id, source_id, target_id} + return std::vector(); + return std::vector{ + EdgeType{edge_id, source_id, target_id} }; } - [[nodiscard]] std::vector edges( + template + [[nodiscard]] std::vector edges( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - edge_type{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} + return std::vector(); + return std::vector{ + EdgeType{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} }; } + template [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id), + this->template out_edges(vertex_id) + ); + } + else { + return this->template out_edges(vertex_id); + } } + template [[nodiscard]] gl_attr_force_inline auto incident_edges( id_type vertex_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id, edge_properties_map), + this->template out_edges(vertex_id, edge_properties_map) + ); + } + else { + return this->template out_edges(vertex_id, edge_properties_map); + } } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { - return std::views::iota(initial_id_v, this->_matrix.size()) + return std::views::iota(initial_id_v, static_cast(this->_matrix.size())) | std::views::filter([this, vertex_id](auto source_id) { - return specialized_impl::get_entry(*this, source_id, vertex_id) != invalid_id; + return this->_get_entry_impl(source_id, vertex_id) != invalid_id; }) | std::views::transform([this, vertex_id](auto source_id) { - return edge_type{ - specialized_impl::get_entry(*this, source_id, vertex_id), - source_id, - vertex_id + return EdgeType{ + this->_get_entry_impl(source_id, vertex_id), source_id, vertex_id }; }); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return std::views::iota(initial_id_v, this->_matrix.size()) + return std::views::iota(initial_id_v, static_cast(this->_matrix.size())) | std::views::filter([this, vertex_id](auto source_id) { - return specialized_impl::get_entry(*this, source_id, vertex_id) != invalid_id; + return this->_get_entry_impl(source_id, vertex_id) != invalid_id; }) | std::views::transform([this, vertex_id, &edge_properties_map](auto source_id) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, vertex_id); - return edge_type{ + const auto edge_id = this->_get_entry_impl(source_id, vertex_id); + return EdgeType{ edge_id, source_id, vertex_id, edge_properties_map[to_idx(edge_id)] }; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -253,13 +225,14 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id](auto entry) { auto [target_id, edge_id] = entry; - return edge_type{edge_id, vertex_id, static_cast(target_id)}; + return EdgeType{edge_id, vertex_id, static_cast(target_id)}; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -268,7 +241,7 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id, &edge_properties_map](auto entry) { const auto [target_id, edge_id] = entry; - return edge_type{ + return EdgeType{ edge_id, vertex_id, static_cast(target_id), @@ -284,6 +257,8 @@ class adjacency_matrix final { // --- friend declarations --- + friend base_type; + template friend struct gl::detail::to_impl; @@ -292,10 +267,6 @@ class adjacency_matrix final { #endif private: - using specialized_impl = - typename specialized::adjacency_matrix_impl_traits::type; - friend specialized_impl; - void _remap_element_ids(std::vector& removed_edge_ids) { std::ranges::sort(removed_edge_ids); removed_edge_ids.erase( @@ -318,7 +289,7 @@ class adjacency_matrix final { } } - adjacency_storage_type _matrix{}; + storage_type _matrix{}; }; } // namespace impl diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/base/adjacency_list.hpp similarity index 51% rename from include/gl/impl/specialized/adjacency_list.hpp rename to include/gl/impl/base/adjacency_list.hpp index 6927fdfb..295ae941 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/base/adjacency_list.hpp @@ -23,8 +23,6 @@ namespace gl::impl { template class adjacency_list; -namespace specialized { - template struct incidence_item { using id_type = IdType; @@ -32,7 +30,7 @@ struct incidence_item { id_type vertex_id; id_type edge_id; - [[nodiscard]] bool operator==(const incidence_item&) const = default; + [[nodiscard]] constexpr bool operator==(const incidence_item&) const noexcept = default; }; namespace detail { @@ -53,70 +51,21 @@ template AdjListItem> } // namespace detail -template AdjacencyList> -requires(traits::c_directed_edge) -struct directed_adjacency_list { - using impl_type = AdjacencyList; - using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; +template +class directed_adjacency_list { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; using item_type = incidence_item; - - // --- vertex modifiers --- - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - - // remove all edges incident to the vertex - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - auto& inc_edges = self._list[idx]; - if (idx == vertex_idx or inc_edges.empty()) - continue; - - const auto removed_subrng = - std::ranges::remove_if(inc_edges, [vertex_id, &removed_edges](auto item) { - if (item.vertex_id == vertex_id) { - removed_edges.push_back(item.edge_id); - return true; - } - return false; - }); - inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); - } - - // remove the list of edges incident from the vertex entirely - self._list.erase(self._list.begin() + to_diff(vertex_id)); - return removed_edges; - } - - // --- vertex getters --- - - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { - return util::concat(successor_ids(self, vertex_id), predecessor_ids(self, vertex_id)); - } - - [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { - return in_edges(self, vertex_id) | std::views::transform(&item_type::vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); - } + using storage_type = std::vector>; // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { - return in_degree(self, vertex_id) + out_degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { + return self.in_degree(vertex_id) + self.out_degree(vertex_id); } - [[nodiscard]] static size_type in_degree(const impl_type& self, id_type vertex_id) { + [[nodiscard]] size_type in_degree(this const auto& self, id_type vertex_id) { size_type in_deg = 0uz; for (const auto& out_edges : self._list) in_deg += static_cast( @@ -126,26 +75,24 @@ struct directed_adjacency_list { return in_deg; } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { return self._list[to_idx(vertex_id)].size(); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._list.size(), 0uz); for (auto idx = 0uz; idx < self._list.size(); ++idx) { degree_map[idx] += self._list[idx].size(); - std::ranges::for_each(self._list[idx], [°ree_map](auto item) { + for (auto item : self._list[idx]) ++degree_map[to_idx(item.vertex_id)]; - }); } return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._list.size(), 0uz); for (const auto& inc_edges : self._list) @@ -155,58 +102,79 @@ struct directed_adjacency_list { return in_degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return self._list - | std::views::transform([](const auto& inc_edges) { return inc_edges.size(); }) + return self._list | std::views::transform([](auto&& inc_edges) { return inc_edges.size(); }) | std::ranges::to>(); } // --- edge modifiers --- - gl_attr_force_inline static void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids ) { auto& inc_edges_source = self._list[to_idx(source_id)]; - inc_edges_source.reserve(inc_edges_source.size() + target_ids.size()); + inc_edges_source.reserve(inc_edges_source.size() + std::ranges::size(target_ids)); for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) inc_edges_source.emplace_back(target_id, edge_id); } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { - auto& inc_edges = self._list[to_idx(edge.source())]; - inc_edges.erase(detail::strict_find(inc_edges, edge)); - } + // --- comparison --- - // --- edge getters --- + [[nodiscard]] friend bool + operator==(const directed_adjacency_list&, const directed_adjacency_list&) = default; - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); +protected: + // --- vertex modifiers --- + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + + // remove all edges incident to the vertex + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + auto& inc_edges = self._list[idx]; + if (idx == vertex_idx or inc_edges.empty()) + continue; + + const auto removed_subrng = + std::ranges::remove_if(inc_edges, [vertex_id, &removed_edges](auto item) { + if (item.vertex_id == vertex_id) { + removed_edges.push_back(item.edge_id); + return true; + } + return false; + }); + inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); + } + + self._list.erase(self._list.begin() + to_diff(vertex_id)); + return removed_edges; } - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) - ); + // --- edge modifiers --- + + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { + auto& inc_edges = self._list[to_idx(edge.source())]; + inc_edges.erase(detail::strict_find(inc_edges, edge)); } - [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { + // --- edge getters --- + + [[nodiscard]] auto _in_edges_impl(this const auto& self, id_type vertex_id) { std::vector in_edges; for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { auto in_edges_view = @@ -214,7 +182,7 @@ struct directed_adjacency_list { return item.vertex_id == tgt_id; }) | std::views::transform([src_id](auto item) { - return incidence_item{src_id, item.edge_id}; + return item_type{src_id, item.edge_id}; }); in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); } @@ -222,116 +190,68 @@ struct directed_adjacency_list { } }; -template AdjacencyList> -requires(traits::c_undirected_edge) -struct undirected_adjacency_list { - using impl_type = AdjacencyList; - using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; +template +class undirected_adjacency_list { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; using item_type = incidence_item; - - // --- vertex modifiers --- - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - // remove all edges incident with the vertex (scan only the selected vertices) - for (auto item : self._list[vertex_idx]) { - if (item.vertex_id == vertex_id) - continue; // will be removed with the vertex's list - - auto& inc_edges = self._list[to_idx(item.vertex_id)]; - const auto removed_subrng = std::ranges::remove_if(inc_edges, [vertex_id](auto item) { - return item.vertex_id == vertex_id; - }); - inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); - } - - // remove the list of edges incident from the vertex entirely - const auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - self._list.erase(self._list.begin() + to_diff(vertex_id)); - return removed_edges; - } - - // --- vertex getters --- - - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); - } + using storage_type = std::vector>; // --- degree getters --- - [[nodiscard]] static size_type degree(const impl_type& self, id_type vertex_id) { + [[nodiscard]] size_type degree(this const auto& self, id_type vertex_id) { size_type degree = 0uz; for (auto item : self._list[to_idx(vertex_id)]) degree += 1uz + static_cast(item.vertex_id == vertex_id); return degree; } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map; degree_map.reserve(self._list.size()); for (id_type id = initial_id; id < self._list.size(); ++id) - degree_map.push_back(degree(self, id)); + degree_map.push_back(self.degree(id)); return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return degree_map(self); + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id + ) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); if (target_id != source_id) self._list[to_idx(target_id)].emplace_back(source_id, edge_id); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids ) { auto& inc_edges_source = self._list[to_idx(source_id)]; - inc_edges_source.reserve(inc_edges_source.size() + target_ids.size()); + inc_edges_source.reserve(inc_edges_source.size() + std::ranges::size(target_ids)); for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) { inc_edges_source.emplace_back(target_id, edge_id); @@ -340,7 +260,38 @@ struct undirected_adjacency_list { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_adjacency_list&, const undirected_adjacency_list&) = default; + +protected: + // --- vertex modifiers --- + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + for (auto item : self._list[vertex_idx]) { + if (item.vertex_id == vertex_id) + continue; + + auto& inc_edges = self._list[to_idx(item.vertex_id)]; + const auto removed_subrng = std::ranges::remove_if(inc_edges, [vertex_id](auto item) { + return item.vertex_id == vertex_id; + }); + inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); + } + + const auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + self._list.erase(self._list.begin() + to_diff(vertex_id)); + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto& self, const auto& edge) { auto& inc_edges_first = self._list[to_idx(edge.source())]; auto& inc_edges_second = self._list[to_idx(edge.target())]; @@ -351,53 +302,27 @@ struct undirected_adjacency_list { // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.out_edges(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.out_edges(vertex_id, edge_properties_map); - } - - [[nodiscard]] gl_attr_force_inline static auto in_edges( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto _in_edges_impl( + this const auto& self, id_type vertex_id ) { return std::views::all(self._list[to_idx(vertex_id)]); } }; -template AdjacencyList> -struct adjacency_list_impl_traits { +template +struct adjacency_list_base { using type = void; - - template - using storage_type = void; }; -template AdjacencyList> -requires traits::c_directed_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = directed_adjacency_list; - - template - using storage_type = std::vector>; -}; - -template AdjacencyList> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = undirected_adjacency_list; - - template - using storage_type = std::vector>; +template +using adjacency_list_base_t = typename adjacency_list_base::type; + +template +struct adjacency_list_base { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_adjacency_list, + undirected_adjacency_list>; }; -} // namespace specialized - } // namespace gl::impl diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/base/adjacency_matrix.hpp similarity index 59% rename from include/gl/impl/specialized/adjacency_matrix.hpp rename to include/gl/impl/base/adjacency_matrix.hpp index 5e21672f..206dcb2b 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/base/adjacency_matrix.hpp @@ -9,11 +9,15 @@ #include "gl/constants.hpp" #include "gl/decl/repr_tags.hpp" #include "gl/graph_traits.hpp" +#include "gl/traits.hpp" #include "gl/types/core.hpp" #include "gl/util/ranges.hpp" #include #include +#include +#include +#include #include namespace gl::impl { @@ -21,8 +25,6 @@ namespace gl::impl { template class adjacency_matrix; -namespace specialized { - namespace detail { [[nodiscard]] auto& strict_get(auto& id_matrix, const auto& edge) { @@ -52,66 +54,18 @@ inline void check_edge_override( } // namespace detail -template AdjacencyMatrix> -requires(traits::c_directed_edge) -struct directed_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; - - // --- general --- - - static void init(impl_type& self, size_type n_vertices) { - self._matrix.resize(n_vertices); - for (auto& row : self._matrix) - row.resize(n_vertices, invalid_id); - } - - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id)][to_idx(target_id)]; - } - - // --- vertex modifiers --- - - static void add_vertex(impl_type& self) { - for (auto& row : self._matrix) - row.emplace_back(invalid_id); - self._matrix.emplace_back(self._matrix.size() + 1uz, invalid_id); - } - - static void add_vertices(impl_type& self, size_type n) { - const auto new_n_vertices = self._matrix.size() + n; - for (auto& row : self._matrix) - row.resize(new_n_vertices, invalid_id); - for (auto _ = 0uz; _ < n; ++_) - self._matrix.emplace_back(new_n_vertices, invalid_id); - } - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); - - const auto vertex_pos = to_diff(vertex_id); - self._matrix.erase(self._matrix.begin() + vertex_pos); - for (auto& row : self._matrix) { - if (const auto edge_id = row[vertex_idx]; edge_id != invalid_id) - removed_edges.push_back(edge_id); - row.erase(row.begin() + vertex_pos); - } - - return removed_edges; - } +template +class directed_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = std::vector>; // --- vertex getters --- - [[nodiscard]] static auto successor_ids(const impl_type& self, id_type vertex_id) { + [[nodiscard]] auto successor_ids(this const auto& self, id_type vertex_id) { return self._matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { auto [target_id, edge_id] = entry; @@ -123,22 +77,20 @@ struct directed_adjacency_matrix { }); } - [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { + [[nodiscard]] auto predecessor_ids(this const auto& self, id_type vertex_id) { return std::views::iota(initial_id_v, static_cast(self._matrix.size())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto src_id) { return self._matrix[to_idx(src_id)][v_idx] != invalid_id; }); } - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { - return util::concat(successor_ids(self, vertex_id), predecessor_ids(self, vertex_id)); + [[nodiscard]] auto neighbor_ids(this const auto& self, id_type vertex_id) { + return util::concat(self.successor_ids(vertex_id), self.predecessor_ids(vertex_id)); } // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { size_type deg = 0uz; const auto vertex_idx = to_idx(vertex_id); for (auto v_idx = 0uz; v_idx < self._matrix.size(); ++v_idx) @@ -150,11 +102,8 @@ struct directed_adjacency_matrix { GL_SUPPRESS_WARNING_BEGIN("-Wsign-conversion") - // NOTE: Indexing into a row which might be a vector (requires size type) or a span/subrange (requires difference type) - - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return static_cast(std::ranges::count_if( self._matrix, [vertex_id](const auto& row) { return row[to_idx(vertex_id)] != invalid_id; } @@ -163,16 +112,15 @@ struct directed_adjacency_matrix { GL_SUPPRESS_WARNING_END - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return self._matrix[vertex_id].size() + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self._matrix[to_idx(vertex_id)].size() - static_cast( - std::ranges::count(self._matrix[vertex_id], invalid_id_v) + std::ranges::count(self._matrix[to_idx(vertex_id)], invalid_id_v) ); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.size(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.size(); ++src_idx) { @@ -187,7 +135,7 @@ struct directed_adjacency_matrix { return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._matrix.size(), 0uz); for (const auto& row : self._matrix) @@ -198,23 +146,24 @@ struct directed_adjacency_matrix { return in_degree_map; } - [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { - return std::views::iota(initial_id_v, self._matrix.size()) - | std::views::transform([&](id_type id) { return out_degree(self, id); }) + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self + ) { + return std::views::iota(initial_id_v, static_cast(self._matrix.size())) + | std::views::transform([&self](id_type id) { return self.out_degree(id); }) | std::ranges::to(); } // --- edge modifiers --- - static inline void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { detail::check_edge_override(self._matrix, source_id, target_id); self._matrix[to_idx(source_id)][to_idx(target_id)] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -227,59 +176,27 @@ struct directed_adjacency_matrix { matrix_source_row[to_idx(target_id)] = edge_id; } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { - detail::strict_get(self._matrix, edge) = invalid_id; - } + // --- comparison --- - // --- edge getters --- + [[nodiscard]] friend bool + operator==(const directed_adjacency_matrix&, const directed_adjacency_matrix&) = default; - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) - ); - } -}; - -template AdjacencyMatrix> -requires(traits::c_undirected_edge) -struct undirected_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; - - // --- general --- - - static void init(impl_type& self, size_type n_vertices) { +protected: + void _init(this auto& self, size_type n_vertices) { self._matrix.resize(n_vertices); for (auto& row : self._matrix) row.resize(n_vertices, invalid_id); } - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id)][to_idx(target_id)]; - } - // --- vertex modifiers --- - static void add_vertex(impl_type& self) { + void _add_vertex_impl(this auto& self) { for (auto& row : self._matrix) row.emplace_back(invalid_id); self._matrix.emplace_back(self._matrix.size() + 1uz, invalid_id); } - static void add_vertices(impl_type& self, size_type n) { + void _add_vertices_impl(this auto& self, size_type n) { const auto new_n_vertices = self._matrix.size() + n; for (auto& row : self._matrix) row.resize(new_n_vertices, invalid_id); @@ -287,25 +204,51 @@ struct undirected_adjacency_matrix { self._matrix.emplace_back(new_n_vertices, invalid_id); } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); - const auto removed_edges = + auto removed_edges = self._matrix[vertex_idx] | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) | std::ranges::to(); const auto vertex_pos = to_diff(vertex_id); self._matrix.erase(self._matrix.begin() + vertex_pos); - for (auto& row : self._matrix) + for (auto& row : self._matrix) { + if (const auto edge_id = row[vertex_idx]; edge_id != invalid_id) + removed_edges.push_back(edge_id); row.erase(row.begin() + vertex_pos); + } return removed_edges; } + // --- edge modifiers --- + + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { + detail::strict_get(self._matrix, edge) = invalid_id; + } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id)][to_idx(target_id)]; + } +}; + +template +class undirected_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = std::vector>; + // --- vertex getters --- - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { + [[nodiscard]] auto neighbor_ids(this const auto& self, id_type vertex_id) { return self._matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { auto [target_id, edge_id] = entry; @@ -317,23 +260,19 @@ struct undirected_adjacency_matrix { }); } - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + this const auto& self, id_type vertex_id ) { - return neighbor_ids(self, vertex_id); + return self.neighbor_ids(vertex_id); } - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); + [[nodiscard]] gl_attr_force_inline auto successor_ids(this const auto& self, id_type vertex_id) { + return self.neighbor_ids(vertex_id); } // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); return self._matrix.size() - static_cast( @@ -342,19 +281,17 @@ struct undirected_adjacency_matrix { + static_cast(self._matrix[vertex_idx][vertex_idx] != invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.size(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.size(); ++src_idx) { @@ -369,21 +306,18 @@ struct undirected_adjacency_matrix { return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return degree_map(self); + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto& self, id_type edge_id, id_type source_id, id_type target_id) { detail::check_edge_override(self._matrix, source_id, target_id); const auto source_idx = to_idx(source_id); @@ -394,8 +328,8 @@ struct undirected_adjacency_matrix { self._matrix[target_idx][source_idx] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -414,60 +348,84 @@ struct undirected_adjacency_matrix { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_adjacency_matrix&, const undirected_adjacency_matrix&) = default; + +protected: + void _init(this auto& self, size_type n_vertices) { + self._matrix.resize(n_vertices); + for (auto& row : self._matrix) + row.resize(n_vertices, invalid_id); + } + + // --- vertex modifiers --- + + void _add_vertex_impl(this auto& self) { + for (auto& row : self._matrix) + row.emplace_back(invalid_id); + self._matrix.emplace_back(self._matrix.size() + 1uz, invalid_id); + } + + void _add_vertices_impl(this auto& self, size_type n) { + const auto new_n_vertices = self._matrix.size() + n; + for (auto& row : self._matrix) + row.resize(new_n_vertices, invalid_id); + for (auto _ = 0uz; _ < n; ++_) + self._matrix.emplace_back(new_n_vertices, invalid_id); + } + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + const auto removed_edges = + self._matrix[vertex_idx] + | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) + | std::ranges::to(); + + const auto vertex_pos = to_diff(vertex_id); + self._matrix.erase(self._matrix.begin() + vertex_pos); + for (auto& row : self._matrix) + row.erase(row.begin() + vertex_pos); + + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto& self, const auto& edge) { if (edge.is_loop()) { detail::strict_get(self._matrix, edge) = invalid_id; } else { detail::strict_get(self._matrix, edge) = invalid_id; - // if the edge was found in the first matrix cell, it will also be present in the second matrix cell self._matrix[to_idx(edge.target())][to_idx(edge.source())] = invalid_id; } } // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.out_edges(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.out_edges(vertex_id, edge_properties_map); + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id)][to_idx(target_id)]; } }; -template AdjacencyMatrix> -struct adjacency_matrix_impl_traits { +template +struct adjacency_matrix_base { using type = void; - - template - using storage_type = void; -}; - -template AdjacencyMatrix> -requires traits::c_directed_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = directed_adjacency_matrix; - - template - using storage_type = std::vector>; }; -template AdjacencyMatrix> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = undirected_adjacency_matrix; - - template - using storage_type = std::vector>; +template +using adjacency_matrix_base_t = typename adjacency_matrix_base::type; + +template +struct adjacency_matrix_base { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_adjacency_matrix, + undirected_adjacency_matrix>; }; -} // namespace specialized - } // namespace gl::impl diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/base/flat_adjacency_list.hpp similarity index 50% rename from include/gl/impl/specialized/flat_adjacency_list.hpp rename to include/gl/impl/base/flat_adjacency_list.hpp index 9424a05a..8e0232bc 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/base/flat_adjacency_list.hpp @@ -7,7 +7,7 @@ #include "gl/constants.hpp" #include "gl/decl/repr_tags.hpp" #include "gl/graph_traits.hpp" -#include "gl/impl/specialized/adjacency_list.hpp" +#include "gl/impl/base/adjacency_list.hpp" #include "gl/types/flat_jagged_vector.hpp" #include @@ -15,110 +15,54 @@ #include #include -namespace gl::impl::specialized { +namespace gl::impl { -template AdjacencyList> -requires(traits::c_directed_edge) -struct directed_flat_adjacency_list { - using impl_type = AdjacencyList; - using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; +template +class directed_flat_adjacency_list { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; using item_type = incidence_item; - - // --- vertex modifiers --- - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - // extract out-edges - using item_type = typename impl_type::item_type; - auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - - // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename impl_type::adjacency_storage_type new_list; - new_list.reserve_segments(self._list.size() - 1uz); - new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); - - std::vector buffer; - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - if (idx == vertex_idx) - continue; - - buffer.clear(); - for (auto item : self._list[idx]) { - if (item.vertex_id == vertex_id) - removed_edges.push_back(item.edge_id); // remove in-edge - else - buffer.push_back(item); - } - new_list.push_back(buffer); - } - - self._list = std::move(new_list); - return removed_edges; - } - - // --- vertex getters --- - - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { - return util::concat(predecessor_ids(self, vertex_id), successor_ids(self, vertex_id)); - } - - [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { - return in_edges(self, vertex_id) | std::views::transform(&item_type::vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); - } + using storage_type = flat_jagged_vector; // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { - return in_degree(self, vertex_id) + out_degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { + return self.in_degree(vertex_id) + self.out_degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return static_cast( std::ranges::count(self._list.data_view(), vertex_id, &item_type::vertex_id) ); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)].size(); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self._list.segment_size(to_idx(vertex_id)); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._list.size(), 0uz); for (auto idx = 0uz; idx < self._list.size(); ++idx) { degree_map[idx] += self._list.segment_size(idx); for (auto item : self._list[idx]) - ++degree_map[item.vertex_id]; + ++degree_map[to_idx(item.vertex_id)]; } return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._list.size(), 0uz); for (auto item : self._list.data_view()) ++in_degree_map[to_idx(item.vertex_id)]; return in_degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { std::vector out_degree; out_degree.reserve(self._list.size()); @@ -129,83 +73,40 @@ struct directed_flat_adjacency_list { // --- edge modifiers --- - gl_attr_force_inline static void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { - self._list.push_back(to_idx(source_id), {target_id, edge_id}); + self._list.emplace_back(to_idx(source_id), target_id, edge_id); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids ) { for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) - self._list.push_back(to_idx(source_id), {target_id, edge_id}); - } - - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { - const auto edge_src = to_idx(edge.source()); - auto segment = self._list[edge_src]; - const auto it = detail::strict_find(segment, edge); - const auto pos = static_cast(std::distance(segment.begin(), it)); - self._list.erase(edge_src, pos); + self._list.emplace_back(to_idx(source_id), target_id, edge_id); } - // --- edge getters --- - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); - } + // --- comparison --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) - ); - } - - [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { - std::vector in_edges; - for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { - auto in_edges_view = - self._list[to_idx(src_id)] | std::views::filter([tgt_id = vertex_id](auto item) { - return item.vertex_id == tgt_id; - }) - | std::views::transform([src_id](auto item) { - return incidence_item{src_id, item.edge_id}; - }); - in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); - } - return in_edges; - } -}; - -template AdjacencyList> -requires(traits::c_undirected_edge) -struct undirected_flat_adjacency_list { - using impl_type = AdjacencyList; - using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; - using item_type = incidence_item; + [[nodiscard]] friend bool + operator==(const directed_flat_adjacency_list&, const directed_flat_adjacency_list&) = default; +protected: // --- vertex modifiers --- - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); - // all removed edges are stored in the vertex's list segment + // extract out-edges auto removed_edges = self._list[vertex_idx] | std::views::transform(&item_type::edge_id) | std::ranges::to(); // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename impl_type::adjacency_storage_type new_list; + storage_type new_list{}; new_list.reserve_segments(self._list.size() - 1uz); new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); @@ -215,10 +116,12 @@ struct undirected_flat_adjacency_list { continue; buffer.clear(); - for (auto item : self._list[idx]) - if (item.vertex_id != vertex_id) + for (auto item : self._list[idx]) { + if (item.vertex_id == vertex_id) + removed_edges.push_back(item.edge_id); // remove in-edge + else buffer.push_back(item); - + } new_list.push_back(buffer); } @@ -226,77 +129,88 @@ struct undirected_flat_adjacency_list { return removed_edges; } - // --- vertex getters --- + // --- edge modifiers --- - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { + const auto edge_src = to_idx(edge.source()); + auto segment = self._list[edge_src]; + const auto it = detail::strict_find(segment, edge); + const auto pos = static_cast(std::distance(segment.begin(), it)); + self._list.erase(edge_src, pos); } - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); - } + // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); + [[nodiscard]] auto _in_edges_impl(this const auto& self, id_type vertex_id) { + std::vector in_edges; + for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { + auto in_edges_view = + self._list[to_idx(src_id)] | std::views::filter([tgt_id = vertex_id](auto item) { + return item.vertex_id == tgt_id; + }) + | std::views::transform([src_id](auto item) { + return item_type{src_id, item.edge_id}; + }); + in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); + } + return in_edges; } +}; + +template +class undirected_flat_adjacency_list { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using item_type = incidence_item; + using storage_type = flat_jagged_vector; // --- degree getters --- - [[nodiscard]] static size_type degree(const impl_type& self, id_type vertex_id) { + [[nodiscard]] size_type degree(this const auto& self, id_type vertex_id) { size_type degree = 0uz; for (auto item : self._list[to_idx(vertex_id)]) degree += 1uz + static_cast(item.vertex_id == vertex_id); return degree; } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map; degree_map.reserve(self._list.size()); for (id_type id = initial_id; id < self._list.size(); ++id) - degree_map.push_back(degree(self, id)); + degree_map.push_back(self.degree(id)); return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return degree_map(self); + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto& self, id_type edge_id, id_type source_id, id_type target_id) { self._list.push_back(to_idx(source_id), {target_id, edge_id}); if (target_id != source_id) self._list.push_back(to_idx(target_id), {source_id, edge_id}); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -308,7 +222,48 @@ struct undirected_flat_adjacency_list { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_flat_adjacency_list&, const undirected_flat_adjacency_list&) = + default; + +protected: + // --- vertex modifiers --- + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + // all removed edges are stored in the vertex's list segment + auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + + // rebuild the graph (faster then shifting the entire data block for each removed edge) + storage_type new_list{}; + new_list.reserve_segments(self._list.size() - 1uz); + new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); + + std::vector buffer; + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + if (idx == vertex_idx) + continue; + + buffer.clear(); + for (auto item : self._list[idx]) + if (item.vertex_id != vertex_id) + buffer.push_back(item); + + new_list.push_back(buffer); + } + + self._list = std::move(new_list); + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto& self, const auto& edge) { const auto src_idx = to_idx(edge.source()); const auto tgt_idx = to_idx(edge.target()); @@ -320,8 +275,7 @@ struct undirected_flat_adjacency_list { self._list.erase(src_idx, pos); } - // remove from the target segment (if edge not a self-loop) - if (src_idx != tgt_idx) { + if (not edge.is_loop()) { auto inc_edges = self._list[tgt_idx]; const auto it = detail::strict_find(inc_edges, edge); const auto pos = static_cast(std::distance(inc_edges.begin(), it)); @@ -331,43 +285,19 @@ struct undirected_flat_adjacency_list { // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto _in_edges_impl( + this const auto& self, id_type vertex_id ) { - return self.out_edges(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.out_edges(vertex_id, edge_properties_map); - } - - [[nodiscard]] gl_attr_force_inline static auto in_edges( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)]; + return std::views::all(self._list[to_idx(vertex_id)]); } }; -template AdjacencyList> -requires traits::c_directed_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = directed_flat_adjacency_list; - - template - using storage_type = flat_jagged_vector; -}; - -template AdjacencyList> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = undirected_flat_adjacency_list; - - template - using storage_type = flat_jagged_vector; +template +struct adjacency_list_base { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_flat_adjacency_list, + undirected_flat_adjacency_list>; }; -} // namespace gl::impl::specialized +} // namespace gl::impl diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/base/flat_adjacency_matrix.hpp similarity index 58% rename from include/gl/impl/specialized/flat_adjacency_matrix.hpp rename to include/gl/impl/base/flat_adjacency_matrix.hpp index a87e78dc..afb04a4a 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/base/flat_adjacency_matrix.hpp @@ -9,7 +9,7 @@ #include "gl/constants.hpp" #include "gl/decl/repr_tags.hpp" #include "gl/graph_traits.hpp" -#include "gl/impl/specialized/adjacency_matrix.hpp" +#include "gl/impl/base/adjacency_matrix.hpp" #include "gl/types/core.hpp" #include "gl/types/flat_matrix.hpp" @@ -19,7 +19,7 @@ #include #include -namespace gl::impl::specialized { +namespace gl::impl { namespace detail { @@ -51,78 +51,19 @@ inline void check_edge_override( } // namespace detail -template AdjacencyMatrix> -requires(traits::c_directed_edge) -struct directed_flat_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using storage_type = typename impl_type::adjacency_storage_type; - - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; - - // --- general --- - - gl_attr_force_inline static void init(impl_type& self, size_type n_vertices) { - self._matrix = storage_type(n_vertices, n_vertices, invalid_id); - } - - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id), to_idx(target_id)]; - } - - // --- vertex modifiers --- - - static void add_vertex(impl_type& self) { - const auto new_size = self._matrix.n_rows() + 1uz; - self._matrix.resize(new_size, new_size, invalid_id); - } - - static void add_vertices(impl_type& self, size_type n) { - const auto new_size = self._matrix.n_rows() + n; - self._matrix.resize(new_size, new_size, invalid_id); - } - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - std::vector removed_edges; - removed_edges.reserve(self._matrix.size() * 2uz); - - // extract out-edges - for (auto edge_id : self._matrix[vertex_idx]) - if (edge_id != invalid_id) - removed_edges.push_back(edge_id); - - // extract in-edges - const auto col = self._matrix.col(vertex_idx); - for (auto r_idx = 0uz; r_idx < self._matrix.n_rows(); ++r_idx) { - if (r_idx == vertex_idx) - continue; - - const auto edge_id = col[to_diff(r_idx)]; - if (edge_id != invalid_id) - removed_edges.push_back(edge_id); - } - - // elegantly remove from the 2D grid - self._matrix.erase_row(vertex_idx); - self._matrix.erase_col(vertex_idx); - - return removed_edges; - } +template +class directed_flat_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = flat_matrix; // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { - return util::concat(predecessor_ids(self, vertex_id), successor_ids(self, vertex_id)); - } - - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + this const auto& self, id_type vertex_id ) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto r_id) { @@ -130,20 +71,20 @@ struct directed_flat_adjacency_matrix { }); } - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline auto successor_ids(this const auto& self, id_type vertex_id) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_cols())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { return self._matrix[v_idx, to_idx(c_id)] != invalid_id; }); } + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(this const auto& self, id_type vertex_id) { + return util::concat(self.predecessor_ids(vertex_id), self.successor_ids(vertex_id)); + } + // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { size_type deg = 0uz; const auto vertex_idx = to_idx(vertex_id); const auto row = self._matrix[vertex_idx]; @@ -157,22 +98,20 @@ struct directed_flat_adjacency_matrix { return deg; } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return static_cast(std::ranges::count_if( self._matrix.col(to_idx(vertex_id)), [](auto edge_id) { return edge_id != invalid_id; } )); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { const auto row = self._matrix[to_idx(vertex_id)]; return row.size() - static_cast(std::ranges::count(row, invalid_id_v)); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.n_rows(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.n_rows(); ++src_idx) { @@ -187,7 +126,7 @@ struct directed_flat_adjacency_matrix { return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._matrix.n_rows(), 0uz); for (const auto row : self._matrix.rows()) @@ -198,23 +137,23 @@ struct directed_flat_adjacency_matrix { return in_degree_map; } - [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { + [[nodiscard]] std::vector out_degree_map(this const auto& self) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) - | std::views::transform([&](id_type id) { return out_degree(self, id); }) + | std::views::transform([&self](id_type id) { return self.out_degree(id); }) | std::ranges::to(); } // --- edge modifiers --- - static inline void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { detail::check_edge_override(self._matrix, source_id, target_id); self._matrix[to_idx(source_id), to_idx(target_id)] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -227,104 +166,102 @@ struct directed_flat_adjacency_matrix { matrix_source_row[to_diff(target_id)] = edge_id; } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { - detail::strict_get(self._matrix, edge) = invalid_id; - } - - // --- edge getters --- - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) - ); - } -}; - -template AdjacencyMatrix> -requires(traits::c_undirected_edge) -struct undirected_flat_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using storage_type = typename impl_type::adjacency_storage_type; - - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; + // --- comparison --- - // --- general --- + [[nodiscard]] friend bool + operator==(const directed_flat_adjacency_matrix&, const directed_flat_adjacency_matrix&) = + default; - gl_attr_force_inline static void init(impl_type& self, size_type n_vertices) { +protected: + gl_attr_force_inline void _init(this auto& self, size_type n_vertices) { self._matrix = storage_type(n_vertices, n_vertices, invalid_id); } - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id), to_idx(target_id)]; - } - // --- vertex modifiers --- - static void add_vertex(impl_type& self) { + void _add_vertex_impl(this auto& self) { const auto new_size = self._matrix.n_rows() + 1uz; self._matrix.resize(new_size, new_size, invalid_id); } - static void add_vertices(impl_type& self, size_type n) { + void _add_vertices_impl(this auto& self, size_type n) { const auto new_size = self._matrix.n_rows() + n; self._matrix.resize(new_size, new_size, invalid_id); } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); + std::vector removed_edges; + removed_edges.reserve(self._matrix.size() * 2uz); - const auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); + // extract out-edges + for (auto edge_id : self._matrix[vertex_idx]) + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + + // extract in-edges + const auto col = self._matrix.col(vertex_idx); + for (auto r_idx = 0uz; r_idx < self._matrix.n_rows(); ++r_idx) { + if (r_idx == vertex_idx) + continue; + const auto edge_id = col[to_diff(r_idx)]; + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + } + + // elegantly remove from the 2D grid self._matrix.erase_row(vertex_idx); self._matrix.erase_col(vertex_idx); return removed_edges; } + // --- edge modifiers --- + + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { + detail::strict_get(self._matrix, edge) = invalid_id; + } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id), to_idx(target_id)]; + } +}; + +template +class undirected_flat_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = flat_matrix; + // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(this const auto& self, id_type vertex_id) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_cols())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { return self._matrix[v_idx, to_idx(c_id)] != invalid_id; }); } - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + this const auto& self, id_type vertex_id ) { - return neighbor_ids(self, vertex_id); + return self.neighbor_ids(vertex_id); } - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); + [[nodiscard]] gl_attr_force_inline auto successor_ids(this const auto& self, id_type vertex_id) { + return self.neighbor_ids(vertex_id); } // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); const auto row = self._matrix[vertex_idx]; return self._matrix.n_cols() @@ -332,19 +269,17 @@ struct undirected_flat_adjacency_matrix { + static_cast(self._matrix[vertex_idx, vertex_idx] != invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.n_rows(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.n_rows(); ++src_idx) { @@ -359,21 +294,18 @@ struct undirected_flat_adjacency_matrix { return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return degree_map(self); + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto& self, id_type edge_id, id_type source_id, id_type target_id) { detail::check_edge_override(self._matrix, source_id, target_id); const auto source_idx = to_idx(source_id); @@ -384,8 +316,8 @@ struct undirected_flat_adjacency_matrix { self._matrix[target_idx, source_idx] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -404,7 +336,47 @@ struct undirected_flat_adjacency_matrix { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_flat_adjacency_matrix&, const undirected_flat_adjacency_matrix&) = + default; + +protected: + gl_attr_force_inline void _init(this auto& self, size_type n_vertices) { + self._matrix = storage_type(n_vertices, n_vertices, invalid_id); + } + + // --- vertex modifiers --- + + void _add_vertex_impl(this auto& self) { + const auto new_size = self._matrix.n_rows() + 1uz; + self._matrix.resize(new_size, new_size, invalid_id); + } + + void _add_vertices_impl(this auto& self, size_type n) { + const auto new_size = self._matrix.n_rows() + n; + self._matrix.resize(new_size, new_size, invalid_id); + } + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + std::vector removed_edges; + for (auto edge_id : self._matrix[vertex_idx]) { + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + } + + self._matrix.erase_row(vertex_idx); + self._matrix.erase_col(vertex_idx); + + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto& self, const auto& edge) { if (edge.is_loop()) { detail::strict_get(self._matrix, edge) = invalid_id; } @@ -416,37 +388,18 @@ struct undirected_flat_adjacency_matrix { // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.out_edges(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.out_edges(vertex_id, edge_properties_map); + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id), to_idx(target_id)]; } }; -template AdjacencyMatrix> -requires traits::c_directed_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = directed_flat_adjacency_matrix; - - template - using storage_type = flat_matrix; -}; - -template AdjacencyMatrix> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = undirected_flat_adjacency_matrix; - - template - using storage_type = flat_matrix; +template +struct adjacency_matrix_base { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_flat_adjacency_matrix, + undirected_flat_adjacency_matrix>; }; -} // namespace gl::impl::specialized +} // namespace gl::impl diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index 8e394470..1652abce 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -12,6 +12,7 @@ #include #include +#include #include namespace gl { @@ -256,7 +257,7 @@ namespace traits { /// /// @tparam T The type to evaluate against the concept. template -concept c_properties = std::semiregular; +concept c_properties = std::semiregular>; /// @ingroup GL-Traits /// @brief Validates if a type is specifically the @ref gl::empty_properties tag. @@ -266,7 +267,8 @@ concept c_properties = std::semiregular; /// /// @tparam T The type to evaluate against the concept. template -concept c_empty_properties = c_properties and std::same_as; +concept c_empty_properties = + c_properties and std::same_as, gl::empty_properties>; /// @ingroup GL-Traits /// @brief Validates if a property type contains actual user-defined data. diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index cb244f5d..eac6dad1 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -91,6 +91,18 @@ class vertex_descriptor final { requires(traits::c_non_empty_properties) : _id(id), _properties(properties) {} + /// @brief Implicit converting constructor from a non-const descriptor to a const descriptor. + /// @tparam NonConstProps The non-const property type. + /// @param other The vertex descriptor to convert from. + template + requires(std::same_as) + vertex_descriptor(const vertex_descriptor& other) noexcept + : _id(other.id()) { + if constexpr (traits::c_non_empty_properties) { + this->_properties = other.properties(); + } + } + /// @brief Returns an invalid vertex descriptor (for empty properties). /// @return A `vertex_descriptor` holding the `invalid_id`. [[nodiscard]] gl_attr_force_inline static vertex_descriptor invalid() noexcept @@ -121,21 +133,28 @@ class vertex_descriptor final { /// @brief Destructor. ~vertex_descriptor() = default; - /// @brief Equality comparison operator. + /// @brief Cross-type equality comparison operator. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The vertex descriptor to compare against. /// @return `true` if both descriptors hold the same ID, `false` otherwise. - [[nodiscard]] gl_attr_force_inline bool operator==(const vertex_descriptor& other + template + requires(std::same_as, std::remove_cv_t>) + [[nodiscard]] gl_attr_force_inline bool operator==( + const vertex_descriptor& other ) const noexcept { - return this->_id == other._id; + return this->_id == other.id(); } - /// @brief Three-way comparison operator. + /// @brief Cross-type three-way comparison operator. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The vertex descriptor to compare against. /// @return The strong ordering result based on the underlying IDs. + template + requires(std::same_as, std::remove_cv_t>) [[nodiscard]] gl_attr_force_inline std::strong_ordering operator<=>( - const vertex_descriptor& other + const vertex_descriptor& other ) const noexcept { - return this->_id <=> other._id; + return this->_id <=> other.id(); } /// @brief Boolean conversion operator. diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index 5d43221b..14bba1ca 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -119,7 +119,7 @@ constexpr gl::size_type n_inc_edges_for_fully_connected_vertex = constants::n_el template struct test_directed_adjacency_list : public test_adjacency_list { using sut_type = SutType; - using edge_type = typename sut_type::edge_type; + using edge_type = typename sut_type::traits_type::edge_type; edge_type add_edge(const auto source_id, const auto target_id) { const auto new_edge_id = this->next_edge_id++; @@ -466,7 +466,7 @@ TEST_CASE_TEMPLATE_INSTANTIATE( template struct test_undirected_adjacency_list : public test_adjacency_list { using sut_type = SutType; - using edge_type = typename sut_type::edge_type; + using edge_type = typename sut_type::traits_type::edge_type; edge_type add_edge(const auto source_id, const auto target_id) { const auto new_edge_id = this->next_edge_id++; diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index 15e6e314..e1913919 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -176,8 +176,7 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; const auto graph = gl::topology::regular_binary_tree(constants::depth); - const auto pred_map = - gl::algorithm::breadth_first_search(graph); + const auto pred_map = gl::algorithm::breadth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pred_map.size(), graph.n_vertices()); diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index e91d9069..1131912c 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -183,7 +183,7 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; const auto graph = gl::topology::regular_binary_tree(constants::depth); - const auto pred_map = gl::algorithm::depth_first_search(graph); + const auto pred_map = gl::algorithm::depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pred_map.size(), graph.n_vertices()); @@ -386,8 +386,7 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; const auto graph = gl::topology::regular_binary_tree(constants::depth); - const auto pred_map = - gl::algorithm::recursive_depth_first_search(graph); + const auto pred_map = gl::algorithm::recursive_depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pred_map.size(), graph.n_vertices()); diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index 510dd5fd..9e482b74 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -27,7 +27,7 @@ TEST_CASE_TEMPLATE_DEFINE( static_assert(gl::traits::c_weight_properties_type); SUBCASE("should throw if there is an edge with a negative weight") { - const auto sut = gl::topology::clique(constants::n_elements_alg); + auto sut = gl::topology::clique(constants::n_elements_alg); sut.edge(constants::v1_id, constants::v2_id)->properties().weight = -static_cast(constants::n_elements_alg); diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index e7aa78fb..39ae114b 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace gl_testing { @@ -47,9 +48,8 @@ namespace predicate { template [[nodiscard]] auto is_vertex_fully_connected(const GraphType& graph) { - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { - return std::ranges::all_of(graph.vertices(), [&](const vertex_type& target) { + return [&graph](const auto& source) { + return std::ranges::all_of(graph.vertices(), [&](const auto& target) { return source == target or graph.has_edge(source, target); }); }; @@ -57,9 +57,8 @@ template template [[nodiscard]] auto is_vertex_not_connected(const GraphType& graph) { - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { - return std::ranges::none_of(graph.vertices(), [&](const vertex_type& target) { + return [&graph](const auto& source) { + return std::ranges::none_of(graph.vertices(), [&](const auto& target) { return graph.has_edge(source, target); }); }; @@ -69,8 +68,7 @@ template [[nodiscard]] auto is_vertex_not_connected_to_any_from( const GraphType& graph, const auto& vertex_it_range ) { - using vertex_type = typename GraphType::vertex_type; - return [&](const vertex_type& source) { + return [&](const auto& source) { return std::ranges::none_of(vertex_it_range, [&](const auto& vertex) { return vertex != source and (graph.has_edge(source, vertex) or graph.has_edge(vertex, source)); @@ -81,8 +79,7 @@ template template [[nodiscard]] auto is_vertex_connected_to_next_only(const GraphType& graph) { using id_type = typename GraphType::id_type; - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto next_vertex_id = static_cast((source.id() + 1uz) % graph.n_vertices()); const auto next_vertex = graph[next_vertex_id]; @@ -95,8 +92,7 @@ template template [[nodiscard]] auto is_vertex_connected_to_prev_only(const GraphType& graph) { using id_type = typename GraphType::id_type; - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto prev_vertex_id = static_cast((source.id() + graph.n_vertices() - 1uz) % graph.n_vertices()); const auto prev_vertex = graph[prev_vertex_id]; @@ -110,8 +106,7 @@ template template [[nodiscard]] auto is_vertex_connected_to_id_adjacent(const GraphType& graph) { using id_type = typename GraphType::id_type; - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto next_vertex_id = static_cast((source.id() + 1uz) % graph.n_vertices()); const auto next_vertex = graph[next_vertex_id]; @@ -128,8 +123,7 @@ template template [[nodiscard]] auto is_connected_to_binary_chlidren(const GraphType& graph) { - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto target_ids = gl::topology::detail::get_bintree_target_ids(source.id()); if (target_ids.first >= graph.n_vertices()) @@ -182,7 +176,6 @@ TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent graph topology builders tests", GraphType, graph_type_template ) { using graph_type = GraphType; - using vertex_type = typename graph_type::vertex_type; SUBCASE("clique(n_vertices) should build a fully connected graph of size n_vertices") { const auto clique = gl::topology::clique(constants::n_elements_top); @@ -215,7 +208,7 @@ TEST_CASE_TEMPLATE_DEFINE( // verify that all vertices from A are connected to all vertices from B and vice versa CHECK(std::ranges::all_of( vertices_a | std::views::take(constants::n_elements_top), - [&](const vertex_type& source) { + [&](const auto& source) { return std::ranges::all_of(vertices_b, [&](const auto& vertex) { return biclique.has_edge(source, vertex) and biclique.has_edge(vertex, source); });