From 9b3ed9c9cd3e82a55b34a2d55a942768d89ed694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Thu, 28 Nov 2024 20:55:10 +0100 Subject: [PATCH] YT-CPPGL-54: Add vertex degree map getters - Added the `in_degree_map`, `out_degree_map` and `degree_map` functions to the graph class - Replaced per vertex in-degree calculating in the `topological_sort` algorithm with the `in_degree_map` function --- docs/graph.md | 12 ++ include/gl/algorithm/topological_sort.hpp | 13 +- include/gl/graph.hpp | 12 ++ include/gl/impl/adjacency_list.hpp | 12 ++ include/gl/impl/adjacency_matrix.hpp | 12 ++ .../gl/impl/specialized/adjacency_list.hpp | 68 +++++++++- .../gl/impl/specialized/adjacency_matrix.hpp | 65 ++++++++++ tests/source/test_adjacency_list.cpp | 120 +++++++++++++++--- tests/source/test_adjacency_matrix.cpp | 120 +++++++++++++++--- tests/source/test_graph.cpp | 12 +- 10 files changed, 387 insertions(+), 59 deletions(-) diff --git a/docs/graph.md b/docs/graph.md index 8520538..0bbd151 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -243,6 +243,10 @@ Based on the specified traits, the `graph` class defines the following types: - `vertex_id: types::id_type` – the ID of the vertex for which to calculate the in-degree. - *Return type*: `types::size_type` +- **`graph.in_degree_map() const`**: + - *Description*: Returns a vector containing the in-degrees of the corresponding vertices (in-degree at index `i` corresponds to the vertex with an ID equal `i`). + - *Return type*: `std::vector` + - **`graph.out_degree(vertex) const`**: - *Description*: Returns the out-degree (number of outgoing edges) of the specified vertex. - *Returned value*: @@ -261,6 +265,10 @@ Based on the specified traits, the `graph` class defines the following types: - `vertex_id: types::id_type` – the ID of the vertex for which to calculate the out-degree. - *Return type*: `types::size_type` +- **`graph.out_degree_map() const`**: + - *Description*: Returns a vector containing the out-degrees of the corresponding vertices (out-degree at index `i` corresponds to the vertex with an ID equal `i`). + - *Return type*: `std::vector` + - **`graph.degree(vertex) const`**: - *Description*: Returns the degree (number of incoming and outgoing edges) of the specified vertex. - *Returned value*: @@ -284,6 +292,10 @@ Based on the specified traits, the `graph` class defines the following types: - `vertex_id: types::id_type` – the ID of the vertex for which to calculate the degree. - *Return type*: `types::size_type` +- **`graph.degree_map() const`**: + - *Description*: Returns a vector containing the degrees of the corresponding vertices (degree at index `i` corresponds to the vertex with an ID equal `i`). + - *Return type*: `std::vector` +
### Edge Operations diff --git a/include/gl/algorithm/topological_sort.hpp b/include/gl/algorithm/topological_sort.hpp index c957479..445a15c 100644 --- a/include/gl/algorithm/topological_sort.hpp +++ b/include/gl/algorithm/topological_sort.hpp @@ -24,17 +24,14 @@ template < const auto vertex_ids = graph.vertex_ids(); - // prepare the vertex in degree list - std::vector vertex_in_deg_list; - vertex_in_deg_list.reserve(graph.n_vertices()); - for (const auto id : vertex_ids) - vertex_in_deg_list.push_back(graph.in_degree(id)); + // prepare the vertex in degree map + std::vector in_degree_map = graph.in_degree_map(); // prepare the initial queue content (source vertices) std::vector source_vertex_list; source_vertex_list.reserve(graph.n_vertices()); for (const auto id : vertex_ids) - if (vertex_in_deg_list[id] == constants::default_size) + if (in_degree_map[id] == constants::default_size) source_vertex_list.emplace_back(id); std::optional> topological_order_opt = @@ -52,11 +49,11 @@ template < topological_order.push_back(vertex.id()); return true; }, - [&vertex_in_deg_list](const vertex_type& vertex, const edge_type& in_edge) + [&in_degree_map](const vertex_type& vertex, const edge_type& in_edge) -> std::optional { // enqueue predicate if (in_edge.is_loop()) return false; - return --vertex_in_deg_list[vertex.id()] == constants::default_size; + return --in_degree_map[vertex.id()] == constants::default_size; }, pre_visit, post_visit diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 2c0bb9b..f0cee67 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -187,6 +187,10 @@ class graph final { return this->_impl.in_degree(vertex_id); } + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { + return this->_impl.in_degree_map(); + } + [[nodiscard]] gl_attr_force_inline types::size_type out_degree(const vertex_type& vertex ) const { this->_verify_vertex(vertex); @@ -199,6 +203,10 @@ class graph final { return this->_impl.out_degree(vertex_id); } + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { + return this->_impl.out_degree_map(); + } + [[nodiscard]] gl_attr_force_inline types::size_type degree(const vertex_type& vertex) const { this->_verify_vertex(vertex); return this->_impl.degree(vertex.id()); @@ -210,6 +218,10 @@ class graph final { return this->_impl.degree(vertex_id); } + [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { + return this->_impl.degree_map(); + } + // --- edge methods --- // clang-format off diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index 3e1cd48..baccf29 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -78,6 +78,18 @@ class adjacency_list final { return specialized_impl::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); + } + + [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { + return specialized_impl::degree_map(*this); + } + gl_attr_force_inline void remove_vertex(const vertex_type& vertex) { specialized_impl::remove_vertex(*this, vertex); } diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 5e11c88..4fc27b6 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -98,6 +98,18 @@ class adjacency_matrix final { return specialized_impl::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); + } + + [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { + return specialized_impl::degree_map(*this); + } + gl_attr_force_inline void remove_vertex(const vertex_type& vertex) { specialized_impl::remove_vertex(*this, vertex.id()); } diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index a57b05f..ac3d83a 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -67,8 +67,8 @@ struct directed_adjacency_list { const impl_type& self, const types::id_type vertex_id ) { types::size_type in_deg = constants::default_size; - for (types::id_type i = constants::initial_id; i < self._list.size(); ++i) { - const auto& adj_edges = self._list[i]; + for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { + const auto& adj_edges = self._list[id]; if (adj_edges.empty()) continue; @@ -92,13 +92,49 @@ struct directed_adjacency_list { return in_degree(self, vertex_id) + out_degree(self, vertex_id); } + [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + std::vector in_degree_map(self._list.size(), constants::zero); + + for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { + std::ranges::for_each(self._list[id], [&in_degree_map](const auto& edge) { + ++in_degree_map[edge->second_id()]; + }); + } + + return in_degree_map; + } + + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + const auto out_degree_view = + self._list + | std::views::transform([](const auto& adj_edges) { return adj_edges.size(); }); + return std::vector(out_degree_view.begin(), out_degree_view.end()); + } + + [[nodiscard]] static std::vector degree_map(const impl_type& self) { + std::vector degree_map(self._list.size(), constants::zero); + + for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { + degree_map[id] += self._list[id].size(); + + // update in degrees + std::ranges::for_each(self._list[id], [°ree_map](const auto& edge) { + ++degree_map[edge->second_id()]; + }); + } + + return degree_map; + } + static void remove_vertex(impl_type& self, const vertex_type& vertex) { const auto vertex_id = vertex.id(); // remove all edges incident to the vertex - for (types::id_type i = constants::initial_id; i < self._list.size(); ++i) { - auto& adj_edges = self._list[i]; - if (i == vertex_id or adj_edges.empty()) + for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { + auto& adj_edges = self._list[id]; + if (id == vertex_id or adj_edges.empty()) continue; const auto rem_subrange = std::ranges::remove_if( @@ -194,6 +230,28 @@ struct undirected_adjacency_list { return degree; } + [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( + const impl_type& self + ) { + return degree_map(self); + } + + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return degree_map(self); + } + + [[nodiscard]] static std::vector degree_map(const impl_type& self) { + std::vector degree_map; + degree_map.reserve(self._list.size()); + + for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) + degree_map.push_back(degree(self, id)); + + return degree_map; + } + static void remove_vertex(impl_type& self, const vertex_type& vertex) { const auto vertex_id = vertex.id(); std::unordered_set incident_vertex_id_set; diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 8b05574..1a1420c 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -81,6 +81,43 @@ struct directed_adjacency_matrix { return in_degree(self, vertex_id) + out_degree(self, vertex_id); } + [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + std::vector in_degree_map(self._matrix.size(), constants::zero); + + for (const auto& row : self._matrix) + for (types::id_type id = constants::initial_id; id < self._matrix.size(); ++id) + if (row[id] != nullptr) + ++in_degree_map[id]; + + return in_degree_map; + } + + [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { + std::vector out_degree_map; + out_degree_map.reserve(self._matrix.size()); + + for (types::id_type id = constants::initial_id; id < self._matrix.size(); ++id) + out_degree_map.push_back(out_degree(self, id)); + + return out_degree_map; + } + + [[nodiscard]] static std::vector degree_map(const impl_type& self) { + std::vector degree_map(self._matrix.size(), constants::zero); + + for (types::id_type u_id = constants::initial_id; u_id < self._matrix.size(); ++u_id) { + const auto& row = self._matrix[u_id]; + for (types::id_type v_id = constants::initial_id; v_id < self._matrix.size(); ++v_id) { + if (row[v_id] != nullptr) { + ++degree_map[u_id]; + ++degree_map[v_id]; + } + } + } + + return degree_map; + } + static void remove_vertex(impl_type& self, const types::id_type vertex_id) { self._n_unique_edges -= self.adjacent_edges(vertex_id).distance(); self._matrix.erase(std::next(std::begin(self._matrix), vertex_id)); @@ -152,6 +189,34 @@ struct undirected_adjacency_matrix { return degree; } + [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( + const impl_type& self + ) { + return degree_map(self); + } + + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return degree_map(self); + } + + [[nodiscard]] static std::vector degree_map(const impl_type& self) { + std::vector degree_map(self._matrix.size(), constants::zero); + + for (types::id_type u_id = constants::initial_id; u_id < self._matrix.size(); ++u_id) { + const auto& row = self._matrix[u_id]; + for (types::id_type v_id = constants::initial_id; v_id <= u_id; ++v_id) { + if (row[v_id] != nullptr) { + ++degree_map[u_id]; + ++degree_map[v_id]; + } + } + } + + return degree_map; + } + static void remove_vertex(impl_type& self, const types::id_type vertex_id) { self._n_unique_edges -= self.adjacent_edges(vertex_id).distance(); self._matrix.erase(std::next(std::begin(self._matrix), vertex_id)); diff --git a/tests/source/test_adjacency_list.cpp b/tests/source/test_adjacency_list.cpp index e218978..8dd4d04 100644 --- a/tests/source/test_adjacency_list.cpp +++ b/tests/source/test_adjacency_list.cpp @@ -87,17 +87,23 @@ struct test_directed_adjacency_list { ); } - void fully_connect_vertex(const lib_t::id_type first_id) { - for (const auto second_id : constants::vertex_id_view) - if (second_id != first_id) - add_edge(first_id, second_id); + void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + for (const auto second_id : constants::vertex_id_view) { + if (second_id == first_id and no_loops) + continue; + + add_edge(first_id, second_id); + } } - void initialize_full_graph() { + void init_complete_graph(const bool no_loops = true) { for (const auto first_id : constants::vertex_id_view) - fully_connect_vertex(first_id); + fully_connect_vertex(first_id, no_loops); - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + if (no_loops) + REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + else + REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements * constants::n_elements); } sut_type sut{constants::n_elements}; @@ -298,7 +304,7 @@ TEST_CASE_FIXTURE( test_directed_adjacency_list, "{in/out}_degree should return the number of edges incident {to/from} the given vertex" ) { - initialize_full_graph(); + init_complete_graph(); std::function deg_proj; @@ -330,7 +336,7 @@ TEST_CASE_FIXTURE( test_directed_adjacency_list, "degree should return the number of edges incident with the given vertex" ) { - initialize_full_graph(); + init_complete_graph(); const auto deg_proj = [this](const auto vertex_id) { return sut.degree(vertex_id); }; CHECK(std::ranges::all_of( @@ -349,11 +355,49 @@ TEST_CASE_FIXTURE( ); } +TEST_CASE_FIXTURE( + test_directed_adjacency_list, + "{in/out}_degree_map should return a map of numbers of edges incident {to/from} the " + "corresponding vertices" +) { + init_complete_graph(false); + const auto expected_deg = constants::n_elements; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); +} + +TEST_CASE_FIXTURE( + test_directed_adjacency_list, + "degree_map should return a map of the numbers of edges incident with the corresponding " + "vertices" +) { + init_complete_graph(false); + const auto expected_deg = constants::n_elements * constants::two; + + std::vector degree_map = sut.degree_map(); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); +} + TEST_CASE_FIXTURE( test_directed_adjacency_list, "remove_vertex should remove the given vertex and all edges incident with it" ) { - initialize_full_graph(); + init_complete_graph(); const auto& removed_vertex = vertices[constants::first_element_idx]; sut.remove_vertex(removed_vertex); @@ -392,18 +436,26 @@ struct test_undirected_adjacency_list { ); } - void fully_connect_vertex(const lib_t::id_type first_id) { - for (const auto second_id : constants::vertex_id_view) - if (second_id != first_id) - add_edge(first_id, second_id); + void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + for (const auto second_id : constants::vertex_id_view) { + if (second_id == first_id and no_loops) + continue; + + add_edge(first_id, second_id); + } } - void initialize_full_graph() { - for (const auto first_id : constants::vertex_id_view) - for (const auto second_id : std::views::iota(constants::vertex_id_1, first_id)) + void init_complete_graph(const bool no_loops = true) { + for (const auto first_id : constants::vertex_id_view) { + const auto bound = no_loops ? first_id : first_id + constants::one; + for (const auto second_id : std::views::iota(constants::vertex_id_1, bound)) add_edge(first_id, second_id); + } - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + if (no_loops) + REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + else + REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph + constants::n_elements); } sut_type sut{constants::n_elements}; @@ -648,7 +700,7 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "{in_/out_/}degree should return the number of edges incident {to/from} the given vertex" ) { - initialize_full_graph(); + init_complete_graph(); std::function deg_proj; @@ -680,11 +732,39 @@ TEST_CASE_FIXTURE( ); } +TEST_CASE_FIXTURE( + test_undirected_adjacency_list, + "{in_/out_/}degree_map should return a map of numbers of edges incident {to/from/with} the " + "corresponding vertices" +) { + init_complete_graph(false); + const auto expected_deg = constants::n_elements + 1; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + SUBCASE("degree") { + degree_map = sut.degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); +} + TEST_CASE_FIXTURE( test_undirected_adjacency_list, "remove_vertex should remove the given vertex and all edges incident with it" ) { - initialize_full_graph(); + init_complete_graph(); const auto& removed_vertex = vertices[constants::first_element_idx]; sut.remove_vertex(removed_vertex); diff --git a/tests/source/test_adjacency_matrix.cpp b/tests/source/test_adjacency_matrix.cpp index 6e00686..de7d15f 100644 --- a/tests/source/test_adjacency_matrix.cpp +++ b/tests/source/test_adjacency_matrix.cpp @@ -133,17 +133,23 @@ struct test_directed_adjacency_matrix { ); } - void fully_connect_vertex(const lib_t::id_type first_id) { - for (const auto second_id : constants::vertex_id_view) - if (second_id != first_id) - add_edge(first_id, second_id); + void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + for (const auto second_id : constants::vertex_id_view) { + if (second_id == first_id and no_loops) + continue; + + add_edge(first_id, second_id); + } } - void initialize_full_graph() { + void init_complete_graph(const bool no_loops = true) { for (const auto first_id : constants::vertex_id_view) - fully_connect_vertex(first_id); + fully_connect_vertex(first_id, no_loops); - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + if (no_loops) + REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + else + REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements * constants::n_elements); } sut_type sut{constants::n_elements}; @@ -294,7 +300,7 @@ TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "{in/out}_degree should return the number of edges incident {to/from} the given vertex" ) { - initialize_full_graph(); + init_complete_graph(); std::function deg_proj; @@ -326,7 +332,7 @@ TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "degree should return the number of edges incident with the given vertex" ) { - initialize_full_graph(); + init_complete_graph(); const auto deg_proj = [this](const auto vertex_id) { return sut.degree(vertex_id); }; CHECK(std::ranges::all_of( @@ -345,11 +351,49 @@ TEST_CASE_FIXTURE( ); } +TEST_CASE_FIXTURE( + test_directed_adjacency_matrix, + "{in/out}_degree_map should return a map of numbers of edges incident {to/from} the " + "corresponding vertices" +) { + init_complete_graph(false); + const auto expected_deg = constants::n_elements; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); +} + +TEST_CASE_FIXTURE( + test_directed_adjacency_matrix, + "degree_map should return a map of the numbers of edges incident with the corresponding " + "vertices" +) { + init_complete_graph(false); + const auto expected_deg = constants::n_elements * constants::two; + + std::vector degree_map = sut.degree_map(); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); +} + TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "remove_vertex should remove the given vertex and all edges incident with it" ) { - initialize_full_graph(); + init_complete_graph(); const auto& removed_vertex = vertices[constants::first_element_idx]; sut.remove_vertex(removed_vertex); @@ -388,18 +432,26 @@ struct test_undirected_adjacency_matrix { ); } - void fully_connect_vertex(const lib_t::id_type first_id) { - for (const auto second_id : constants::vertex_id_view) - if (second_id != first_id) - add_edge(first_id, second_id); + void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + for (const auto second_id : constants::vertex_id_view) { + if (second_id == first_id and no_loops) + continue; + + add_edge(first_id, second_id); + } } - void initialize_full_graph() { - for (const auto first_id : constants::vertex_id_view) - for (const auto second_id : std::views::iota(constants::vertex_id_1, first_id)) + void init_complete_graph(const bool no_loops = true) { + for (const auto first_id : constants::vertex_id_view) { + const auto bound = no_loops ? first_id : first_id + constants::one; + for (const auto second_id : std::views::iota(constants::vertex_id_1, bound)) add_edge(first_id, second_id); + } - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + if (no_loops) + REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + else + REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph + constants::n_elements); } sut_type sut{constants::n_elements}; @@ -587,7 +639,7 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "{in_/out_/}degree should return the number of edges incident {to/from/with} the given vertex" ) { - initialize_full_graph(); + init_complete_graph(); std::function deg_proj; @@ -619,11 +671,39 @@ TEST_CASE_FIXTURE( ); } +TEST_CASE_FIXTURE( + test_undirected_adjacency_matrix, + "{in_/out_/}degree_map should return a map of numbers of edges incident {to/from/with} the " + "corresponding vertices" +) { + init_complete_graph(false); + const auto expected_deg = constants::n_elements + 1; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + SUBCASE("degree") { + degree_map = sut.degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); +} + TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "remove_vertex should remove the given vertex and all edges incident with it" ) { - initialize_full_graph(); + init_complete_graph(); const auto& removed_vertex = vertices[constants::first_element_idx]; sut.remove_vertex(removed_vertex); diff --git a/tests/source/test_graph.cpp b/tests/source/test_graph.cpp index 6a0c779..784a09e 100644 --- a/tests/source/test_graph.cpp +++ b/tests/source/test_graph.cpp @@ -21,7 +21,7 @@ struct test_graph { template GraphType> requires(lib_tt::is_directed_v) - void initialize_full_graph(GraphType& graph) { + void init_complete_graph(GraphType& graph) { const auto vertices = graph.vertices(); for (const auto& first : vertices) for (const auto& second : vertices) @@ -37,7 +37,7 @@ struct test_graph { template GraphType> requires(lib_tt::is_undirected_v) - void initialize_full_graph(GraphType& graph) { + void init_complete_graph(GraphType& graph) { const auto vertices = graph.vertices(); for (const auto& first : vertices) for (const auto& second : vertices) @@ -257,7 +257,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("remove_vertex(vertex) should remove the given vertex and align ids of remaining " "vertices") { sut_type sut{constants::n_elements}; - fixture.initialize_full_graph(sut); + fixture.init_complete_graph(sut); sut.remove_vertex(constants::vertex_id_1); @@ -291,7 +291,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("remove_vertex(id) should remove the given vertex and align ids of remaining vertices" ) { sut_type sut{constants::n_elements}; - fixture.initialize_full_graph(sut); + fixture.init_complete_graph(sut); sut.remove_vertex(constants::vertex_id_1); @@ -322,7 +322,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp constexpr auto n_vertices = constants::n_elements + constants::one_element; sut_type sut{n_vertices}; - fixture.initialize_full_graph(sut); + fixture.init_complete_graph(sut); sut.remove_vertices_from( vertex_id_list{constants::vertex_id_1, constants::vertex_id_3, constants::vertex_id_1} @@ -345,7 +345,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp constexpr auto n_vertices = constants::n_elements + constants::one_element; sut_type sut{n_vertices}; - fixture.initialize_full_graph(sut); + fixture.init_complete_graph(sut); const auto& v1 = sut.get_vertex(constants::vertex_id_1); const auto& v3 = sut.get_vertex(constants::vertex_id_3);