This is probably the stupidest implementation ever, but it works. To be fixed!
This commit is contained in:
@@ -3,8 +3,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
@@ -15,31 +15,44 @@
|
|||||||
|
|
||||||
namespace daggy {
|
namespace daggy {
|
||||||
|
|
||||||
|
enum class VertexState {
|
||||||
|
UNVISITED = 0,
|
||||||
|
VISITING,
|
||||||
|
VISITED
|
||||||
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class DAG {
|
class DAG {
|
||||||
public:
|
public:
|
||||||
DAG() {}
|
DAG() {}
|
||||||
|
|
||||||
// Vertices
|
// Vertices
|
||||||
void addVertex(T id);
|
void addVertex(T id, VertexState state = VertexState::UNVISITED);
|
||||||
void dropVertex(const T & id);
|
void dropVertex(const T & id);
|
||||||
const std::unordered_set<T> & getChildren(const T & id) const;
|
std::set<T> getVertices() const;
|
||||||
const std::unordered_set<T> & getRoots() const;
|
std::set<T> getParents(const T & id) const;
|
||||||
|
std::set<T> getChildren(const T & id) const;
|
||||||
|
|
||||||
// Edges
|
// Edges
|
||||||
void addEdge(const T & src, const T & dst);
|
void addEdge(const T & src, const T & dst);
|
||||||
void dropEdge(const T & src, const T & dst);
|
void dropEdge(const T & src, const T & dst);
|
||||||
|
bool hasPath(const T & from, const T & to) const;
|
||||||
// Returns the path from {from} to {to}
|
|
||||||
std::deque<T> shortestPath(const T & from, const T & to);
|
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
bool empty() const;
|
bool empty() const;
|
||||||
|
|
||||||
|
// Traversal
|
||||||
|
void setVisitState(VertexState state);
|
||||||
|
VertexState getVertexState(const T & id) const;
|
||||||
|
bool allVisited() const;
|
||||||
|
|
||||||
|
std::optional<const T> visitNext();
|
||||||
|
void completeVisit(const T & id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<T, std::unordered_set<T>> vertices_;
|
std::unordered_map<T, VertexState> vertices_;
|
||||||
std::unordered_set<T> roots_;
|
std::set<std::pair<T, T>> edges_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "DAGImpl.hpp"
|
#include "DAGImpl.hpp"
|
||||||
|
|||||||
@@ -9,67 +9,128 @@ bool DAG<T>::empty() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void DAG<T>::addVertex(T id) {
|
void DAG<T>::addVertex(T id, VertexState state) {
|
||||||
if (vertices_.find(id) != vertices_.end())
|
if (vertices_.find(id) != vertices_.end())
|
||||||
throw std::runtime_error("Vertex already exists in graph");
|
throw std::runtime_error("Vertex already exists in graph");
|
||||||
vertices_[id];
|
vertices_[id] = state;
|
||||||
roots_.insert(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void DAG<T>::dropVertex(const T & id) {
|
void DAG<T>::dropVertex(const T & id) {
|
||||||
vertices_.extract(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<typename T>
|
template<typename T>
|
||||||
void DAG<T>::dropEdge(const T & from, const T & to) {
|
void DAG<T>::dropEdge(const T & from, const T & to) {
|
||||||
auto & src = vertices_.at(from);
|
for (auto it = edges_.begin(); it != edges_.end(); ) {
|
||||||
src.extract(to);
|
if (it->first == from and it->second == to) {
|
||||||
roots_.extract(to);
|
it = edges_.erase(it);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void DAG<T>::addEdge(const T & from, const T & to) {
|
void DAG<T>::addEdge(const T & from, const T & to) {
|
||||||
auto & src = vertices_.at(from);
|
if (hasPath(to, from))
|
||||||
|
throw std::runtime_error("Adding edge would result in a cycle");
|
||||||
if (shortestPath(to, from).size() > 1) {
|
edges_.emplace(from, to);
|
||||||
throw std::runtime_error("Unable to add edge that would result in a cycle");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the edge
|
|
||||||
src.insert(to);
|
|
||||||
roots_.extract(to);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::deque<T> DAG<T>::shortestPath(const T & from, const T & to) {
|
bool DAG<T>::hasPath(const T & from, const T & to) const {
|
||||||
std::deque<T> subpath;
|
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);
|
if (hasPath(pr.second, to)) return true;
|
||||||
|
|
||||||
for (const auto & cid : src) {
|
|
||||||
auto pth = shortestPath(cid, to);
|
|
||||||
if (subpath.size() == 0 or subpath.size() > pth.size())
|
|
||||||
subpath.swap(pth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subpath.size() == 0) return subpath;
|
return false;
|
||||||
|
|
||||||
subpath.push_front(from);
|
|
||||||
|
|
||||||
return subpath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
const std::unordered_set<T> & DAG<T>::getChildren(const T & id) const {
|
std::set<T> DAG<T>::getVertices() const {
|
||||||
|
std::set<T> vertices;
|
||||||
|
for (const auto & [v, _] : vertices_) {
|
||||||
|
vertices.insert(v);
|
||||||
|
}
|
||||||
|
return vertices;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::set<T> DAG<T>::getParents(const T & id) const {
|
||||||
|
std::set<T> parents;
|
||||||
|
for (const auto & [p, c] : edges_) {
|
||||||
|
if (c == id) parents.push_back(p);
|
||||||
|
}
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::set<T> DAG<T>::getChildren(const T & id) const {
|
||||||
|
std::set<T> children;
|
||||||
|
for (const auto & [p, c] : edges_) {
|
||||||
|
if (p == id) children.push_back(c);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void DAG<T>::setVisitState(VertexState state) {
|
||||||
|
for (auto & [v, s] : vertices_) s = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
VertexState DAG<T>::getVertexState(const T & id) const {
|
||||||
return vertices_.at(id);
|
return vertices_.at(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
const std::unordered_set<T> & DAG<T>::getRoots() const {
|
bool DAG<T>::allVisited() const {
|
||||||
return roots_;
|
for (const auto & [_, s] : vertices_) {
|
||||||
|
if (s != VertexState::VISITED) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::optional<const T> DAG<T>::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<typename T>
|
||||||
|
void DAG<T>::completeVisit(const T & id) {
|
||||||
|
auto it = vertices_.find(id);
|
||||||
|
if (it == vertices_.end()) return;
|
||||||
|
it->second = VertexState::VISITED;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <deque>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <iterator>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "DAG.hpp"
|
|
||||||
|
|
||||||
/*
|
|
||||||
The DAG structure in daggy is just to ensure that tasks are run
|
|
||||||
in the correct dependent order.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace daggy {
|
|
||||||
template<typename T>
|
|
||||||
class DAGVisitor {
|
|
||||||
public:
|
|
||||||
DAGVisitor(const DAG<T> & dag, std::unordered_set<T> roots = {});
|
|
||||||
|
|
||||||
std::optional<const T> 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<T> & dag_;
|
|
||||||
std::deque<T> queued_;
|
|
||||||
std::unordered_set<T> processing_;
|
|
||||||
std::unordered_set<T> complete_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "DAGVisitorImpl.hpp"
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
template<typename T>
|
|
||||||
DAGVisitor<T>::DAGVisitor(const DAG<T> & dag, std::unordered_set<T> 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<typename T>
|
|
||||||
std::optional<const T> DAGVisitor<T>::visitNext() {
|
|
||||||
if (queued_.empty()) return {};
|
|
||||||
const auto & id = queued_.front();
|
|
||||||
processing_.insert(id);
|
|
||||||
queued_.pop_front();
|
|
||||||
return id;
|
|
||||||
}; // Get the next ID
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void DAGVisitor<T>::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<typename T>
|
|
||||||
bool DAGVisitor<T>::isComplete() const {
|
|
||||||
bool complete = queued_.empty();
|
|
||||||
for (const auto & p : processing_) {
|
|
||||||
complete &= dag_.getChildren(p).empty();
|
|
||||||
}
|
|
||||||
return complete;
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "daggy/DAG.hpp"
|
#include "daggy/DAG.hpp"
|
||||||
#include "daggy/DAGVisitor.hpp"
|
|
||||||
|
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
|
||||||
@@ -17,27 +16,69 @@ TEST_CASE("DAG Construction Tests", "[dag]") {
|
|||||||
dag.addEdge(i-1, i);
|
dag.addEdge(i-1, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
REQUIRE(dag.getRoots().size() == 1);
|
|
||||||
REQUIRE(dag.size() == 10);
|
REQUIRE(dag.size() == 10);
|
||||||
REQUIRE(! dag.empty());
|
REQUIRE(! dag.empty());
|
||||||
|
|
||||||
// Cannot add an edge that would result in a cycle
|
// Cannot add an edge that would result in a cycle
|
||||||
REQUIRE_THROWS(dag.addEdge(9, 5));
|
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<int> dag;
|
daggy::DAG<int> dag;
|
||||||
|
|
||||||
dag.addVertex(0);
|
const int N_VERTICES = 10;
|
||||||
for (int i = 1; i < 10; ++i) {
|
|
||||||
dag.addVertex(i);
|
for (int i = 0; i < N_VERTICES; ++i) { dag.addVertex(i); }
|
||||||
dag.addEdge(i-1, i);
|
|
||||||
|
/*
|
||||||
|
0 ---------------------\
|
||||||
|
1 ---------- \ \
|
||||||
|
2 ---- 3 ---- > 5 -------> 6 -----> 7
|
||||||
|
4 -------------------------------/
|
||||||
|
8 --> 9
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::vector<std::pair<int,int>> 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") {
|
SECTION("Baisc Traversal") {
|
||||||
REQUIRE(dag.shortestPath(0,9).size() == 10);
|
dag.setVisitState(daggy::VertexState::UNVISITED);
|
||||||
|
std::vector<int> 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);
|
std::cout << "ORDER:";
|
||||||
REQUIRE(dag.shortestPath(0,9).size() == 7);
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "daggy/DAGVisitor.hpp"
|
|
||||||
|
|
||||||
#include "catch.hpp"
|
|
||||||
|
|
||||||
TEST_CASE("DAG Visitor Tests", "[dagvisitor]") {
|
|
||||||
daggy::DAG<int> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user