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.
- spatial_graph/__init__.py +15 -0
- spatial_graph/dtypes.py +130 -0
- spatial_graph/graph/__init__.py +3 -0
- spatial_graph/graph/graph.py +226 -0
- spatial_graph/graph/src/LICENSE.txt +7 -0
- spatial_graph/graph/src/graph_lite.h +1357 -0
- spatial_graph/graph/wrapper_template.pyx +654 -0
- spatial_graph/rtree/__init__.py +5 -0
- spatial_graph/rtree/line_rtree.py +156 -0
- spatial_graph/rtree/point_rtree.py +6 -0
- spatial_graph/rtree/rtree.py +125 -0
- spatial_graph/rtree/src/ARCHITECTURE.md +162 -0
- spatial_graph/rtree/src/LICENSE +20 -0
- spatial_graph/rtree/src/config.h +13 -0
- spatial_graph/rtree/src/rtree.c +1021 -0
- spatial_graph/rtree/src/rtree.h +97 -0
- spatial_graph/rtree/wrapper_template.pyx +356 -0
- spatial_graph/spatial_graph.py +95 -0
- spatial_graph-0.0.1.dist-info/METADATA +130 -0
- spatial_graph-0.0.1.dist-info/RECORD +22 -0
- spatial_graph-0.0.1.dist-info/WHEEL +4 -0
- spatial_graph-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|