From df854fc370c0d8675741c9446afbd64a143c68bb Mon Sep 17 00:00:00 2001 From: Ian Roddis Date: Wed, 9 Jun 2021 21:52:10 -0300 Subject: [PATCH] This is probably the stupidest implementation ever, but it works. To be fixed! --- daggy/include/daggy/DAG.hpp | 31 ++++-- daggy/include/daggy/DAGImpl.hpp | 127 ++++++++++++++++++------- daggy/include/daggy/DAGVisitor.hpp | 36 ------- daggy/include/daggy/DAGVisitorImpl.hpp | 42 -------- tests/unit_dag.cpp | 63 +++++++++--- tests/unit_dag_visitor.cpp | 34 ------- 6 files changed, 168 insertions(+), 165 deletions(-) delete mode 100644 daggy/include/daggy/DAGVisitor.hpp delete mode 100644 daggy/include/daggy/DAGVisitorImpl.hpp delete mode 100644 tests/unit_dag_visitor.cpp diff --git a/daggy/include/daggy/DAG.hpp b/daggy/include/daggy/DAG.hpp index d75817a..888c0d3 100644 --- a/daggy/include/daggy/DAG.hpp +++ b/daggy/include/daggy/DAG.hpp @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include #include @@ -15,31 +15,44 @@ namespace daggy { + enum class VertexState { + UNVISITED = 0, + VISITING, + VISITED + }; + template class DAG { public: DAG() {} // Vertices - void addVertex(T id); + void addVertex(T id, VertexState state = VertexState::UNVISITED); void dropVertex(const T & id); - const std::unordered_set & getChildren(const T & id) const; - const std::unordered_set & getRoots() const; + std::set getVertices() const; + std::set getParents(const T & id) const; + std::set getChildren(const T & id) const; // Edges void addEdge(const T & src, const T & dst); void dropEdge(const T & src, const T & dst); - - // Returns the path from {from} to {to} - std::deque shortestPath(const T & from, const T & to); + bool hasPath(const T & from, const T & to) const; // Attributes size_t size() const; bool empty() const; + // Traversal + void setVisitState(VertexState state); + VertexState getVertexState(const T & id) const; + bool allVisited() const; + + std::optional visitNext(); + void completeVisit(const T & id); + private: - std::unordered_map> vertices_; - std::unordered_set roots_; + std::unordered_map vertices_; + std::set> edges_; }; #include "DAGImpl.hpp" diff --git a/daggy/include/daggy/DAGImpl.hpp b/daggy/include/daggy/DAGImpl.hpp index 5805e21..363a3e0 100644 --- a/daggy/include/daggy/DAGImpl.hpp +++ b/daggy/include/daggy/DAGImpl.hpp @@ -9,67 +9,128 @@ bool DAG::empty() const { } template -void DAG::addVertex(T id) { +void DAG::addVertex(T id, VertexState state) { if (vertices_.find(id) != vertices_.end()) throw std::runtime_error("Vertex already exists in graph"); - vertices_[id]; - roots_.insert(id); + vertices_[id] = state; } template void DAG::dropVertex(const T & id) { vertices_.extract(id); - roots_.extract(id); + for (auto it = edges_.begin(); it != edges_.end(); ) { + if (it->first == id or it->second == id) { + it = edges_.erase(it); + } else { + ++it; + } + } } template void DAG::dropEdge(const T & from, const T & to) { - auto & src = vertices_.at(from); - src.extract(to); - roots_.extract(to); + for (auto it = edges_.begin(); it != edges_.end(); ) { + if (it->first == from and it->second == to) { + it = edges_.erase(it); + break; + } else { + ++it; + } + } } template void DAG::addEdge(const T & from, const T & to) { - auto & src = vertices_.at(from); - - if (shortestPath(to, from).size() > 1) { - throw std::runtime_error("Unable to add edge that would result in a cycle"); - } - - // Add the edge - src.insert(to); - roots_.extract(to); + if (hasPath(to, from)) + throw std::runtime_error("Adding edge would result in a cycle"); + edges_.emplace(from, to); } template -std::deque DAG::shortestPath(const T & from, const T & to) { - std::deque subpath; +bool DAG::hasPath(const T & from, const T & to) const { + bool pathFound = false; - if (from == to) return {to}; + for (const auto & pr : edges_) { + if (pr.first != from) continue; + if (pr.second == to) return true; - auto & src = vertices_.at(from); - - for (const auto & cid : src) { - auto pth = shortestPath(cid, to); - if (subpath.size() == 0 or subpath.size() > pth.size()) - subpath.swap(pth); + if (hasPath(pr.second, to)) return true; } - if (subpath.size() == 0) return subpath; - - subpath.push_front(from); - - return subpath; + return false; } template -const std::unordered_set & DAG::getChildren(const T & id) const { +std::set DAG::getVertices() const { + std::set vertices; + for (const auto & [v, _] : vertices_) { + vertices.insert(v); + } + return vertices; +} + +template +std::set DAG::getParents(const T & id) const { + std::set parents; + for (const auto & [p, c] : edges_) { + if (c == id) parents.push_back(p); + } + return parents; +} + +template +std::set DAG::getChildren(const T & id) const { + std::set children; + for (const auto & [p, c] : edges_) { + if (p == id) children.push_back(c); + } + return children; +} + +template +void DAG::setVisitState(VertexState state) { + for (auto & [v, s] : vertices_) s = state; +} + +template +VertexState DAG::getVertexState(const T & id) const { return vertices_.at(id); } template -const std::unordered_set & DAG::getRoots() const { - return roots_; +bool DAG::allVisited() const { + for (const auto & [_, s] : vertices_) { + if (s != VertexState::VISITED) return false; + } + return true; } +template +std::optional DAG::visitNext() { + for (auto & [v, s] : vertices_) { + if (s != VertexState::UNVISITED) continue; + + // check to see if all parents are completed + bool parentsComplete = true; + for (const auto & [p, c] : edges_) { + if (c != v) continue; + if (vertices_[p] != VertexState::VISITED) { + parentsComplete = false; + break; + } + } + + if (! parentsComplete) continue; + + s = VertexState::VISITING; + return v; + } + return {}; +} + +template +void DAG::completeVisit(const T & id) { + auto it = vertices_.find(id); + if (it == vertices_.end()) return; + it->second = VertexState::VISITED; +} diff --git a/daggy/include/daggy/DAGVisitor.hpp b/daggy/include/daggy/DAGVisitor.hpp deleted file mode 100644 index 2eae226..0000000 --- a/daggy/include/daggy/DAGVisitor.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "DAG.hpp" - -/* - The DAG structure in daggy is just to ensure that tasks are run - in the correct dependent order. -*/ - -namespace daggy { - template - class DAGVisitor { - public: - DAGVisitor(const DAG & dag, std::unordered_set roots = {}); - - std::optional visitNext(); // Get the next ID - void completeVisit(const T& id); // Mark the ID - bool isComplete() const; // True if the graph has been fully traversed - - private: - const DAG & dag_; - std::deque queued_; - std::unordered_set processing_; - std::unordered_set complete_; - }; - -#include "DAGVisitorImpl.hpp" -} diff --git a/daggy/include/daggy/DAGVisitorImpl.hpp b/daggy/include/daggy/DAGVisitorImpl.hpp deleted file mode 100644 index 548d74e..0000000 --- a/daggy/include/daggy/DAGVisitorImpl.hpp +++ /dev/null @@ -1,42 +0,0 @@ -template -DAGVisitor::DAGVisitor(const DAG & dag, std::unordered_set roots) - : dag_(dag) { - if (roots.size() == 0) { - for (const auto & id : dag_.getRoots()) { - queued_.emplace_back(id); - } - } else { - for (auto & id : roots) { - queued_.push_back(id); - } - } -} - -template -std::optional DAGVisitor::visitNext() { - if (queued_.empty()) return {}; - const auto & id = queued_.front(); - processing_.insert(id); - queued_.pop_front(); - return id; -}; // Get the next ID - -template -void DAGVisitor::completeVisit(const T& id) { - auto entry = processing_.extract(id); - complete_.insert(entry.value()); - for (const auto & c : dag_.getChildren(id)) { - if (complete_.find(c) == complete_.end()) { - queued_.push_back(c); - } - } -} - -template -bool DAGVisitor::isComplete() const { - bool complete = queued_.empty(); - for (const auto & p : processing_) { - complete &= dag_.getChildren(p).empty(); - } - return complete; -}; diff --git a/tests/unit_dag.cpp b/tests/unit_dag.cpp index 761c9c9..88c070d 100644 --- a/tests/unit_dag.cpp +++ b/tests/unit_dag.cpp @@ -1,7 +1,6 @@ #include #include "daggy/DAG.hpp" -#include "daggy/DAGVisitor.hpp" #include "catch.hpp" @@ -17,27 +16,69 @@ TEST_CASE("DAG Construction Tests", "[dag]") { dag.addEdge(i-1, i); } - REQUIRE(dag.getRoots().size() == 1); REQUIRE(dag.size() == 10); REQUIRE(! dag.empty()); // Cannot add an edge that would result in a cycle REQUIRE_THROWS(dag.addEdge(9, 5)); + + SECTION("Visit State") { + dag.setVisitState(daggy::VertexState::VISITING); + for (const auto v : dag.getVertices()) { + REQUIRE(dag.getVertexState(v) == daggy::VertexState::VISITING); + } + } } -TEST_CASE("DAG Basic Tests", "[dag]") { +TEST_CASE("DAG Traversal Tests", "[dag]") { daggy::DAG dag; - dag.addVertex(0); - for (int i = 1; i < 10; ++i) { - dag.addVertex(i); - dag.addEdge(i-1, i); + const int N_VERTICES = 10; + + for (int i = 0; i < N_VERTICES; ++i) { dag.addVertex(i); } + + /* + 0 ---------------------\ + 1 ---------- \ \ + 2 ---- 3 ---- > 5 -------> 6 -----> 7 + 4 -------------------------------/ + 8 --> 9 + */ + + std::vector> edges{ + {0, 6} + , {1, 5} + , {5, 6} + , {6, 7} + , {2, 3} + , {3, 5} + , {4, 7} + , {8, 9} + }; + + for (auto const [from, to] : edges) { + dag.addEdge(from, to); } - SECTION("Pathing") { - REQUIRE(dag.shortestPath(0,9).size() == 10); + SECTION("Baisc Traversal") { + dag.setVisitState(daggy::VertexState::UNVISITED); + std::vector visitOrder(N_VERTICES); + size_t i = 0; + while (! dag.allVisited()) { + const auto & v = dag.visitNext().value(); + dag.completeVisit(v); + visitOrder[v] = i; + ++i; + } - dag.addEdge(5, 9); - REQUIRE(dag.shortestPath(0,9).size() == 7); + std::cout << "ORDER:"; + for (size_t i = 0; i < N_VERTICES; ++i) std::cout << " (" << i << ',' << visitOrder[i] << ')'; + //for (auto v : visitOrder) std::cout << " " << v; + std::cout << std::endl; + + // Ensure visit order is preserved + for (auto const [from, to] : edges) { + REQUIRE(visitOrder[from] <= visitOrder[to]); + } } } diff --git a/tests/unit_dag_visitor.cpp b/tests/unit_dag_visitor.cpp deleted file mode 100644 index c8422f4..0000000 --- a/tests/unit_dag_visitor.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include "daggy/DAGVisitor.hpp" - -#include "catch.hpp" - -TEST_CASE("DAG Visitor Tests", "[dagvisitor]") { - daggy::DAG dag; - - REQUIRE_NOTHROW(dag.addVertex(0)); - for (int i = 1; i < 10; ++i) { - dag.addVertex(i); - dag.addEdge(i-1, i); - } - - // Add an edge to make it interesting - dag.addEdge(5, 7); - - // for (auto v : dag.getRoots()) std::cout << " " << v << std::endl; - - SECTION("Basic traversal") { - daggy::DAGVisitor visitor(dag); - REQUIRE(! visitor.isComplete()); - - size_t i = 0; - while (! visitor.isComplete()) { - auto id = visitor.visitNext(); - REQUIRE(id.has_value()); - visitor.completeVisit(id.value()); - ++i; - } - REQUIRE(i == 10); - } -}