Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
graph_traverse_generators_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 https://github.com/lrleon/Aleph-w
6
7 This file is part of Aleph-w library
8
9 Copyright (c) 2002-2026 Leandro Rabindranath Leon
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 SOFTWARE.
28*/
29
42#include <set>
43#include <stdexcept>
44#include <type_traits>
45#include <vector>
46
47#include <gtest/gtest.h>
48
50#include <graph-traverse.H>
51#include <tpl_graph.H>
52
53using namespace Aleph;
54
55namespace
56{
61
62template <class GT>
63std::vector<int> node_keys(const std::vector<typename GT::Node *> &nodes)
64{
65 std::vector<int> keys;
66 keys.reserve(nodes.size());
67 for (typename GT::Node *n : nodes)
68 keys.push_back(n->get_info());
69 return keys;
70}
71
72// Diamond-shaped connected graph:
73// 0 --- 1
74// | |
75// 2 --- 3 --- 4
76TestGraph build_diamond(std::vector<TestGraph::Node *> &nodes)
77{
78 TestGraph g;
79 for (int i = 0; i < 5; ++i)
80 nodes.push_back(g.insert_node(i));
81 g.insert_arc(nodes[0], nodes[1], 1.0);
82 g.insert_arc(nodes[0], nodes[2], 2.0);
83 g.insert_arc(nodes[1], nodes[3], 3.0);
84 g.insert_arc(nodes[2], nodes[3], 4.0);
85 g.insert_arc(nodes[3], nodes[4], 5.0);
86 return g;
87}
88} // namespace
89
90// Compile-time regression: traverse() is &-ref-qualified specifically so
91// that calling it on a temporary traverser — the dangerous one-liner
92// `Graph_Traverse_BFS_Generator<GT,Itor>(g).traverse(start)`, whose
93// coroutine frame would otherwise read a dangling `this` on first resume —
94// is a compile error instead of undefined behavior. `std::is_invocable_v`
95// (not a bare `requires{...}` expression: a ref-qualifier mismatch is a
96// hard error, not a SFINAE-friendly substitution failure, so it would
97// abort the whole translation unit instead of just making the requirement
98// unsatisfied) checks this without ever attempting the invalid call.
100static_assert(
101 not std::is_invocable_v<decltype(&BfsGen::traverse), BfsGen, TestGraph::Node *>,
102 "Graph_Traverse_Generator::traverse() must reject rvalue (temporary) receivers");
103static_assert(
104 std::is_invocable_v<decltype(&BfsGen::traverse), BfsGen &, TestGraph::Node *>,
105 "Graph_Traverse_Generator::traverse() must still accept lvalue receivers");
106
108{
109 TestGraph g;
110 TestGraph::Node *n = g.insert_node(42);
111
113 std::vector<int> seen;
114 for (TestGraph::Node *v : bfs.traverse(n))
115 seen.push_back(v->get_info());
116 EXPECT_EQ(seen, (std::vector<int>{42}));
117}
118
120{
121 TestGraph g;
122
125 {
126 for (TestGraph::Node *v : bfs.traverse(nullptr))
127 (void) v;
128 },
129 std::invalid_argument);
130}
131
133{
134 std::vector<TestGraph::Node *> nodes;
136
138 std::vector<int> seen;
139 for (TestGraph::Node *v : bfs.traverse(nodes[0]))
140 seen.push_back(v->get_info());
141
142 ASSERT_EQ(seen.size(), 5u);
143 EXPECT_EQ(std::set<int>(seen.begin(), seen.end()),
144 (std::set<int>{0, 1, 2, 3, 4}));
145 EXPECT_EQ(seen[0], 0); // start node is always first
146}
147
149{
150 std::vector<TestGraph::Node *> nodes;
152
154 std::vector<int> lazy_seen;
155 for (TestGraph::Node *v : lazy_bfs.traverse(nodes[0]))
156 lazy_seen.push_back(v->get_info());
157
159 std::vector<int> eager_seen;
160 eager_bfs(nodes[0], [&](TestGraph::Node *v) {
161 eager_seen.push_back(v->get_info());
162 return true;
163 });
164
166}
167
169{
170 std::vector<TestGraph::Node *> nodes;
172
174 std::vector<int> lazy_seen;
175 for (TestGraph::Node *v : lazy_dfs.traverse(nodes[0]))
176 lazy_seen.push_back(v->get_info());
177
179 std::vector<int> eager_seen;
180 eager_dfs(nodes[0], [&](TestGraph::Node *v) {
181 eager_seen.push_back(v->get_info());
182 return true;
183 });
184
186}
187
189{
190 // Component 1: 0 - 1 - 2 Component 2: 3 - 4
191 TestGraph g;
192 std::vector<TestGraph::Node *> nodes;
193 for (int i = 0; i < 5; ++i)
194 nodes.push_back(g.insert_node(i));
195 g.insert_arc(nodes[0], nodes[1], 1.0);
196 g.insert_arc(nodes[1], nodes[2], 2.0);
197 g.insert_arc(nodes[3], nodes[4], 3.0);
198
200 std::vector<int> seen;
201 for (TestGraph::Node *v : bfs.traverse(nodes[0]))
202 seen.push_back(v->get_info());
203
204 EXPECT_EQ(std::set<int>(seen.begin(), seen.end()), (std::set<int>{0, 1, 2}));
205}
206
208{
209 std::vector<TestGraph::Node *> nodes;
211
213 int visited = 0;
214 for (TestGraph::Node *v : bfs.traverse(nodes[0]))
215 {
216 (void) v;
217 if (++visited == 2)
218 break;
219 }
220 EXPECT_EQ(visited, 2); // stopped well before all 5 nodes were visited
221}
222
223// Regression test: a self-loop at the start node must not be yielded
224// twice, and must not prevent other reachable nodes from being visited.
225// The initial frontier-seeding loop used to unconditionally overwrite the
226// target node's state to Processing, which for a self-loop (tgt == start)
227// clobbered start's already-Processed state.
229{
230 TestGraph g;
233 g.insert_arc(a, a, 0.0); // self-loop at the start node
234 g.insert_arc(a, b, 1.0);
235
236 std::vector<int> bfs_seen;
238 for (TestGraph::Node *n : bfs.traverse(a))
239 bfs_seen.push_back(n->get_info());
240 EXPECT_EQ(bfs_seen, (std::vector<int>{1, 2}));
241
242 std::vector<int> dfs_seen;
244 for (TestGraph::Node *n : dfs.traverse(a))
245 dfs_seen.push_back(n->get_info());
246 EXPECT_EQ(dfs_seen, (std::vector<int>{1, 2}));
247}
248
249// Same regression, but the self-loop is on a node discovered mid-traversal
250// rather than on the start node itself (already handled correctly by the
251// main loop's own guard; kept as a sibling test for symmetry/coverage).
253{
254 TestGraph g;
258 g.insert_arc(a, b, 1.0);
259 g.insert_arc(b, b, 0.0); // self-loop at a non-start node
260 g.insert_arc(b, c, 1.0);
261
262 std::vector<int> seen;
264 for (TestGraph::Node *n : bfs.traverse(a))
265 seen.push_back(n->get_info());
266 EXPECT_EQ(seen, (std::vector<int>{1, 2, 3}));
267}
268
270{
271 // 0 - 1 - 2 - 3 - 0 (cycle)
272 TestGraph g;
273 std::vector<TestGraph::Node *> nodes;
274 for (int i = 0; i < 4; ++i)
275 nodes.push_back(g.insert_node(i));
276 g.insert_arc(nodes[0], nodes[1], 1.0);
277 g.insert_arc(nodes[1], nodes[2], 2.0);
278 g.insert_arc(nodes[2], nodes[3], 3.0);
279 g.insert_arc(nodes[3], nodes[0], 4.0);
280
282 std::vector<int> seen;
283 for (TestGraph::Node *v : dfs.traverse(nodes[0]))
284 seen.push_back(v->get_info());
285
286 ASSERT_EQ(seen.size(), 4u);
287 EXPECT_EQ(std::set<int>(seen.begin(), seen.end()), (std::set<int>{0, 1, 2, 3}));
288}
289
291{
292 // 0 -> 1 -> 2 ; note: no arc back, and no arc from 2 anywhere.
293 TestDigraph g;
294 std::vector<TestDigraph::Node *> nodes;
295 for (int i = 0; i < 3; ++i)
296 nodes.push_back(g.insert_node(i));
297 g.insert_arc(nodes[0], nodes[1], 1.0);
298 g.insert_arc(nodes[1], nodes[2], 2.0);
299
301 std::vector<int> from0;
302 for (TestDigraph::Node *v : bfs.traverse(nodes[0]))
303 from0.push_back(v->get_info());
304 EXPECT_EQ(from0, (std::vector<int>{0, 1, 2}));
305
306 // Starting from node 2 (a sink), only node 2 itself is reachable.
307 std::vector<int> from2;
308 for (TestDigraph::Node *v : bfs.traverse(nodes[2]))
309 from2.push_back(v->get_info());
310 EXPECT_EQ(from2, (std::vector<int>{2}));
311}
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3960
typename BaseGraph::Node Node
Definition graph-dry.H:3963
Lazily traverse a graph depth-first or breadth-first.
Aleph::Generator< typename GT::Node * > traverse(typename GT::Node *start) &
Lazily traverse the graph from start.
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:525
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:605
NodeInfo & get_info() noexcept
Return a modifiable reference to the data contained in the node.
Definition graph-dry.H:536
Traverse a graph depth-first or breadth-first and execute a visit function.
#define TEST(name)
Lazy (coroutine-based) graph traversal (DFS, BFS).
Graph traversal algorithms (DFS, BFS).
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:223
Filtered iterator of adjacent arcs of a node.
Definition tpl_graph.H:1120
int keys[]
Generic graph and digraph implementations.