Coverage Report

Created: 2025-12-31 08:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/geojson/directedacyclicgraph.hpp
Line
Count
Source
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
7.08k
    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
632k
    {
52
632k
        nodes.insert(i);
53
632k
        names[i] = s;
54
632k
    }
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
368k
{
110
368k
    if (i == j)
111
5
    {
112
5
        return "self cycle";
113
5
    }
114
368k
    const auto iterI = outgoingNodes.find(i);
115
368k
    if (iterI != outgoingNodes.end() &&
116
304k
        iterI->second.find(j) != iterI->second.end())
117
275k
    {
118
275k
        return "already inserted edge";
119
275k
    }
120
121
92.6k
    if (!cpl::contains(nodes, i))
122
0
    {
123
0
        return "node i unknown";
124
0
    }
125
92.6k
    if (!cpl::contains(nodes, j))
126
0
    {
127
0
        return "node j unknown";
128
0
    }
129
130
92.6k
    if (isTherePathFromTo(j, i))
131
10.1k
    {
132
10.1k
        return "can't add edge: this would cause a cycle";
133
10.1k
    }
134
135
82.4k
    outgoingNodes[i].insert(j);
136
82.4k
    incomingNodes[j].insert(i);
137
82.4k
    return nullptr;
138
92.6k
}
139
140
template <class T, class V>
141
const char *DirectedAcyclicGraph<T, V>::removeEdge(const T &i, const T &j)
142
54.3k
{
143
54.3k
    auto iterI = outgoingNodes.find(i);
144
54.3k
    if (iterI == outgoingNodes.end())
145
0
        return "no outgoing nodes from i";
146
54.3k
    auto iterIJ = iterI->second.find(j);
147
54.3k
    if (iterIJ == iterI->second.end())
148
0
        return "no outgoing node from i to j";
149
54.3k
    iterI->second.erase(iterIJ);
150
54.3k
    if (iterI->second.empty())
151
40.4k
        outgoingNodes.erase(iterI);
152
153
54.3k
    auto iterJ = incomingNodes.find(j);
154
54.3k
    assert(iterJ != incomingNodes.end());
155
54.3k
    auto iterJI = iterJ->second.find(i);
156
54.3k
    assert(iterJI != iterJ->second.end());
157
54.3k
    iterJ->second.erase(iterJI);
158
54.3k
    if (iterJ->second.empty())
159
45.0k
        incomingNodes.erase(iterJ);
160
161
54.3k
    return nullptr;
162
54.3k
}
163
164
template <class T, class V>
165
bool DirectedAcyclicGraph<T, V>::isTherePathFromTo(const T &i, const T &j) const
166
92.6k
{
167
92.6k
    std::set<T> plannedForVisit;
168
92.6k
    std::stack<T> toVisit;
169
92.6k
    toVisit.push(i);
170
92.6k
    plannedForVisit.insert(i);
171
630k
    while (!toVisit.empty())
172
547k
    {
173
547k
        const T n = toVisit.top();
174
547k
        toVisit.pop();
175
547k
        if (n == j)
176
10.1k
            return true;
177
537k
        const auto iter = outgoingNodes.find(n);
178
537k
        if (iter != outgoingNodes.end())
179
398k
        {
180
398k
            for (const T &k : iter->second)
181
609k
            {
182
609k
                if (!cpl::contains(plannedForVisit, k))
183
488k
                {
184
488k
                    plannedForVisit.insert(k);
185
488k
                    toVisit.push(k);
186
488k
                }
187
609k
            }
188
398k
        }
189
537k
    }
190
82.4k
    return false;
191
92.6k
}
192
193
template <class T, class V>
194
std::vector<T> DirectedAcyclicGraph<T, V>::findStartingNodes() const
195
4.29k
{
196
4.29k
    std::vector<T> ret;
197
4.29k
    for (const auto &i : nodes)
198
53.2k
    {
199
53.2k
        if (!cpl::contains(incomingNodes, i))
200
8.23k
            ret.emplace_back(i);
201
53.2k
    }
202
4.29k
    return ret;
203
4.29k
}
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
4.29k
{
210
4.29k
    std::vector<T> ret;
211
4.29k
    ret.reserve(nodes.size());
212
213
4.29k
    const auto cmp = [this](const T &a, const T &b)
214
142k
    { return names.find(a)->second < names.find(b)->second; };
215
4.29k
    std::set<T, decltype(cmp)> S(cmp);
216
217
4.29k
    const auto sn = findStartingNodes();
218
4.29k
    for (const auto &i : sn)
219
8.23k
        S.insert(i);
220
221
57.5k
    while (true)
222
57.5k
    {
223
57.5k
        auto iterFirst = S.begin();
224
57.5k
        if (iterFirst == S.end())
225
4.29k
            break;
226
53.2k
        const auto n = *iterFirst;
227
53.2k
        S.erase(iterFirst);
228
53.2k
        ret.emplace_back(n);
229
230
53.2k
        const auto iter = outgoingNodes.find(n);
231
53.2k
        if (iter != outgoingNodes.end())
232
40.4k
        {
233
            // Need to take a copy as we remove edges during iteration
234
40.4k
            const std::set<T> myOutgoingNodes = iter->second;
235
40.4k
            for (const T &m : myOutgoingNodes)
236
54.3k
            {
237
54.3k
                const char *retRemoveEdge = removeEdge(n, m);
238
54.3k
                (void)retRemoveEdge;
239
54.3k
                assert(retRemoveEdge == nullptr);
240
54.3k
                if (!cpl::contains(incomingNodes, m))
241
45.0k
                {
242
45.0k
                    S.insert(m);
243
45.0k
                }
244
54.3k
            }
245
40.4k
        }
246
53.2k
    }
247
248
    // Should not happen for a direct acyclic graph
249
4.29k
    assert(incomingNodes.empty());
250
4.29k
    assert(outgoingNodes.empty());
251
252
4.29k
    return ret;
253
4.29k
}
254
255
}  // namespace gdal
256
257
#endif  // DIRECTEDACYCLICGRAPH_INCLUDED_H