/src/gdal/ogr/ogrsf_frmts/geojson/directedacyclicgraph.hpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: Implementation of topologic sorting over a directed acyclic graph |
5 | | * Author: Even Rouault |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys dot com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #ifndef DIRECTEDACYCLICGRAPH_INCLUDED_H |
14 | | #define DIRECTEDACYCLICGRAPH_INCLUDED_H |
15 | | |
16 | | #include <algorithm> |
17 | | #include <list> |
18 | | #include <map> |
19 | | #include <set> |
20 | | #include <stack> |
21 | | #include <string> |
22 | | #include <vector> |
23 | | |
24 | | #include <cassert> |
25 | | |
26 | | namespace gdal |
27 | | { |
28 | | |
29 | | // See https://en.wikipedia.org/wiki/Directed_acyclic_graph |
30 | | template <class T, class V = std::string> class DirectedAcyclicGraph |
31 | | { |
32 | | std::set<T> nodes{}; |
33 | | std::map<T, std::set<T>> |
34 | | incomingNodes{}; // incomingNodes[j][i] means an edge from i to j |
35 | | std::map<T, std::set<T>> |
36 | | outgoingNodes{}; // outgoingNodes[i][j] means an edge from i to j |
37 | | std::map<T, V> names{}; |
38 | | |
39 | | public: |
40 | 0 | DirectedAcyclicGraph() = default; |
41 | | |
42 | | void clear() |
43 | | { |
44 | | nodes.clear(); |
45 | | incomingNodes.clear(); |
46 | | outgoingNodes.clear(); |
47 | | names.clear(); |
48 | | } |
49 | | |
50 | | void addNode(const T &i, const V &s) |
51 | 0 | { |
52 | 0 | nodes.insert(i); |
53 | 0 | names[i] = s; |
54 | 0 | } |
55 | | |
56 | | void removeNode(const T &i); |
57 | | const char *addEdge(const T &i, const T &j); |
58 | | const char *removeEdge(const T &i, const T &j); |
59 | | bool isTherePathFromTo(const T &i, const T &j) const; |
60 | | std::vector<T> findStartingNodes() const; |
61 | | std::vector<T> getTopologicalOrdering(); |
62 | | }; |
63 | | |
64 | | template <class T, class V> |
65 | | void DirectedAcyclicGraph<T, V>::removeNode(const T &i) |
66 | 0 | { |
67 | 0 | nodes.erase(i); |
68 | 0 | names.erase(i); |
69 | |
|
70 | 0 | { |
71 | 0 | auto incomingIter = incomingNodes.find(i); |
72 | 0 | if (incomingIter != incomingNodes.end()) |
73 | 0 | { |
74 | 0 | for (const T &j : incomingIter->second) |
75 | 0 | { |
76 | 0 | auto outgoingIter = outgoingNodes.find(j); |
77 | 0 | assert(outgoingIter != outgoingNodes.end()); |
78 | 0 | auto iterJI = outgoingIter->second.find(i); |
79 | 0 | assert(iterJI != outgoingIter->second.end()); |
80 | 0 | outgoingIter->second.erase(iterJI); |
81 | 0 | if (outgoingIter->second.empty()) |
82 | 0 | outgoingNodes.erase(outgoingIter); |
83 | 0 | } |
84 | 0 | incomingNodes.erase(incomingIter); |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | 0 | { |
89 | 0 | auto outgoingIter = outgoingNodes.find(i); |
90 | 0 | if (outgoingIter != outgoingNodes.end()) |
91 | 0 | { |
92 | 0 | for (const T &j : outgoingIter->second) |
93 | 0 | { |
94 | 0 | auto incomingIter = incomingNodes.find(j); |
95 | 0 | assert(incomingIter != incomingNodes.end()); |
96 | 0 | auto iterJI = incomingIter->second.find(i); |
97 | 0 | assert(iterJI != incomingIter->second.end()); |
98 | 0 | incomingIter->second.erase(iterJI); |
99 | 0 | if (incomingIter->second.empty()) |
100 | 0 | incomingNodes.erase(incomingIter); |
101 | 0 | } |
102 | 0 | outgoingNodes.erase(outgoingIter); |
103 | 0 | } |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | template <class T, class V> |
108 | | const char *DirectedAcyclicGraph<T, V>::addEdge(const T &i, const T &j) |
109 | 0 | { |
110 | 0 | if (i == j) |
111 | 0 | { |
112 | 0 | return "self cycle"; |
113 | 0 | } |
114 | 0 | const auto iterI = outgoingNodes.find(i); |
115 | 0 | if (iterI != outgoingNodes.end() && |
116 | 0 | iterI->second.find(j) != iterI->second.end()) |
117 | 0 | { |
118 | 0 | return "already inserted edge"; |
119 | 0 | } |
120 | | |
121 | 0 | if (!cpl::contains(nodes, i)) |
122 | 0 | { |
123 | 0 | return "node i unknown"; |
124 | 0 | } |
125 | 0 | if (!cpl::contains(nodes, j)) |
126 | 0 | { |
127 | 0 | return "node j unknown"; |
128 | 0 | } |
129 | | |
130 | 0 | if (isTherePathFromTo(j, i)) |
131 | 0 | { |
132 | 0 | return "can't add edge: this would cause a cycle"; |
133 | 0 | } |
134 | | |
135 | 0 | outgoingNodes[i].insert(j); |
136 | 0 | incomingNodes[j].insert(i); |
137 | 0 | return nullptr; |
138 | 0 | } |
139 | | |
140 | | template <class T, class V> |
141 | | const char *DirectedAcyclicGraph<T, V>::removeEdge(const T &i, const T &j) |
142 | 0 | { |
143 | 0 | auto iterI = outgoingNodes.find(i); |
144 | 0 | if (iterI == outgoingNodes.end()) |
145 | 0 | return "no outgoing nodes from i"; |
146 | 0 | auto iterIJ = iterI->second.find(j); |
147 | 0 | if (iterIJ == iterI->second.end()) |
148 | 0 | return "no outgoing node from i to j"; |
149 | 0 | iterI->second.erase(iterIJ); |
150 | 0 | if (iterI->second.empty()) |
151 | 0 | outgoingNodes.erase(iterI); |
152 | |
|
153 | 0 | auto iterJ = incomingNodes.find(j); |
154 | 0 | assert(iterJ != incomingNodes.end()); |
155 | 0 | auto iterJI = iterJ->second.find(i); |
156 | 0 | assert(iterJI != iterJ->second.end()); |
157 | 0 | iterJ->second.erase(iterJI); |
158 | 0 | if (iterJ->second.empty()) |
159 | 0 | incomingNodes.erase(iterJ); |
160 | |
|
161 | 0 | return nullptr; |
162 | 0 | } |
163 | | |
164 | | template <class T, class V> |
165 | | bool DirectedAcyclicGraph<T, V>::isTherePathFromTo(const T &i, const T &j) const |
166 | 0 | { |
167 | 0 | std::set<T> plannedForVisit; |
168 | 0 | std::stack<T> toVisit; |
169 | 0 | toVisit.push(i); |
170 | 0 | plannedForVisit.insert(i); |
171 | 0 | while (!toVisit.empty()) |
172 | 0 | { |
173 | 0 | const T n = toVisit.top(); |
174 | 0 | toVisit.pop(); |
175 | 0 | if (n == j) |
176 | 0 | return true; |
177 | 0 | const auto iter = outgoingNodes.find(n); |
178 | 0 | if (iter != outgoingNodes.end()) |
179 | 0 | { |
180 | 0 | for (const T &k : iter->second) |
181 | 0 | { |
182 | 0 | if (!cpl::contains(plannedForVisit, k)) |
183 | 0 | { |
184 | 0 | plannedForVisit.insert(k); |
185 | 0 | toVisit.push(k); |
186 | 0 | } |
187 | 0 | } |
188 | 0 | } |
189 | 0 | } |
190 | 0 | return false; |
191 | 0 | } |
192 | | |
193 | | template <class T, class V> |
194 | | std::vector<T> DirectedAcyclicGraph<T, V>::findStartingNodes() const |
195 | 0 | { |
196 | 0 | std::vector<T> ret; |
197 | 0 | for (const auto &i : nodes) |
198 | 0 | { |
199 | 0 | if (!cpl::contains(incomingNodes, i)) |
200 | 0 | ret.emplace_back(i); |
201 | 0 | } |
202 | 0 | return ret; |
203 | 0 | } |
204 | | |
205 | | // Kahn's algorithm: |
206 | | // https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm |
207 | | template <class T, class V> |
208 | | std::vector<T> DirectedAcyclicGraph<T, V>::getTopologicalOrdering() |
209 | 0 | { |
210 | 0 | std::vector<T> ret; |
211 | 0 | ret.reserve(nodes.size()); |
212 | |
|
213 | 0 | const auto cmp = [this](const T &a, const T &b) |
214 | 0 | { return names.find(a)->second < names.find(b)->second; }; |
215 | 0 | std::set<T, decltype(cmp)> S(cmp); |
216 | |
|
217 | 0 | const auto sn = findStartingNodes(); |
218 | 0 | for (const auto &i : sn) |
219 | 0 | S.insert(i); |
220 | |
|
221 | 0 | while (true) |
222 | 0 | { |
223 | 0 | auto iterFirst = S.begin(); |
224 | 0 | if (iterFirst == S.end()) |
225 | 0 | break; |
226 | 0 | const auto n = *iterFirst; |
227 | 0 | S.erase(iterFirst); |
228 | 0 | ret.emplace_back(n); |
229 | |
|
230 | 0 | const auto iter = outgoingNodes.find(n); |
231 | 0 | if (iter != outgoingNodes.end()) |
232 | 0 | { |
233 | | // Need to take a copy as we remove edges during iteration |
234 | 0 | const std::set<T> myOutgoingNodes = iter->second; |
235 | 0 | for (const T &m : myOutgoingNodes) |
236 | 0 | { |
237 | 0 | const char *retRemoveEdge = removeEdge(n, m); |
238 | 0 | (void)retRemoveEdge; |
239 | 0 | assert(retRemoveEdge == nullptr); |
240 | 0 | if (!cpl::contains(incomingNodes, m)) |
241 | 0 | { |
242 | 0 | S.insert(m); |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | // Should not happen for a direct acyclic graph |
249 | 0 | assert(incomingNodes.empty()); |
250 | 0 | assert(outgoingNodes.empty()); |
251 | | |
252 | 0 | return ret; |
253 | 0 | } |
254 | | |
255 | | } // namespace gdal |
256 | | |
257 | | #endif // DIRECTEDACYCLICGRAPH_INCLUDED_H |