aboutsummaryrefslogtreecommitdiffstats
path: root/libdevcore/Algorithms.h
blob: 34a4dfe5ace8d05b0bea0270512e659d292f27c3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
    This file is part of solidity.

    solidity is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    solidity is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with solidity.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once


#include <functional>
#include <set>

namespace dev
{

/**
 * Detector for cycles in directed graphs. It returns the first
 * vertex on the path towards a cycle or a nullptr if there is
 * no reachable cycle starting from a given vertex.
 */
template <typename V>
class CycleDetector
{
public:
    using Visitor = std::function<void(V const&, CycleDetector&, size_t)>;

    /// Initializes the cycle detector
    /// @param _visit function that is given the current vertex
    ///               and is supposed to call @a run on all
    ///               adjacent vertices.
    explicit CycleDetector(Visitor _visit):
        m_visit(std::move(_visit))
    {  }

    /// Recursively perform cycle detection starting
    /// (or continuing) with @param _vertex
    /// @returns the first vertex on the path towards a cycle from @a _vertex
    /// or nullptr if no cycle is reachable from @a _vertex.
    V const* run(V const& _vertex)
    {
        if (m_firstCycleVertex)
            return m_firstCycleVertex;
        if (m_processed.count(&_vertex))
            return nullptr;
        else if (m_processing.count(&_vertex))
            return m_firstCycleVertex = &_vertex;
        m_processing.insert(&_vertex);

        m_depth++;
        m_visit(_vertex, *this, m_depth);
        m_depth--;
        if (m_firstCycleVertex && m_depth == 1)
            m_firstCycleVertex = &_vertex;

        m_processing.erase(&_vertex);
        m_processed.insert(&_vertex);
        return m_firstCycleVertex;
    }

private:
    Visitor m_visit;
    std::set<V const*> m_processing;
    std::set<V const*> m_processed;
    size_t m_depth = 0;
    V const* m_firstCycleVertex = nullptr;
};

/**
 * Generic breadth first search.
 *
 * Example: Gather all (recursive) children in a graph starting at (and including) ``root``:
 *
 * Node const* root = ...;
 * std::set<Node> allNodes = BreadthFirstSearch<Node>{{root}}.run([](Node const& _node, auto&& _addChild) {
 *   // Potentially process ``_node``.
 *   for (Node const& _child: _node.children())
 *     // Potentially filter the children to be visited.
 *     _addChild(_child);
 * }).visited;
 *
 * Note that the order of the traversal is *non-deterministic* (the children are stored in a std::set of pointers).
 */
template<typename V>
struct BreadthFirstSearch
{
    /// Runs the breadth first search. The verticesToTraverse member of the struct needs to be initialized.
    /// @param _forEachChild is a callable of the form [...](V const& _node, auto&& _addChild) { ... }
    /// that is called for each visited node and is supposed to call _addChild(childNode) for every child
    /// node of _node.
    template<typename ForEachChild>
    BreadthFirstSearch& run(ForEachChild&& _forEachChild)
    {
        while (!verticesToTraverse.empty())
        {
            V const* v = *verticesToTraverse.begin();
            verticesToTraverse.erase(verticesToTraverse.begin());
            visited.insert(v);

            _forEachChild(*v, [this](V const& _vertex) {
                if (!visited.count(&_vertex))
                    verticesToTraverse.insert(&_vertex);
            });
        }
        return *this;
    }

    std::set<V const*> verticesToTraverse;
    std::set<V const*> visited{};
};

}