LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - directedacyclicgraph.hpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 102 115 88.7 %
Date: 2024-11-21 22:18:42 Functions: 9 9 100.0 %

          Line data    Source code
       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        1353 :     DirectedAcyclicGraph() = default;
      41             : 
      42             :     void clear()
      43             :     {
      44             :         nodes.clear();
      45             :         incomingNodes.clear();
      46             :         outgoingNodes.clear();
      47             :         names.clear();
      48             :     }
      49             : 
      50       20117 :     void addNode(const T &i, const V &s)
      51             :     {
      52       20117 :         nodes.insert(i);
      53       20117 :         names[i] = s;
      54       20117 :     }
      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           2 : void DirectedAcyclicGraph<T, V>::removeNode(const T &i)
      66             : {
      67           2 :     nodes.erase(i);
      68           2 :     names.erase(i);
      69             : 
      70             :     {
      71           2 :         auto incomingIter = incomingNodes.find(i);
      72           2 :         if (incomingIter != incomingNodes.end())
      73             :         {
      74           4 :             for (const T &j : incomingIter->second)
      75             :             {
      76           2 :                 auto outgoingIter = outgoingNodes.find(j);
      77           2 :                 assert(outgoingIter != outgoingNodes.end());
      78           2 :                 auto iterJI = outgoingIter->second.find(i);
      79           2 :                 assert(iterJI != outgoingIter->second.end());
      80           2 :                 outgoingIter->second.erase(iterJI);
      81           2 :                 if (outgoingIter->second.empty())
      82           2 :                     outgoingNodes.erase(outgoingIter);
      83             :             }
      84           2 :             incomingNodes.erase(incomingIter);
      85             :         }
      86             :     }
      87             : 
      88             :     {
      89           2 :         auto outgoingIter = outgoingNodes.find(i);
      90           2 :         if (outgoingIter != outgoingNodes.end())
      91             :         {
      92           0 :             for (const T &j : outgoingIter->second)
      93             :             {
      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             :             }
     102           0 :             outgoingNodes.erase(outgoingIter);
     103             :         }
     104             :     }
     105           2 : }
     106             : 
     107             : template <class T, class V>
     108       17643 : const char *DirectedAcyclicGraph<T, V>::addEdge(const T &i, const T &j)
     109             : {
     110       17643 :     if (i == j)
     111             :     {
     112           3 :         return "self cycle";
     113             :     }
     114       17640 :     const auto iterI = outgoingNodes.find(i);
     115       34582 :     if (iterI != outgoingNodes.end() &&
     116       34582 :         iterI->second.find(j) != iterI->second.end())
     117             :     {
     118       15342 :         return "already inserted edge";
     119             :     }
     120             : 
     121        2298 :     if (!cpl::contains(nodes, i))
     122             :     {
     123           0 :         return "node i unknown";
     124             :     }
     125        2298 :     if (!cpl::contains(nodes, j))
     126             :     {
     127           0 :         return "node j unknown";
     128             :     }
     129             : 
     130        2298 :     if (isTherePathFromTo(j, i))
     131             :     {
     132        1576 :         return "can't add edge: this would cause a cycle";
     133             :     }
     134             : 
     135         722 :     outgoingNodes[i].insert(j);
     136         722 :     incomingNodes[j].insert(i);
     137         722 :     return nullptr;
     138             : }
     139             : 
     140             : template <class T, class V>
     141         720 : const char *DirectedAcyclicGraph<T, V>::removeEdge(const T &i, const T &j)
     142             : {
     143         720 :     auto iterI = outgoingNodes.find(i);
     144         720 :     if (iterI == outgoingNodes.end())
     145           0 :         return "no outgoing nodes from i";
     146         720 :     auto iterIJ = iterI->second.find(j);
     147         720 :     if (iterIJ == iterI->second.end())
     148           0 :         return "no outgoing node from i to j";
     149         720 :     iterI->second.erase(iterIJ);
     150         720 :     if (iterI->second.empty())
     151         693 :         outgoingNodes.erase(iterI);
     152             : 
     153         720 :     auto iterJ = incomingNodes.find(j);
     154         720 :     assert(iterJ != incomingNodes.end());
     155         720 :     auto iterJI = iterJ->second.find(i);
     156         720 :     assert(iterJI != iterJ->second.end());
     157         720 :     iterJ->second.erase(iterJI);
     158         720 :     if (iterJ->second.empty())
     159         694 :         incomingNodes.erase(iterJ);
     160             : 
     161         720 :     return nullptr;
     162             : }
     163             : 
     164             : template <class T, class V>
     165        2298 : bool DirectedAcyclicGraph<T, V>::isTherePathFromTo(const T &i, const T &j) const
     166             : {
     167        4596 :     std::set<T> plannedForVisit;
     168        4596 :     std::stack<T> toVisit;
     169        2298 :     toVisit.push(i);
     170        2298 :     plannedForVisit.insert(i);
     171        6282 :     while (!toVisit.empty())
     172             :     {
     173        5560 :         const T n = toVisit.top();
     174        5560 :         toVisit.pop();
     175        5560 :         if (n == j)
     176        1576 :             return true;
     177        3984 :         const auto iter = outgoingNodes.find(n);
     178        3984 :         if (iter != outgoingNodes.end())
     179             :         {
     180        8100 :             for (const T &k : iter->second)
     181             :             {
     182        4839 :                 if (!cpl::contains(plannedForVisit, k))
     183             :                 {
     184        3263 :                     plannedForVisit.insert(k);
     185        3263 :                     toVisit.push(k);
     186             :                 }
     187             :             }
     188             :         }
     189             :     }
     190         722 :     return false;
     191             : }
     192             : 
     193             : template <class T, class V>
     194         631 : std::vector<T> DirectedAcyclicGraph<T, V>::findStartingNodes() const
     195             : {
     196         631 :     std::vector<T> ret;
     197        1696 :     for (const auto &i : nodes)
     198             :     {
     199        1065 :         if (!cpl::contains(incomingNodes, i))
     200         371 :             ret.emplace_back(i);
     201             :     }
     202         631 :     return ret;
     203             : }
     204             : 
     205             : // Kahn's algorithm:
     206             : // https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
     207             : template <class T, class V>
     208         631 : std::vector<T> DirectedAcyclicGraph<T, V>::getTopologicalOrdering()
     209             : {
     210         631 :     std::vector<T> ret;
     211         631 :     ret.reserve(nodes.size());
     212             : 
     213         745 :     const auto cmp = [this](const T &a, const T &b)
     214          57 :     { return names.find(a)->second < names.find(b)->second; };
     215        1262 :     std::set<T, decltype(cmp)> S(cmp);
     216             : 
     217        1262 :     const auto sn = findStartingNodes();
     218        1002 :     for (const auto &i : sn)
     219         371 :         S.insert(i);
     220             : 
     221        1065 :     while (true)
     222             :     {
     223        1696 :         auto iterFirst = S.begin();
     224        1696 :         if (iterFirst == S.end())
     225         631 :             break;
     226        1065 :         const auto n = *iterFirst;
     227        1065 :         S.erase(iterFirst);
     228        1065 :         ret.emplace_back(n);
     229             : 
     230        1065 :         const auto iter = outgoingNodes.find(n);
     231        1065 :         if (iter != outgoingNodes.end())
     232             :         {
     233             :             // Need to take a copy as we remove edges during iteration
     234        1386 :             const std::set<T> myOutgoingNodes = iter->second;
     235        1413 :             for (const T &m : myOutgoingNodes)
     236             :             {
     237         720 :                 const char *retRemoveEdge = removeEdge(n, m);
     238             :                 (void)retRemoveEdge;
     239         720 :                 assert(retRemoveEdge == nullptr);
     240         720 :                 if (!cpl::contains(incomingNodes, m))
     241             :                 {
     242         694 :                     S.insert(m);
     243             :                 }
     244             :             }
     245             :         }
     246             :     }
     247             : 
     248             :     // Should not happen for a direct acyclic graph
     249         631 :     assert(incomingNodes.empty());
     250         631 :     assert(outgoingNodes.empty());
     251             : 
     252        1262 :     return ret;
     253             : }
     254             : 
     255             : }  // namespace gdal
     256             : 
     257             : #endif  // DIRECTEDACYCLICGRAPH_INCLUDED_H

Generated by: LCOV version 1.14