Coverage Report

Created: 2025-06-22 06:59

/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