spatial-graph 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1357 @@
1
+ #ifndef GSK_GRAPH_LITE_H
2
+ #define GSK_GRAPH_LITE_H
3
+
4
+ #include <functional>
5
+ #include <vector>
6
+ #include <list>
7
+ #include <map>
8
+ #include <unordered_map>
9
+ #include <set>
10
+ #include <unordered_set>
11
+ #include <algorithm>
12
+ #include <memory>
13
+ #include <type_traits>
14
+ #include <cassert>
15
+ #include <iostream>
16
+ #include <sstream>
17
+
18
+ // container spec
19
+ namespace graph_lite {
20
+ // ContainerGen, supposed to be container of neighbors
21
+ enum class Container {
22
+ VEC, LIST, SET, UNORDERED_SET, MULTISET, UNORDERED_MULTISET
23
+ };
24
+
25
+ // self loop permission
26
+ enum class SelfLoop {
27
+ ALLOWED, DISALLOWED
28
+ };
29
+
30
+ // multi-edge permission
31
+ enum class MultiEdge {
32
+ ALLOWED, DISALLOWED
33
+ };
34
+
35
+ // directed or undirected graph
36
+ enum class EdgeDirection {
37
+ DIRECTED, UNDIRECTED
38
+ };
39
+
40
+ // map for adj list
41
+ enum class Map {
42
+ MAP, UNORDERED_MAP
43
+ };
44
+
45
+ // Logging permission
46
+ enum class Logging {
47
+ ALLOWED, DISALLOWED
48
+ };
49
+
50
+ }
51
+
52
+ // type manipulation
53
+ namespace graph_lite::detail {
54
+ template <typename T>
55
+ constexpr bool is_vector_v = std::is_same_v<T, std::vector<typename T::value_type, typename T::allocator_type>>;
56
+
57
+ template<typename T>
58
+ constexpr bool is_list_v = std::is_same_v<T, std::vector<typename T::value_type, typename T::allocator_type>>;
59
+
60
+ // determine if type is map or unordered_map
61
+ template <typename T, typename U = void>
62
+ struct is_map: std::false_type {};
63
+ template<typename T>
64
+ struct is_map<T, std::void_t<typename T::key_type,
65
+ typename T::mapped_type,
66
+ typename T::key_compare,
67
+ typename T::allocator_type>> {
68
+ static constexpr bool value = std::is_same_v<T, std::map<typename T::key_type,
69
+ typename T::mapped_type,
70
+ typename T::key_compare,
71
+ typename T::allocator_type>>;
72
+ };
73
+ template<typename T>
74
+ constexpr bool is_map_v = is_map<T>::value;
75
+
76
+ template <typename T, typename U = void>
77
+ struct is_unordered_map: std::false_type {};
78
+ template<typename T>
79
+ struct is_unordered_map<T, std::void_t<typename T::key_type,
80
+ typename T::mapped_type,
81
+ typename T::hasher,
82
+ typename T::key_equal,
83
+ typename T::allocator_type>> {
84
+ static constexpr bool value = std::is_same_v<T, std::unordered_map<typename T::key_type,
85
+ typename T::mapped_type,
86
+ typename T::hasher,
87
+ typename T::key_equal,
88
+ typename T::allocator_type>>;
89
+ };
90
+ template<typename T>
91
+ constexpr bool is_unordered_map_v = is_unordered_map<T>::value;
92
+ template<typename T>
93
+ constexpr bool is_either_map_v = is_map_v<T> or is_unordered_map_v<T>;
94
+
95
+ // CREDIT: https://stackoverflow.com/questions/765148/how-to-remove-constness-of-const-iterator
96
+ template <typename ContainerType, typename ConstIterator>
97
+ typename ContainerType::iterator const_iter_to_iter(ContainerType& c, ConstIterator it) {
98
+ return c.erase(it, it);
99
+ }
100
+ // shorthand for turning const T& into T
101
+ template<typename T>
102
+ struct remove_cv_ref {
103
+ using type = std::remove_cv_t<std::remove_reference_t<T>>;
104
+ };
105
+ template<typename T>
106
+ using remove_cv_ref_t = typename remove_cv_ref<T>::type;
107
+ // END OF shorthand for turning const T& into T
108
+
109
+ // test if lhs==rhs and lhs!=rhs work
110
+ template<typename T, typename = std::void_t<>>
111
+ struct is_eq_comparable : std::false_type {};
112
+ template<typename T>
113
+ struct is_eq_comparable<T, std::void_t<decltype(std::declval<T>() == std::declval<T>())>> : std::true_type {};
114
+ template<typename T>
115
+ constexpr bool is_eq_comparable_v = is_eq_comparable<T>::value;
116
+
117
+ // END OF test if lhs==rhs work
118
+
119
+ // test comparability; see if a < b works
120
+ template<typename T, typename = std::void_t<>>
121
+ struct is_comparable : std::false_type {};
122
+ template<typename T>
123
+ struct is_comparable<T, std::void_t<decltype(std::declval<T>() < std::declval<T>())>> : std::true_type {};
124
+ template<typename T>
125
+ constexpr bool is_comparable_v = is_comparable<T>::value;
126
+ // END OF test comparability
127
+
128
+ // test streamability
129
+ template<typename T, typename = std::void_t<>>
130
+ struct is_streamable : std::false_type {};
131
+ template<typename T>
132
+ struct is_streamable<T, std::void_t<decltype(std::declval<std::ostream&>()<<std::declval<T>())>> : std::true_type {};
133
+ template<typename T>
134
+ constexpr bool is_streamable_v = is_streamable<T>::value;
135
+ // END OF test streamability
136
+
137
+ // test hashability
138
+ template<typename T, typename = std::void_t<>>
139
+ struct is_std_hashable : std::false_type {};
140
+ template<typename T>
141
+ struct is_std_hashable<T, std::void_t<decltype(std::declval<std::hash<T>>()(std::declval<T>()))>> : std::true_type {};
142
+ template<typename T>
143
+ constexpr bool is_std_hashable_v = is_std_hashable<T>::value;
144
+ // END OF test hashability
145
+
146
+ template <Container C>
147
+ struct MultiEdgeTraits {};
148
+ template<> struct MultiEdgeTraits<Container::VEC> { static constexpr MultiEdge value = MultiEdge::ALLOWED; };
149
+ template<> struct MultiEdgeTraits<Container::LIST> { static constexpr MultiEdge value = MultiEdge::ALLOWED; };
150
+ template<> struct MultiEdgeTraits<Container::MULTISET> { static constexpr MultiEdge value = MultiEdge::ALLOWED; };
151
+ template<> struct MultiEdgeTraits<Container::UNORDERED_MULTISET> { static constexpr MultiEdge value = MultiEdge::ALLOWED; };
152
+ template<> struct MultiEdgeTraits<Container::SET> { static constexpr MultiEdge value = MultiEdge::DISALLOWED; };
153
+ template<> struct MultiEdgeTraits<Container::UNORDERED_SET> { static constexpr MultiEdge value = MultiEdge::DISALLOWED; };
154
+
155
+ template<typename T>
156
+ struct OutIn {
157
+ T out;
158
+ T in;
159
+ };
160
+ }
161
+
162
+ // operation on containers
163
+ namespace graph_lite::detail::container {
164
+ template<typename ContainerType, typename ValueType,
165
+ typename = std::enable_if_t<std::is_convertible_v<remove_cv_ref_t<ValueType>, typename ContainerType::value_type>>>
166
+ auto insert(ContainerType& c, ValueType&& v) {
167
+ return c.insert(c.cend(), std::forward<ValueType>(v));
168
+ }
169
+
170
+ // find the first occurrence of a value
171
+ // using different find for efficiency; std::find is always linear
172
+ template<typename ContainerType, typename ValueType, std::enable_if_t<!is_vector_v<ContainerType> and !is_list_v<ContainerType>, bool> = true>
173
+ auto find(ContainerType& c, const ValueType& v) {
174
+ return c.find(v);
175
+ }
176
+
177
+ template<typename ValueType, typename T>
178
+ auto find(std::vector<std::pair<ValueType, T>>& c, const ValueType& v) {
179
+ return std::find_if(c.begin(), c.end(), [&v](const auto& p){ return p.first==v; });
180
+ }
181
+ template<typename ValueType, typename T>
182
+ auto find(std::list<std::pair<ValueType, T>>& c, const ValueType& v) {
183
+ return std::find_if(c.begin(), c.end(), [&v](const auto& p){ return p.first==v; });
184
+ }
185
+
186
+ template<typename ValueType, typename T>
187
+ auto find(const std::vector<std::pair<ValueType, T>>& c, const ValueType& v) {
188
+ return std::find_if(c.begin(), c.end(), [&v](const auto& p){ return p.first==v; });
189
+ }
190
+ template<typename ValueType, typename T>
191
+ auto find(const std::list<std::pair<ValueType, T>>& c, const ValueType& v) {
192
+ return std::find_if(c.begin(), c.end(), [&v](const auto& p){ return p.first==v; });
193
+ }
194
+
195
+ template<typename ValueType, typename FullValueType,
196
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, bool> = true>
197
+ auto find(std::vector<FullValueType>& c, const ValueType& v) { return std::find(c.begin(), c.end(), v); }
198
+
199
+ template<typename ValueType, typename FullValueType,
200
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, bool> = true>
201
+ auto find(std::list<FullValueType>& c, const ValueType& v) { return std::find(c.begin(), c.end(), v); }
202
+
203
+ template<typename ValueType, typename FullValueType,
204
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, bool> = true>
205
+ auto find(const std::vector<FullValueType>& c, const ValueType& v) { return std::find(c.begin(), c.end(), v); }
206
+
207
+ template<typename ValueType, typename FullValueType,
208
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, bool> = true>
209
+ auto find(const std::list<FullValueType>& c, const ValueType& v) { return std::find(c.begin(), c.end(), v); }
210
+ // END OF find the first occurrence of a value
211
+
212
+ // count the occurrence of a value
213
+ template<typename ContainerType, typename ValueType>
214
+ std::enable_if_t<!is_vector_v<ContainerType> and !is_list_v<ContainerType>, int>
215
+ count(const ContainerType& c, const ValueType& v) {
216
+ return c.count(v);
217
+ }
218
+
219
+ template<typename ValueType, typename T>
220
+ int count(const std::vector<std::pair<ValueType, T>>& c, const ValueType& v) {
221
+ return std::count_if(c.begin(), c.end(), [&v](const auto& e){ return e.first==v; });
222
+ }
223
+
224
+ template<typename ValueType, typename T>
225
+ int count(const std::list<std::pair<ValueType, T>>& c, const ValueType& v) {
226
+ return std::count_if(c.begin(), c.end(), [&v](const auto& e){ return e.first==v; });
227
+ }
228
+
229
+ template<typename ValueType, typename FullValueType>
230
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, int>
231
+ count(const std::vector<FullValueType>& c, const ValueType& v) { return std::count(c.begin(), c.end(), v); }
232
+
233
+ template<typename ValueType, typename FullValueType>
234
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, int>
235
+ count(const std::list<FullValueType>& c, const ValueType& v) { return std::count(c.begin(), c.end(), v); }
236
+ // END OF count the occurrence of a value
237
+
238
+ // remove all by value
239
+ // erase always erases all with value v; again, std::remove is linear
240
+ template<typename ContainerType, typename ValueType>
241
+ std::enable_if_t<!is_vector_v<ContainerType> and !is_list_v<ContainerType>, int>
242
+ erase_all(ContainerType& c, const ValueType& v) {
243
+ static_assert(std::is_same_v<ContainerType, std::remove_const_t<ContainerType>>);
244
+ return c.erase(v);
245
+ }
246
+
247
+ template<typename ValueType, typename FullValueType>
248
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, int>
249
+ erase_all(std::vector<FullValueType>& c, const ValueType& v) {
250
+ size_t old_size = c.size();
251
+ c.erase(std::remove(c.begin(), c.end(), v), c.end());
252
+ return old_size - c.size();
253
+ }
254
+
255
+ template<typename ValueType, typename T>
256
+ int erase_all(std::vector<std::pair<ValueType, T>>& c, const ValueType& v) {
257
+ size_t old_size = c.size();
258
+ c.erase(std::remove_if(c.begin(), c.end(), [&v](const auto& p){ return p.first==v; }), c.end());
259
+ return old_size - c.size();
260
+ }
261
+
262
+ template<typename ValueType, typename FullValueType>
263
+ std::enable_if_t<std::is_convertible_v<ValueType, FullValueType>, int>
264
+ erase_all(std::list<FullValueType>& c, const ValueType& v) {
265
+ size_t old_size = c.size();
266
+ c.remove(v);
267
+ return old_size - c.size();
268
+ }
269
+
270
+ template<typename ValueType, typename T>
271
+ int erase_all(std::list<std::pair<ValueType, T>>& c, const ValueType& v) {
272
+ size_t old_size = c.size();
273
+ c.remove_if([&v](const auto& p){ return p.first==v; });
274
+ return old_size - c.size();
275
+ }
276
+ // END OF remove all by value
277
+
278
+ // remove one by value or position; remove AT MOST 1 element
279
+ template<typename ContainerType, typename ValueType>
280
+ int erase_one(ContainerType& c, const ValueType& v) {
281
+ if constexpr(std::is_same_v<remove_cv_ref_t<ValueType>, typename ContainerType::iterator>
282
+ or std::is_same_v<remove_cv_ref_t<ValueType>, typename ContainerType::const_iterator>) { // remove by pos
283
+ c.erase(v);
284
+ return 1;
285
+ } else { // remove by value
286
+ auto pos = find(c, v);
287
+ if (pos==c.end()) {
288
+ return 0;
289
+ }
290
+ c.erase(pos);
291
+ return 1;
292
+ }
293
+ }
294
+ // END OF remove one
295
+ }
296
+
297
+ // mixin base classes of Graph
298
+ namespace graph_lite::detail {
299
+ // EdgePropListBase provides optional member variable edge_prop_list
300
+ template<typename EPT>
301
+ struct EdgePropListBase {
302
+ protected:
303
+ std::list<EPT> edge_prop_list;
304
+ };
305
+ template<>
306
+ struct EdgePropListBase<void> {}; // empty base optimization if edge prop is not needed
307
+
308
+ // EdgeDirectionBase provides different API for directed and undirected graphs
309
+ template<typename GType, EdgeDirection direction>
310
+ struct EdgeDirectionBase {};
311
+
312
+ template<typename GType>
313
+ struct EdgeDirectionBase<GType, EdgeDirection::UNDIRECTED> { // undirected graph only
314
+ template<typename T>
315
+ auto neighbors(const T& node_iv) const {
316
+ const auto* self = static_cast<const GType*>(this);
317
+ return self->template get_neighbors_helper<true>(node_iv);
318
+ }
319
+ template<typename T>
320
+ auto neighbors(const T& node_iv) {
321
+ auto* self = static_cast<GType*>(this);
322
+ return self->template get_neighbors_helper<true>(node_iv);
323
+ }
324
+ // returns the number of neighbors of a node
325
+ template<typename T>
326
+ int count_neighbors(const T& node_iv) const {
327
+ const auto* self = static_cast<const GType*>(this);
328
+ return self->template count_neighbors_helper<true>(node_iv);
329
+ }
330
+
331
+ // find a node with value tgt within the neighborhood of src
332
+ template<typename U, typename V>
333
+ auto find_neighbor(const U& src_iv, V&& tgt_identifier) const {
334
+ const auto* self = static_cast<const GType*>(this);
335
+ return self->template find_neighbor_helper<true>(src_iv, std::forward<V>(tgt_identifier));
336
+ }
337
+ template<typename U, typename V>
338
+ auto find_neighbor(const U& src_iv, V&& tgt_identifier) { // non-const overload
339
+ auto* self = static_cast<GType*>(this);
340
+ return self->template find_neighbor_helper<true>(src_iv, std::forward<V>(tgt_identifier));
341
+ }
342
+ };
343
+
344
+ template<typename GType>
345
+ struct EdgeDirectionBase<GType, EdgeDirection::DIRECTED> { // directed graph only
346
+ template<typename T>
347
+ auto out_neighbors(const T& node_iv) const {
348
+ const auto* self = static_cast<const GType*>(this);
349
+ return self->template get_neighbors_helper<true>(node_iv);
350
+ }
351
+ template<typename T>
352
+ auto out_neighbors(const T& node_iv) {
353
+ auto* self = static_cast<GType*>(this);
354
+ return self->template get_neighbors_helper<true>(node_iv);
355
+ }
356
+
357
+ template<typename T>
358
+ auto in_neighbors(const T& node_iv) const {
359
+ const auto* self = static_cast<const GType*>(this);
360
+ return self->template get_neighbors_helper<false>(node_iv);
361
+ }
362
+ template<typename T>
363
+ auto in_neighbors(const T& node_iv) {
364
+ auto* self = static_cast<GType*>(this);
365
+ return self->template get_neighbors_helper<false>(node_iv);
366
+ }
367
+
368
+ // returns the number of out neighbors of a node
369
+ template<typename T>
370
+ int count_out_neighbors(const T& node_iv) const {
371
+ const auto* self = static_cast<const GType*>(this);
372
+ return self->template count_neighbors_helper<true>(node_iv);
373
+ }
374
+
375
+ // returns the number of out neighbors of a node
376
+ template<typename T>
377
+ int count_in_neighbors(const T& node_iv) const {
378
+ const auto* self = static_cast<const GType*>(this);
379
+ return self->template count_neighbors_helper<false>(node_iv);
380
+ }
381
+
382
+ // find a node with value tgt within the out-neighborhood of src
383
+ template<typename U, typename V>
384
+ auto find_out_neighbor(const U& src_iv, V&& tgt_identifier) const {
385
+ const auto* self = static_cast<const GType*>(this);
386
+ return self->template find_neighbor_helper<true>(src_iv, std::forward<V>(tgt_identifier));
387
+ }
388
+ template<typename U, typename V>
389
+ auto find_out_neighbor(const U& src_iv, V&& tgt_identifier) { // non-const overload
390
+ auto* self = static_cast<GType*>(this);
391
+ return self->template find_neighbor_helper<true>(src_iv, std::forward<V>(tgt_identifier));
392
+ }
393
+
394
+ // find a node with value tgt within the in-neighborhood of src
395
+ template<typename U, typename V>
396
+ auto find_in_neighbor(const U& src_iv, V&& tgt_identifier) const {
397
+ const auto* self = static_cast<const GType*>(this);
398
+ return self->template find_neighbor_helper<false>(src_iv, std::forward<V>(tgt_identifier));
399
+ }
400
+ template<typename U, typename V>
401
+ auto find_in_neighbor(const U& src_iv, V&& tgt_identifier) { // non-const overload
402
+ auto* self = static_cast<GType*>(this);
403
+ return self->template find_neighbor_helper<false>(src_iv, std::forward<V>(tgt_identifier));
404
+ }
405
+ };
406
+
407
+ // NodePropGraphBase provides different API depending on whether node prop is needed
408
+ template<typename GType, typename NodePropType>
409
+ struct NodePropGraphBase {
410
+ public: // this can seg fault if node_identifier is invalid...
411
+ template<typename T>
412
+ const NodePropType& node_prop(const T& node_iv) const {
413
+ const auto* self = static_cast<const GType*>(this);
414
+ auto pos = self->find_by_iter_or_by_value(node_iv);
415
+ if (pos == self->adj_list.end()) {
416
+ if constexpr(GType::template is_iterator<T>()) {
417
+ throw std::out_of_range("Requested node does not exist");
418
+ } else {
419
+ static_assert(GType::template can_construct_node<T>);
420
+ std::ostringstream msg_stream;
421
+ msg_stream << "Node " << typename GType::node_type{node_iv} << " does not exist";
422
+ throw std::out_of_range(msg_stream.str());
423
+ }
424
+ }
425
+ return pos->second.prop;
426
+ }
427
+ template<typename T>
428
+ NodePropType& node_prop(const T& node_iv) {
429
+ return const_cast<NodePropType&>(static_cast<const NodePropGraphBase*>(this)->node_prop(node_iv));
430
+ }
431
+
432
+ template<typename NT, typename ...NPT>
433
+ int add_node_with_prop(NT&& new_node, NPT&&... prop) noexcept {
434
+ static_assert(std::is_same_v<remove_cv_ref_t<NT>, typename GType::node_type>);
435
+ static_assert(std::is_constructible_v<NodePropType, NPT...>);
436
+ auto* self = static_cast<GType*>(this);
437
+ if (!self->adj_list.count(new_node)) { // insert if not already existing
438
+ // this should invoke(in-place) the constructor of PropNode
439
+ self->adj_list.emplace(std::piecewise_construct,
440
+ std::forward_as_tuple(std::forward<NT>(new_node)),
441
+ std::forward_as_tuple(std::forward<NPT>(prop)...));
442
+ return 1;
443
+ }
444
+ return 0; // a no-op if already existing
445
+ }
446
+ };
447
+ template<typename GType>
448
+ struct NodePropGraphBase<GType, void> { // no node prop
449
+ template<typename T>
450
+ int add_nodes(T&& new_node) noexcept { // base case
451
+ static_assert(std::is_same_v<detail::remove_cv_ref_t<T>, typename GType::node_type>);
452
+ auto* self = static_cast<GType*>(this);
453
+ int old_size = self->adj_list.size();
454
+ self->adj_list[std::forward<T>(new_node)]; // insertion here; no-op if already existing
455
+ return self->adj_list.size() - old_size;
456
+ }
457
+ template<typename T, typename... Args>
458
+ int add_nodes(T&& new_node, Args&&... args) noexcept {
459
+ static_assert(std::is_same_v<detail::remove_cv_ref_t<T>, typename GType::node_type>);
460
+ return add_nodes(std::forward<T>(new_node)) + add_nodes(std::forward<Args>(args)...);
461
+ }
462
+ };
463
+
464
+ // EdgePropGraphBase provides method "add_edge" when edge prop is not needed;
465
+ // and "add_edge_with_prop" when edge prop is needed
466
+ template<typename GType, typename EdgePropType>
467
+ struct EdgePropGraphBase {
468
+ // extract edge property given node pair
469
+ template<typename U, typename V>
470
+ EdgePropType& edge_prop(U&& source_iv, V&& target_iv) {
471
+ return const_cast<EdgePropType&>(
472
+ static_cast<const EdgePropGraphBase*>(this)->template edge_prop<U, V>(
473
+ std::forward<U>(source_iv),
474
+ std::forward<V>(target_iv)
475
+ )
476
+ );
477
+ }
478
+
479
+ template<typename U, typename V>
480
+ const EdgePropType& edge_prop(U&& source_iv, V&& target_iv) const {
481
+ const auto* self = static_cast<const GType*>(this);
482
+ auto find_neighbor_iv = [self, &source_iv, &target_iv]() {
483
+ auto&& tgt_val = self->unwrap_by_iter_or_by_value(std::forward<V>(target_iv));
484
+ if constexpr(GType::DIRECTION==EdgeDirection::UNDIRECTED) {
485
+ return self->find_neighbor(source_iv, std::forward<decltype(tgt_val)>(tgt_val));
486
+ } else {
487
+ return self->find_out_neighbor(source_iv, std::forward<decltype(tgt_val)>(tgt_val));
488
+ }
489
+ };
490
+ auto find_neighbor_vi = [self, &source_iv, &target_iv]() {
491
+ auto&& src_val = self->unwrap_by_iter_or_by_value(std::forward<U>(source_iv));
492
+ if constexpr(GType::DIRECTION==EdgeDirection::UNDIRECTED) {
493
+ return self->find_neighbor(target_iv, std::forward<decltype(src_val)>(src_val));
494
+ } else {
495
+ return self->find_in_neighbor(target_iv, std::forward<decltype(src_val)>(src_val));
496
+ }
497
+ };
498
+ // this ensures that no adj list lookup will happen if either is an iterator
499
+ if constexpr(GType::template is_iterator<U>()) { // I & V or I & I
500
+ auto [found, pos] = find_neighbor_iv();
501
+ if (not found) {
502
+ std::ostringstream msg_stream;
503
+ msg_stream << "Edge (" << typename GType::node_type{source_iv} << ", " << typename GType::node_type{target_iv} << ") does not exist";
504
+ throw std::out_of_range(msg_stream.str());
505
+ }
506
+ return pos->second.prop();
507
+ } else {
508
+ // V & I or V & V
509
+ auto [found, pos] = find_neighbor_vi();
510
+ if (not found) {
511
+ std::ostringstream msg_stream;
512
+ msg_stream << "Edge (" << typename GType::node_type{source_iv} << ", " << typename GType::node_type{target_iv} << ") does not exist";
513
+ throw std::out_of_range(msg_stream.str());
514
+ }
515
+ return pos->second.prop();
516
+ }
517
+ }
518
+
519
+ template<typename U, typename V, typename ...EPT>
520
+ int add_edge_with_prop(U&& source_iv, V&& target_iv, EPT&&... prop) noexcept {
521
+ static_assert(std::is_constructible_v<EdgePropType, EPT...>);
522
+ auto* self = static_cast<GType*>(this);
523
+ auto src_pos = self->find_by_iter_or_by_value(source_iv);
524
+ auto tgt_pos = self->find_by_iter_or_by_value(target_iv);
525
+ if(src_pos==self->adj_list.end() or tgt_pos==self->adj_list.end()) {
526
+ if constexpr(GType::LOGGING == Logging::ALLOWED){
527
+ if (src_pos==self->adj_list.end()) {
528
+ self->print_by_iter_or_by_value(std::cerr << "(add_edge) edge involves non-existent source", source_iv) << "\n";
529
+ }
530
+ if (tgt_pos==self->adj_list.end()) {
531
+ self->print_by_iter_or_by_value(std::cerr << "(add_edge) edge involves non-existent target", target_iv) << "\n";
532
+ }
533
+ }
534
+ return 0;
535
+ }
536
+ const typename GType::node_type& src_full = src_pos->first;
537
+ const typename GType::node_type& tgt_full = tgt_pos->first; // flesh out src and tgt
538
+ if(self->check_edge_dup(src_pos, src_full, tgt_full)) { return 0; }
539
+ if(self->check_self_loop(src_pos, tgt_pos, src_full)) { return 0; }
540
+ auto prop_pos = self->insert_edge_prop(std::forward<EPT>(prop)...);
541
+ container::insert(self->get_out_neighbors(src_pos), std::make_pair(tgt_full, prop_pos));
542
+ if (src_pos!=tgt_pos or GType::DIRECTION==EdgeDirection::DIRECTED) {
543
+ container::insert(self->get_in_neighbors(tgt_pos), std::make_pair(src_full, prop_pos));
544
+ }
545
+ ++self->num_of_edges;
546
+ return 1;
547
+ }
548
+ };
549
+ template<typename GType>
550
+ struct EdgePropGraphBase<GType, void> {
551
+ template<typename U, typename V>
552
+ int add_edge(U&& source_iv, V&& target_iv) noexcept {
553
+ auto* self = static_cast<GType*>(this);
554
+ auto src_pos = self->find_by_iter_or_by_value(source_iv);
555
+ auto tgt_pos = self->find_by_iter_or_by_value(target_iv);
556
+ if(src_pos==self->adj_list.end() or tgt_pos==self->adj_list.end()) {
557
+ if constexpr(GType::LOGGING == Logging::ALLOWED){
558
+ if (src_pos==self->adj_list.end()) {
559
+ self->print_by_iter_or_by_value(std::cerr << "(add_edge) edge involves non-existent source", source_iv) << "\n";
560
+ }
561
+ if (tgt_pos==self->adj_list.end()) {
562
+ self->print_by_iter_or_by_value(std::cerr << "(add_edge) edge involves non-existent target", target_iv) << "\n";
563
+ }
564
+ }
565
+ return 0;
566
+ }
567
+ const typename GType::node_type& src_full = src_pos->first;
568
+ const typename GType::node_type& tgt_full = tgt_pos->first; // flesh out src and tgt
569
+ if(self->check_edge_dup(src_pos, src_full, tgt_full)) { return 0; }
570
+ if(self->check_self_loop(src_pos, tgt_pos, src_full)) { return 0; }
571
+ container::insert(self->get_out_neighbors(src_pos), tgt_full);
572
+ if (src_pos!=tgt_pos or GType::DIRECTION==EdgeDirection::DIRECTED) {
573
+ container::insert(self->get_in_neighbors(tgt_pos), src_full);
574
+ }
575
+ ++self->num_of_edges;
576
+ return 1;
577
+ }
578
+ };
579
+ }
580
+
581
+ // Graph class
582
+ namespace graph_lite {
583
+ template<typename NodeType=int, typename NodePropType=void, typename EdgePropType=void,
584
+ EdgeDirection direction=EdgeDirection::UNDIRECTED,
585
+ MultiEdge multi_edge=MultiEdge::DISALLOWED,
586
+ SelfLoop self_loop=SelfLoop::DISALLOWED,
587
+ Map adj_list_spec=Map::UNORDERED_MAP,
588
+ Container neighbors_container_spec=Container::UNORDERED_SET,
589
+ Logging logging=Logging::DISALLOWED>
590
+ class Graph: private detail::EdgePropListBase<EdgePropType>,
591
+ public detail::EdgeDirectionBase<Graph<NodeType, NodePropType, EdgePropType,
592
+ direction, multi_edge, self_loop, adj_list_spec, neighbors_container_spec, logging>, direction>,
593
+ public detail::NodePropGraphBase<Graph<NodeType, NodePropType, EdgePropType,
594
+ direction, multi_edge, self_loop, adj_list_spec, neighbors_container_spec, logging>, NodePropType>,
595
+ public detail::EdgePropGraphBase<Graph<NodeType, NodePropType, EdgePropType,
596
+ direction, multi_edge, self_loop, adj_list_spec, neighbors_container_spec, logging>, EdgePropType> {
597
+ // friend class with CRTP base classes
598
+ friend detail::EdgeDirectionBase<Graph<NodeType, NodePropType, EdgePropType,
599
+ direction, multi_edge, self_loop, adj_list_spec, neighbors_container_spec, logging>, direction>;
600
+ friend detail::NodePropGraphBase<Graph<NodeType, NodePropType, EdgePropType,
601
+ direction, multi_edge, self_loop, adj_list_spec, neighbors_container_spec, logging>, NodePropType>;
602
+ friend detail::EdgePropGraphBase<Graph<NodeType, NodePropType, EdgePropType,
603
+ direction, multi_edge, self_loop, adj_list_spec, neighbors_container_spec, logging>, EdgePropType>;
604
+ static_assert(std::is_same_v<NodeType, std::remove_reference_t<NodeType>>, "NodeType should not be a reference");
605
+ static_assert(std::is_same_v<NodeType, std::remove_cv_t<NodeType>>, "NodeType should not be cv-qualified");
606
+ static_assert(std::is_same_v<NodePropType, std::remove_reference_t<NodePropType>>
607
+ and std::is_same_v<EdgePropType, std::remove_reference_t<EdgePropType>>,
608
+ "Property types should not be references");
609
+ static_assert(std::is_same_v<NodePropType, std::remove_cv_t<NodePropType>>
610
+ and std::is_same_v<EdgePropType, std::remove_cv_t<EdgePropType>>,
611
+ "Property types should not be cv-qualified");
612
+ static_assert(detail::is_eq_comparable_v<NodeType>, "NodeType does not support ==; implement operator==");
613
+ static_assert(detail::is_streamable_v<NodeType>, "NodeType is not streamable; implement operator<<");
614
+ static_assert(not ((neighbors_container_spec == Container::UNORDERED_SET
615
+ or neighbors_container_spec == Container::UNORDERED_MULTISET
616
+ or adj_list_spec == Map::UNORDERED_MAP)
617
+ and !detail::is_std_hashable_v<NodeType>), "NodeType is not hashable");
618
+ static_assert(not ((neighbors_container_spec == Container::SET
619
+ or neighbors_container_spec == Container::MULTISET
620
+ or adj_list_spec == Map::MAP)
621
+ and !detail::is_comparable_v<NodeType>), "NodeType does not support operator <");
622
+ static_assert(not (detail::MultiEdgeTraits<neighbors_container_spec>::value == MultiEdge::DISALLOWED
623
+ and multi_edge == MultiEdge::ALLOWED), "node container does not support multi-edge");
624
+ static_assert(not ((neighbors_container_spec == Container::MULTISET or neighbors_container_spec == Container::UNORDERED_MULTISET)
625
+ and multi_edge == MultiEdge::DISALLOWED), "disallowing multi-edge yet still using multi-set; use set/unordered_set instead");
626
+ public: // exposed types and constants
627
+ using node_type = NodeType;
628
+ using node_prop_type = NodePropType;
629
+ using edge_prop_type = EdgePropType;
630
+ static constexpr EdgeDirection DIRECTION = direction;
631
+ static constexpr MultiEdge MULTI_EDGE = multi_edge;
632
+ static constexpr SelfLoop SELF_LOOP = self_loop;
633
+ static constexpr Map ADJ_LIST_SPEC = adj_list_spec;
634
+ static constexpr Container NEIGHBORS_CONTAINER_SPEC = neighbors_container_spec;
635
+ static constexpr Logging LOGGING = logging;
636
+ private: // type gymnastics
637
+ // handle neighbors that may have property
638
+ // PairIterator is useful only when (1) the container is VEC or LIST and (2) edge prop is needed
639
+ template<typename ContainerType>
640
+ class PairIterator { // always non const, since the build-in iter can handle const
641
+ friend class Graph;
642
+ private:
643
+ using It = typename ContainerType::iterator;
644
+ using ConstIt = typename ContainerType::const_iterator;
645
+ It it;
646
+ using VT = typename It::value_type;
647
+ using FirstType = typename VT::first_type;
648
+ using SecondType = typename VT::second_type;
649
+ public: // mimic the iter of a std::map
650
+ using difference_type = typename It::difference_type;
651
+ using value_type = std::pair<const FirstType, SecondType>;
652
+ using reference = std::pair<const FirstType&, SecondType&>;
653
+ using pointer = std::pair<const FirstType, SecondType>*;
654
+ using iterator_category = std::bidirectional_iterator_tag;
655
+ PairIterator()=default;
656
+ // can be implicitly converted FROM a non-const iter
657
+ PairIterator(const It& it): it{it} {}
658
+ // can be implicitly converted TO a const iter
659
+ // relying on the imp conv of the underlying iter
660
+ operator ConstIt() { return it; }
661
+
662
+ friend bool operator==(const PairIterator& lhs, const PairIterator& rhs) {
663
+ return lhs.it==rhs.it;
664
+ }
665
+ friend bool operator!=(const PairIterator& lhs, const PairIterator& rhs) {
666
+ return lhs.it!=rhs.it;
667
+ }
668
+ friend bool operator==(const PairIterator& lhs, const ConstIt& rhs) {
669
+ return lhs.it==rhs;
670
+ }
671
+ friend bool operator!=(const PairIterator& lhs, const ConstIt& rhs) {
672
+ return lhs.it!=rhs;
673
+ }
674
+ // symmetry
675
+ friend bool operator==(const ConstIt& lhs, const PairIterator& rhs) {
676
+ return rhs==lhs;
677
+ }
678
+ friend bool operator!=(const ConstIt& lhs, const PairIterator& rhs) {
679
+ return rhs!=lhs;
680
+ }
681
+
682
+ reference operator*() const {
683
+ return { std::cref(it->first), std::ref(it->second) };
684
+ }
685
+ pointer operator->() const {
686
+ std::pair<FirstType, SecondType>* ptr = it.operator->();
687
+ using CVT = std::pair<const FirstType, SecondType>;
688
+ static_assert(offsetof(VT, first) == offsetof(CVT, first)
689
+ && offsetof(VT, second) == offsetof(CVT, second));
690
+ return static_cast<pointer>(static_cast<void*>(ptr)); // adding constness to first
691
+ }
692
+
693
+ PairIterator& operator++() { // prefix
694
+ ++it;
695
+ return *this;
696
+ }
697
+ PairIterator& operator--() { // prefix
698
+ --it;
699
+ return *this;
700
+ }
701
+ PairIterator operator++(int) & { // postfix
702
+ PairIterator tmp = *this;
703
+ ++(*this);
704
+ return tmp;
705
+ }
706
+ PairIterator operator--(int) & { // postfix
707
+ PairIterator tmp = *this;
708
+ --(*this);
709
+ return tmp;
710
+ }
711
+ };
712
+
713
+ template<typename EPT>
714
+ struct EdgePropIterWrap {
715
+ friend class Graph;
716
+ private:
717
+ using Iter = typename std::list<EPT>::iterator;
718
+ // list iterators are NOT invalidated by insertion/removal(of others), making this possible
719
+ Iter pos;
720
+ public:
721
+ EdgePropIterWrap()=default;
722
+ explicit EdgePropIterWrap(const Iter& pos): pos{pos} {}
723
+ const EPT& prop() const { return *(this->pos); }
724
+ EPT& prop() { return *(this->pos); }
725
+ };
726
+
727
+ using NeighborType = std::conditional_t<std::is_void_v<EdgePropType>,
728
+ NodeType, std::pair<NodeType, EdgePropIterWrap<EdgePropType>>>;
729
+ // type of neighbors container; the typename NT is here only because explicit spec is not allowed in a class...
730
+ template <Container C, typename NT, typename EPT>
731
+ struct ContainerGen { };
732
+ template <typename NT, typename EPT>
733
+ struct ContainerGen<Container::LIST, NT, EPT> {
734
+ using type = std::list<NeighborType>;
735
+ };
736
+ template <typename NT, typename EPT>
737
+ struct ContainerGen<Container::VEC, NT, EPT> {
738
+ using type = std::vector<NeighborType>;
739
+ };
740
+ template<typename NT, typename EPT>
741
+ struct ContainerGen<Container::SET, NT, EPT> {
742
+ using type = std::map<NT, EdgePropIterWrap<EPT>>;
743
+ };
744
+ template <typename NT>
745
+ struct ContainerGen<Container::SET, NT, void> {
746
+ using type = std::set<NT>;
747
+ };
748
+ template <typename NT, typename EPT>
749
+ struct ContainerGen<Container::MULTISET, NT, EPT> {
750
+ using type = std::multimap<NT, EdgePropIterWrap<EPT>>;
751
+ };
752
+ template <typename NT>
753
+ struct ContainerGen<Container::MULTISET, NT, void> {
754
+ using type = std::multiset<NT>;
755
+ };
756
+ template <typename NT, typename EPT>
757
+ struct ContainerGen<Container::UNORDERED_SET, NT, EPT> {
758
+ using type = std::unordered_map<NT, EdgePropIterWrap<EPT>>;
759
+ };
760
+ template <typename NT>
761
+ struct ContainerGen<Container::UNORDERED_SET, NT, void> {
762
+ using type = std::unordered_set<NT>;
763
+ };
764
+ template <typename NT, typename EPT>
765
+ struct ContainerGen<Container::UNORDERED_MULTISET, NT, EPT> {
766
+ using type = std::unordered_multimap<NT, EdgePropIterWrap<EPT>>;
767
+ };
768
+ template <typename NT>
769
+ struct ContainerGen<Container::UNORDERED_MULTISET, NT, void> {
770
+ using type = std::unordered_multiset<NT>;
771
+ };
772
+ public:
773
+ using NeighborsContainerType = typename ContainerGen<neighbors_container_spec, NodeType, EdgePropType>::type;
774
+ private:
775
+ using NeighborsType = std::conditional_t<direction==EdgeDirection::UNDIRECTED,
776
+ NeighborsContainerType,
777
+ detail::OutIn<NeighborsContainerType>>;
778
+ struct PropNode { // node with property
779
+ NodePropType prop;
780
+ NeighborsType neighbors;
781
+ PropNode() = default; // needed for map/unordered map
782
+ template<typename ...NPT>
783
+ explicit PropNode(NPT&&... prop): prop{std::forward<NPT>(prop)...}, neighbors{} {
784
+ static_assert(std::is_constructible_v<NodePropType, NPT...>);
785
+ }
786
+ };
787
+ static constexpr bool has_node_prop = not std::is_void_v<NodePropType>;
788
+ using AdjListValueType = std::conditional_t<not has_node_prop, NeighborsType, PropNode>;
789
+ using AdjListType = std::conditional_t<adj_list_spec == Map::MAP,
790
+ std::map<NodeType, AdjListValueType>,
791
+ std::unordered_map<NodeType, AdjListValueType>>;
792
+ public: // iterator types
793
+ using NeighborsConstIterator = typename NeighborsContainerType::const_iterator;
794
+ private:
795
+ template<typename T>
796
+ static constexpr bool can_construct_node = std::is_constructible_v<NodeType, detail::remove_cv_ref_t<T>>;
797
+ static constexpr bool has_edge_prop = not std::is_void_v<EdgePropType>;
798
+ static constexpr bool need_pair_iter = has_edge_prop and (neighbors_container_spec == Container::VEC or neighbors_container_spec == Container::LIST);
799
+ public:
800
+ using NeighborsIterator = std::conditional_t<has_edge_prop,
801
+ std::conditional_t<need_pair_iter,
802
+ PairIterator<NeighborsContainerType>, // make node(aka first) immutable
803
+ typename NeighborsContainerType::iterator>, // the iter for map/multi-map works just fine
804
+ NeighborsConstIterator>; // if no edge prop is needed, always const
805
+ static_assert(std::is_convertible_v<NeighborsIterator, NeighborsConstIterator>);
806
+ using NeighborsView = std::pair<NeighborsIterator, NeighborsIterator>;
807
+ using NeighborsConstView = std::pair<NeighborsConstIterator, NeighborsConstIterator>;
808
+ private:
809
+ using AdjListIterType = typename AdjListType::iterator;
810
+ using AdjListConstIterType = typename AdjListType::const_iterator;
811
+ private:
812
+ int num_of_edges{};
813
+ AdjListType adj_list;
814
+ private: // iterator support
815
+ template<bool IsConst>
816
+ class Iter {
817
+ public:
818
+ using difference_type = typename AdjListConstIterType::difference_type;
819
+ using value_type = const NodeType;
820
+ using reference = const NodeType&;
821
+ using pointer = const NodeType*;
822
+ using iterator_category = std::bidirectional_iterator_tag;
823
+ private:
824
+ template<bool>
825
+ friend class Iter;
826
+ friend class Graph;
827
+ using AdjIterT = std::conditional_t<IsConst, AdjListConstIterType, AdjListIterType>;
828
+ AdjIterT it;
829
+ public:
830
+ Iter()=default;
831
+ Iter(AdjIterT it): it{it} {};
832
+
833
+ // enables implicit conversion from non-const to const
834
+ template<bool WasConst, typename=std::enable_if_t<IsConst or !WasConst>>
835
+ Iter(const Iter<WasConst>& other): it{other.it} {}
836
+
837
+ Iter& operator++() { // prefix
838
+ ++it;
839
+ return *this;
840
+ }
841
+ Iter operator++(int) & { // postfix
842
+ Iter tmp = *this;
843
+ ++(*this);
844
+ return tmp;
845
+ }
846
+ Iter& operator--() { // prefix
847
+ --it;
848
+ return *this;
849
+ }
850
+ Iter operator--(int) & { // postfix
851
+ Iter tmp = *this;
852
+ --(*this);
853
+ return tmp;
854
+ }
855
+
856
+ const NodeType& operator*() const {
857
+ return it->first;
858
+ }
859
+ const NodeType* operator->() const {
860
+ return &(it->first);
861
+ }
862
+ friend bool operator==(const Iter& lhs, const Iter& rhs) { return lhs.it == rhs.it; }
863
+ friend bool operator!=(const Iter& lhs, const Iter& rhs) { return lhs.it != rhs.it; }
864
+ };
865
+
866
+ public:
867
+ using Iterator = Iter<false>;
868
+ using ConstIterator = Iter<true>;
869
+ static_assert(std::is_convertible_v<Iterator, ConstIterator>);
870
+
871
+ Iterator begin() noexcept { return Iter<false>(adj_list.begin()); }
872
+ Iterator end() noexcept { return Iter<false>(adj_list.end()); }
873
+ ConstIterator begin() const noexcept { return Iter<true>(adj_list.cbegin()); }
874
+ ConstIterator end() const noexcept { return Iter<true>(adj_list.cend()); }
875
+ // END OF iterator support
876
+ private: // neighbor access helpers
877
+ const NeighborsContainerType& get_out_neighbors(AdjListConstIterType adj_iter) const {
878
+ if constexpr(not has_node_prop) {
879
+ if constexpr(direction==EdgeDirection::UNDIRECTED) { return adj_iter->second; }
880
+ else { return adj_iter->second.out; }
881
+ } else {
882
+ if constexpr(direction==EdgeDirection::UNDIRECTED) { return adj_iter->second.neighbors; }
883
+ else { return adj_iter->second.neighbors.out; }
884
+ }
885
+ }
886
+ NeighborsContainerType& get_out_neighbors(AdjListIterType adj_iter) {
887
+ return const_cast<NeighborsContainerType&>(static_cast<const Graph*>(this)->get_out_neighbors(adj_iter));
888
+ }
889
+
890
+ const NeighborsContainerType& get_in_neighbors(AdjListConstIterType adj_iter) const {
891
+ if constexpr(not has_node_prop) {
892
+ if constexpr(direction==EdgeDirection::UNDIRECTED) { return adj_iter->second; }
893
+ else { return adj_iter->second.in; }
894
+ } else {
895
+ if constexpr(direction==EdgeDirection::UNDIRECTED) { return adj_iter->second.neighbors; }
896
+ else { return adj_iter->second.neighbors.in; }
897
+ }
898
+ }
899
+ NeighborsContainerType& get_in_neighbors(AdjListIterType adj_iter) {
900
+ return const_cast<NeighborsContainerType&>(static_cast<const Graph*>(this)->get_in_neighbors(adj_iter));
901
+ }
902
+
903
+ // helpers for EdgeDirectionBase
904
+ template<bool is_out, typename T>
905
+ [[nodiscard]] int count_neighbors_helper(const T& node_iv) const {
906
+ AdjListConstIterType pos = find_by_iter_or_by_value(node_iv);
907
+ if (pos==adj_list.end()) {
908
+ std::string msg = is_out ? "out" : "in";
909
+ if constexpr(logging == Logging::ALLOWED){
910
+ print_by_iter_or_by_value(std::cerr << "(count_neighbors) counting " << msg << "-neighbors of a non-existent node", node_iv) << "\n";
911
+ }
912
+ throw std::runtime_error("counting "+ msg +"-neighbors of a non-existent node");
913
+ }
914
+ if constexpr(is_out) {
915
+ return get_out_neighbors(pos).size();
916
+ } else {
917
+ return get_in_neighbors(pos).size();
918
+ }
919
+ }
920
+
921
+ template<bool is_out, typename T>
922
+ NeighborsConstView get_neighbors_helper(const T& node_iv) const {
923
+ AdjListConstIterType pos = find_by_iter_or_by_value(node_iv);
924
+ if (pos==adj_list.end()) {
925
+ std::string msg = is_out ? "out" : "in";
926
+ if constexpr(logging == Logging::ALLOWED){
927
+ print_by_iter_or_by_value(std::cerr << "(neighbors) finding "<< msg <<"-neighbors of a non-existent node", node_iv) << "\n";
928
+ }
929
+ throw std::runtime_error("finding "+ msg +"-neighbors of a non-existent node");
930
+ }
931
+ const NeighborsContainerType& neighbors = [this, &pos]() -> auto& {
932
+ if constexpr(is_out) { return get_out_neighbors(pos); }
933
+ else { return get_in_neighbors(pos); }
934
+ }();
935
+ return {neighbors.begin(), neighbors.end()};
936
+ }
937
+ template<bool is_out, typename T>
938
+ NeighborsView get_neighbors_helper(const T& node_iv) {
939
+ AdjListIterType pos = find_by_iter_or_by_value(node_iv);
940
+ if (pos==adj_list.end()) {
941
+ std::string msg = is_out ? "out" : "in";
942
+ if constexpr(logging == Logging::ALLOWED){
943
+ print_by_iter_or_by_value(std::cerr << "(neighbors) finding "<< msg <<"-neighbors of a non-existent node", node_iv) << "\n";
944
+ }
945
+ throw std::runtime_error("finding "+ msg +"-neighbors of a non-existent node");
946
+ }
947
+ NeighborsContainerType& neighbors = [this, &pos]() -> auto& {
948
+ if constexpr(is_out) { return get_out_neighbors(pos); }
949
+ else { return get_in_neighbors(pos); }
950
+ }();
951
+ return {neighbors.begin(), neighbors.end()};
952
+ }
953
+ // END OF helpers for EdgeDirectionBase
954
+
955
+ const NodeType& get_neighbor_node(const NeighborsConstIterator& nbr_pos) const {
956
+ if constexpr(has_edge_prop) {
957
+ return nbr_pos->first;
958
+ } else {
959
+ return *nbr_pos;
960
+ }
961
+ }
962
+ // END OF neighbor access helpers
963
+ private: // helpers for node search
964
+ // find a node by value
965
+ template<typename T>
966
+ AdjListConstIterType find_node(const T& node_identifier) const {
967
+ static_assert(can_construct_node<T>);
968
+ if constexpr(std::is_convertible_v<T, NodeType>) { // implicit conversion
969
+ return adj_list.find(node_identifier);
970
+ } else { // conversion has to be explicit
971
+ NodeType node{node_identifier};
972
+ return adj_list.find(node);
973
+ }
974
+ }
975
+ template<typename T>
976
+ AdjListIterType find_node(const T& node_identifier) {
977
+ return detail::const_iter_to_iter(adj_list,
978
+ static_cast<const Graph*>(this)->find_node(node_identifier));
979
+ }
980
+
981
+ template<typename T>
982
+ static constexpr bool is_iterator() {
983
+ return std::is_same_v<ConstIterator, detail::remove_cv_ref_t<T>>
984
+ or std::is_same_v<Iterator, detail::remove_cv_ref_t<T>>;
985
+ }
986
+
987
+ template<typename T>
988
+ decltype(auto) unwrap_by_iter_or_by_value(T&& iter_or_val) const {
989
+ if constexpr(is_iterator<T>()) {
990
+ return iter_or_val.it->first;
991
+ } else {
992
+ static_assert(can_construct_node<T>);
993
+ return std::forward<T>(iter_or_val); // simply pass along
994
+ }
995
+ }
996
+
997
+ // either unwrap the iterator, or find the node in adj_list
998
+ template<typename T>
999
+ AdjListConstIterType find_by_iter_or_by_value(const T& iter_or_val) const {
1000
+ if constexpr(is_iterator<T>()) { // by iter
1001
+ return iter_or_val.it;
1002
+ } else { // by value
1003
+ return find_node(iter_or_val);
1004
+ }
1005
+ }
1006
+ template<typename T>
1007
+ AdjListIterType find_by_iter_or_by_value(const T& iter_or_val) {
1008
+ return detail::const_iter_to_iter(adj_list,
1009
+ static_cast<const Graph*>(this)->find_by_iter_or_by_value(iter_or_val));
1010
+ }
1011
+ // END OF either unwrap the iterator, or find the node in adj_list
1012
+
1013
+ // helper method to provide better error messages
1014
+ template<typename T>
1015
+ std::ostream& print_by_iter_or_by_value(std::ostream& os, const T& iter_or_val) const {
1016
+ if constexpr(is_iterator<T>()) { // by iter
1017
+ return os; // no-op if by iter
1018
+ } else { // by value
1019
+ static_assert(can_construct_node<T>);
1020
+ os << ": " << NodeType{iter_or_val};
1021
+ return os;
1022
+ }
1023
+ }
1024
+
1025
+ // find tgt in the neighborhood of src; returns a pair {is_found, neighbor_iterator}
1026
+ template<bool is_out, typename U, typename V>
1027
+ std::pair<bool, NeighborsConstIterator> find_neighbor_helper(const U& src_iv, V&& tgt_identifier) const {
1028
+ static_assert(can_construct_node<V>);
1029
+ AdjListConstIterType src_pos = find_by_iter_or_by_value(src_iv);
1030
+ if (src_pos==adj_list.end()) {
1031
+ if constexpr(logging == Logging::ALLOWED){
1032
+ print_by_iter_or_by_value(std::cerr << "(find_neighbor) source node not found", src_iv) << "\n";
1033
+ }
1034
+ throw std::runtime_error{"source node is not found"};
1035
+ }
1036
+ const NeighborsContainerType& src_neighbors = [this, &src_pos]() -> auto& {
1037
+ if constexpr(is_out) {
1038
+ return get_out_neighbors(src_pos);
1039
+ } else {
1040
+ return get_in_neighbors(src_pos);
1041
+ }
1042
+ }();
1043
+ if constexpr(std::is_same_v<NodeType, detail::remove_cv_ref_t<V>>) {
1044
+ NeighborsConstIterator tgt_pos = detail::container::find(src_neighbors, tgt_identifier);
1045
+ return {tgt_pos!=src_neighbors.end(), tgt_pos};
1046
+ } else {
1047
+ NeighborsConstIterator tgt_pos = detail::container::find(src_neighbors, NodeType{std::forward<V>(tgt_identifier)});
1048
+ return {tgt_pos!=src_neighbors.end(), tgt_pos};
1049
+ }
1050
+ }
1051
+ template<bool is_out, typename U, typename V>
1052
+ std::pair<bool, NeighborsIterator> find_neighbor_helper(const U& src_iv, V&& tgt_identifier) {
1053
+ static_assert(can_construct_node<V>);
1054
+ AdjListIterType src_pos = find_by_iter_or_by_value(src_iv);
1055
+ if (src_pos==adj_list.end()) {
1056
+ if constexpr(logging == Logging::ALLOWED){
1057
+ print_by_iter_or_by_value(std::cerr << "(find_neighbor) source node not found", src_iv) << "\n";
1058
+ }
1059
+ throw std::runtime_error{"source node is not found"};
1060
+ }
1061
+ NeighborsContainerType& src_neighbors = [this, &src_pos]() -> auto& {
1062
+ if constexpr(is_out) {
1063
+ return get_out_neighbors(src_pos);
1064
+ } else {
1065
+ return get_in_neighbors(src_pos);
1066
+ }
1067
+ }();
1068
+ if constexpr(std::is_same_v<NodeType, detail::remove_cv_ref_t<V>>) {
1069
+ auto tgt_pos = detail::container::find(src_neighbors, tgt_identifier);
1070
+ return {tgt_pos!=src_neighbors.end(), tgt_pos};
1071
+ } else {
1072
+ auto tgt_pos = detail::container::find(src_neighbors, NodeType{std::forward<V>(tgt_identifier)});
1073
+ return {tgt_pos!=src_neighbors.end(), tgt_pos};
1074
+ }
1075
+ }
1076
+ // END OF find tgt in the neighborhood of src; returns a pair {is_found, neighbor_iterator}
1077
+ // END OF helpers for node search
1078
+ public: // simple queries
1079
+ [[nodiscard]] size_t size() const noexcept { return adj_list.size(); }
1080
+
1081
+ [[nodiscard]] int num_edges() const noexcept { return num_of_edges; }
1082
+
1083
+ template<typename T>
1084
+ bool has_node(const T& node_identifier) const noexcept {
1085
+ auto pos = find_node(node_identifier);
1086
+ return pos!=adj_list.end();
1087
+ }
1088
+
1089
+ // count the number of edges between src and tgt
1090
+ template<typename U, typename V>
1091
+ int count_edges(const U& source_iv, const V& target_iv) const noexcept {
1092
+ AdjListConstIterType src_pos = find_by_iter_or_by_value(source_iv);
1093
+ AdjListConstIterType tgt_pos = find_by_iter_or_by_value(target_iv);
1094
+ if (src_pos==adj_list.end() or tgt_pos==adj_list.end()) {
1095
+ if constexpr(logging == Logging::ALLOWED){
1096
+ if (src_pos==adj_list.end()) {
1097
+ print_by_iter_or_by_value(std::cerr << "(count_edges) source node not found", source_iv) << "\n";
1098
+ }
1099
+ if (tgt_pos==adj_list.end()) {
1100
+ print_by_iter_or_by_value(std::cerr << "(count_edges) target node not found", target_iv) << "\n";
1101
+ }
1102
+ }
1103
+ return 0;
1104
+ }
1105
+ return detail::container::count(get_out_neighbors(src_pos), tgt_pos->first);
1106
+ }
1107
+
1108
+ // find a node in the graph by value
1109
+ template<typename T>
1110
+ ConstIterator find(const T& node_identifier) const noexcept {
1111
+ AdjListConstIterType pos = find_node(node_identifier);
1112
+ return ConstIterator{pos};
1113
+ }
1114
+ template<typename T>
1115
+ Iterator find(const T& node_identifier) noexcept {
1116
+ AdjListIterType pos = find_node(node_identifier);
1117
+ return Iterator{pos};
1118
+ }
1119
+ private: // edge addition helpers
1120
+ template<typename...EPT>
1121
+ auto insert_edge_prop(EPT&&... prop) {
1122
+ static_assert(has_edge_prop);
1123
+ static_assert(std::is_constructible_v<EdgePropType, EPT...>);
1124
+ this->edge_prop_list.emplace_back(std::forward<EPT>(prop)...);
1125
+ return EdgePropIterWrap<EdgePropType>{std::prev(this->edge_prop_list.end())}; // iterator to the last element
1126
+ }
1127
+
1128
+ bool check_edge_dup(AdjListConstIterType src_pos, const NodeType& src_full, const NodeType& tgt_full) const {
1129
+ if constexpr(multi_edge==MultiEdge::DISALLOWED) { // check is needed only when we disallow dup
1130
+ // this catches multi-self-loop as well
1131
+ const NeighborsContainerType& neighbors = get_out_neighbors(src_pos);
1132
+ if (detail::container::find(neighbors, tgt_full)!=neighbors.end()) {
1133
+ if constexpr(logging == Logging::ALLOWED){
1134
+ std::cerr << "(add_edge) re-adding existing edge: (" << src_full << ", " << tgt_full << ")\n";
1135
+ }
1136
+ return true;
1137
+ }
1138
+ }
1139
+ return false;
1140
+ }
1141
+
1142
+ bool check_self_loop(AdjListIterType src_pos, AdjListIterType tgt_pos, const NodeType& src_full) {
1143
+ if constexpr(self_loop==SelfLoop::DISALLOWED) {
1144
+ if (src_pos==tgt_pos) {
1145
+ if constexpr(logging == Logging::ALLOWED){
1146
+ std::cerr << "(add_edge) adding self loop on node: " << src_full << "\n";
1147
+ }
1148
+ return true;
1149
+ }
1150
+ }
1151
+ return false;
1152
+ }
1153
+ // END OF edge addition helpers
1154
+ private: // edge removal helpers
1155
+ // because every edge has double entry, this method finds the correct double entry to remove
1156
+ NeighborsConstIterator find_tgt_remove_pos(AdjListIterType src_pos, NeighborsConstIterator src_remove_pos, NeighborsContainerType& tgt_neighbors) {
1157
+ if constexpr(has_edge_prop and multi_edge==MultiEdge::ALLOWED) {
1158
+ auto prop_address = &(src_remove_pos->second.prop()); // finding the corresponding double entry
1159
+ auto prop_finder = [&prop_address](const auto& tgt_nbr) { return prop_address== &(tgt_nbr.second.prop()); };
1160
+ if constexpr(neighbors_container_spec == Container::VEC or neighbors_container_spec == Container::LIST) {
1161
+ // NeighborsContainerType is a vector/list of pairs
1162
+ // linearly search for the correct entry and remove
1163
+ return std::find_if(tgt_neighbors.begin(), tgt_neighbors.end(), prop_finder);
1164
+ } else {
1165
+ // NeighborsContainerType is a multi_map or unordered_multi_map
1166
+ static_assert(neighbors_container_spec == Container::MULTISET
1167
+ or neighbors_container_spec == Container::UNORDERED_MULTISET);
1168
+ auto [eq_begin, eq_end] = tgt_neighbors.equal_range(src_pos->first); // slightly optimized search
1169
+ return std::find_if(eq_begin, eq_end, prop_finder);
1170
+ }
1171
+ } else { // either multi edge is disallowed, or we don't differentiate multi-edges
1172
+ static_assert(not has_edge_prop or multi_edge==MultiEdge::DISALLOWED);
1173
+ return detail::container::find(tgt_neighbors, src_pos->first);
1174
+ }
1175
+ }
1176
+
1177
+ // this method is useful for the "remove all" operation
1178
+ std::pair<NeighborsConstIterator, NeighborsConstIterator> find_remove_range(NeighborsContainerType& neighbors, const NodeType& node) {
1179
+ static_assert(has_edge_prop);
1180
+ if constexpr(neighbors_container_spec == Container::VEC or neighbors_container_spec == Container::LIST) {
1181
+ NeighborsConstIterator partition_pos = std::partition(neighbors.begin(), neighbors.end(),
1182
+ [&node](const auto& src_nbr){ return !(src_nbr.first==node); });
1183
+ return {partition_pos, neighbors.end()};
1184
+ } else {
1185
+ static_assert(neighbors_container_spec == Container::MULTISET or neighbors_container_spec == Container::UNORDERED_MULTISET);
1186
+ return neighbors.equal_range(node);
1187
+ }
1188
+ }
1189
+ // END OF edge removal helpers
1190
+ public: // edge removal
1191
+ // all iterators are assumed to be valid
1192
+ int remove_edge(ConstIterator source_pos, NeighborsConstIterator target_nbr_pos) noexcept {
1193
+ AdjListIterType src_pos = detail::const_iter_to_iter(adj_list, source_pos.it);
1194
+ NeighborsContainerType& src_neighbors = get_out_neighbors(src_pos);
1195
+ assert(target_nbr_pos!=src_neighbors.cend());
1196
+ AdjListIterType tgt_pos = adj_list.find(get_neighbor_node(target_nbr_pos));
1197
+ assert(tgt_pos!=adj_list.end());
1198
+ if constexpr(self_loop==SelfLoop::DISALLOWED) { assert(src_pos!=tgt_pos); }
1199
+ NeighborsContainerType& tgt_neighbors = get_in_neighbors(tgt_pos);
1200
+ NeighborsConstIterator tgt_remove_pos = find_tgt_remove_pos(src_pos, target_nbr_pos, tgt_neighbors);
1201
+ assert(tgt_remove_pos!=tgt_neighbors.end());
1202
+ if constexpr(has_edge_prop) { // need to remove edge prop, as well
1203
+ this->edge_prop_list.erase(target_nbr_pos->second.pos);
1204
+ }
1205
+ detail::container::erase_one(src_neighbors, target_nbr_pos);
1206
+ if (src_pos!=tgt_pos or direction==EdgeDirection::DIRECTED) {
1207
+ // when src==tgt && UNDIRECTED, there is NO double entry
1208
+ detail::container::erase_one(tgt_neighbors, tgt_remove_pos);
1209
+ }
1210
+ --num_of_edges;
1211
+ return 1;
1212
+ }
1213
+
1214
+ // remove all edges between source and target
1215
+ template<typename U, typename V>
1216
+ std::enable_if_t<not std::is_convertible_v<V, NeighborsConstIterator>, int>
1217
+ remove_edge(const U& source_iv, const V& target_iv) noexcept {
1218
+ auto src_pos = find_by_iter_or_by_value(source_iv);
1219
+ auto tgt_pos = find_by_iter_or_by_value(target_iv);
1220
+ if(src_pos==adj_list.end() or tgt_pos==adj_list.end()) {
1221
+ if constexpr(logging == Logging::ALLOWED){
1222
+ if (src_pos==adj_list.end()) {
1223
+ print_by_iter_or_by_value(std::cerr << "(remove_edge) edge involves non-existent node", source_iv) << "\n";
1224
+ }
1225
+ if (tgt_pos==adj_list.end()) {
1226
+ print_by_iter_or_by_value(std::cerr << "(remove_edge) edge involves non-existent node", target_iv) << "\n";
1227
+ }
1228
+ }
1229
+ return 0; // no-op if nodes are not found
1230
+ }
1231
+ const NodeType & src_full = src_pos->first;
1232
+ const NodeType & tgt_full = tgt_pos->first;
1233
+ if constexpr(self_loop==SelfLoop::DISALLOWED) {
1234
+ if (src_pos==tgt_pos) { // we know self loop cannot exist
1235
+ if constexpr(logging == Logging::ALLOWED){
1236
+ std::cerr << "(remove_edge) cannot remove self loop on node " << src_full << " when self loop is not even permitted\n";
1237
+ }
1238
+ return 0;
1239
+ }
1240
+ }
1241
+ NeighborsContainerType& src_neighbors = get_out_neighbors(src_pos);
1242
+ if constexpr(multi_edge==MultiEdge::DISALLOWED) { // remove at most 1
1243
+ NeighborsIterator src_remove_pos = detail::container::find(src_neighbors, tgt_full);
1244
+ if (src_remove_pos==src_neighbors.cend()) {
1245
+ if constexpr(logging == Logging::ALLOWED){
1246
+ std::cerr << "(remove_edge) edge (" << src_full << ", " << tgt_full << ") not found\n";
1247
+ }
1248
+ return 0;
1249
+ }
1250
+ remove_edge(ConstIterator{src_pos}, src_remove_pos);
1251
+ --num_of_edges;
1252
+ return 1;
1253
+ } else { // remove all edges between src and tgt, potentially removing no edge at all
1254
+ static_assert(multi_edge==MultiEdge::ALLOWED);
1255
+ static_assert(neighbors_container_spec != Container::SET and neighbors_container_spec != Container::UNORDERED_SET);
1256
+ int num_edges_removed = 0;
1257
+ if constexpr(has_edge_prop) { // remove prop too
1258
+ const auto [src_remove_begin, src_remove_end] = find_remove_range(src_neighbors, tgt_full);
1259
+ // loop through this range to remove prop
1260
+ for (auto it=src_remove_begin; it!=src_remove_end; ++it) {
1261
+ ++num_edges_removed;
1262
+ this->edge_prop_list.erase(it->second.pos);
1263
+ }
1264
+ // erase this range itself
1265
+ src_neighbors.erase(src_remove_begin, src_remove_end);
1266
+ } else { // simply erase all
1267
+ num_edges_removed = detail::container::erase_all(src_neighbors, tgt_full);
1268
+ }
1269
+ if (src_pos!=tgt_pos or direction==EdgeDirection::DIRECTED) {
1270
+ int num_tgt_removed = detail::container::erase_all(get_in_neighbors(tgt_pos), src_full);
1271
+ assert(num_edges_removed == num_tgt_removed);
1272
+ }
1273
+ if constexpr(logging == Logging::ALLOWED){
1274
+ if (num_edges_removed==0) {
1275
+ std::cerr << "(remove_edge) edge (" << src_full << ", " << tgt_full << ") not found\n";
1276
+ }
1277
+ }
1278
+ num_of_edges -= num_edges_removed;
1279
+ return num_edges_removed;
1280
+ }
1281
+ }
1282
+ private: // node removal helper
1283
+ template<bool OutIn>
1284
+ void purge_edge_with(AdjListIterType pos) noexcept {
1285
+ const NodeType& node = pos->first;
1286
+ NeighborsContainerType& neighbors = [this, &pos]() -> auto& {
1287
+ if constexpr(OutIn) {
1288
+ return get_out_neighbors(pos);
1289
+ } else {
1290
+ return get_in_neighbors(pos);
1291
+ }
1292
+ }();
1293
+ NeighborsIterator nbr_begin = neighbors.begin();
1294
+ NeighborsIterator nbr_end = neighbors.end();
1295
+ // this loop should be enough for undirected graphs
1296
+ for (auto it=nbr_begin; it!=nbr_end; ++it) {
1297
+ // purge edges that has to do with the to-be-removed node
1298
+ AdjListIterType neighbor_pos = adj_list.find(get_neighbor_node(it));
1299
+ NeighborsContainerType& neighbors_of_neighbor = [this, &neighbor_pos]() -> auto& {
1300
+ if constexpr(OutIn) {
1301
+ return get_in_neighbors(neighbor_pos);
1302
+ } else {
1303
+ return get_out_neighbors(neighbor_pos);
1304
+ }
1305
+ }();
1306
+ if constexpr(self_loop==SelfLoop::DISALLOWED) {
1307
+ detail::container::erase_all(neighbors_of_neighbor, node);
1308
+ } else {
1309
+ if (neighbor_pos!=pos) { // need to check for self loop
1310
+ detail::container::erase_all(neighbors_of_neighbor, node);
1311
+ }
1312
+ }
1313
+ // remove edge property if needed
1314
+ if constexpr(has_edge_prop) {
1315
+ this->edge_prop_list.erase(it->second.pos);
1316
+ }
1317
+ }
1318
+ }
1319
+ public: // node removal
1320
+ // we can allow removal of several nodes by iterator because erase does not invalidate other iterators
1321
+ template<typename T>
1322
+ int remove_nodes(const T& node_iv) noexcept {
1323
+ auto pos = find_by_iter_or_by_value(node_iv);
1324
+ if (pos == adj_list.end()) { // no-op if not found
1325
+ if constexpr(logging == Logging::ALLOWED){
1326
+ print_by_iter_or_by_value(std::cerr << "(remove_nodes) removing non-existent node", node_iv) << "\n";
1327
+ }
1328
+ return 0;
1329
+ }
1330
+ purge_edge_with<true>(pos); // purge all edges going out of node
1331
+ if constexpr(direction==EdgeDirection::DIRECTED) {
1332
+ // this loop makes it work for directed graphs as well
1333
+ purge_edge_with<false>(pos); // purge all edges coming into node
1334
+ }
1335
+ // count purged edges
1336
+ auto& out_nbrs = get_out_neighbors(pos);
1337
+ int num_edges_purged = out_nbrs.size();
1338
+ if constexpr(direction==EdgeDirection::DIRECTED) {
1339
+ num_edges_purged += get_in_neighbors(pos).size();
1340
+ if constexpr(self_loop==SelfLoop::ALLOWED) { // we would be double counting self-edges
1341
+ num_edges_purged -= detail::container::count(out_nbrs, pos->first);
1342
+ }
1343
+ }
1344
+ num_of_edges -= num_edges_purged;
1345
+ // finally erase pos
1346
+ adj_list.erase(pos);
1347
+ return 1;
1348
+ }
1349
+ template<typename T, typename... Args>
1350
+ int remove_nodes(const T& node_iv, const Args&... args) noexcept {
1351
+ return remove_nodes(node_iv) + remove_nodes(args...);
1352
+ }
1353
+ // END OF node removal
1354
+ };
1355
+ }
1356
+
1357
+ #endif //GSK_GRAPH_LITE_H