#pragma once #include <map> #include <list> #include <memory> #include <sstream> #include <string> #include <stdexcept> #include <algorithm> #include "log.hpp" namespace villas { namespace graph { // use vector indices as identifiers // forward declarations class Edge; class Vertex; class Vertex { template<typename VertexType, typename EdgeType> friend class DirectedGraph; public: using Identifier = std::size_t; friend std::ostream& operator<< (std::ostream& stream, const Vertex& vertex) { return stream << vertex.id; } bool operator==(const Vertex& other) { return this->id == other.id; } private: Identifier id; // HACK: how to resolve this circular type dependency? std::list<std::size_t> edges; }; class Edge { template<typename VertexType, typename EdgeType> friend class DirectedGraph; public: using Identifier = std::size_t; friend std::ostream& operator<< (std::ostream& stream, const Edge& edge) { return stream << edge.id; } bool operator==(const Edge& other) { return this->id == other.id; } private: Identifier id; Vertex::Identifier from; Vertex::Identifier to; }; template<typename VertexType = Vertex, typename EdgeType = Edge> class DirectedGraph { public: using VertexIdentifier = Vertex::Identifier; using EdgeIdentifier = Edge::Identifier; DirectedGraph(const std::string& name = "DirectedGraph") : lastVertexId(0), lastEdgeId(0) { logger = loggerGetOrCreate(name); } std::shared_ptr<VertexType> getVertex(VertexIdentifier vertexId) const { if(vertexId < 0 or vertexId >= lastVertexId) throw std::invalid_argument("vertex doesn't exist"); // cannot use [] operator, because creates non-existing elements // at() will throw std::out_of_range if element does not exist return vertices.at(vertexId); } std::shared_ptr<EdgeType> getEdge(EdgeIdentifier edgeId) const { if(edgeId < 0 or edgeId >= lastEdgeId) throw std::invalid_argument("edge doesn't exist"); // cannot use [] operator, because creates non-existing elements // at() will throw std::out_of_range if element does not exist return edges.at(edgeId); } std::size_t getEdgeCount() const { return edges.size(); } std::size_t getVertexCount() const { return vertices.size(); } VertexIdentifier addVertex(std::shared_ptr<VertexType> vertex) { vertex->id = lastVertexId++; logger->debug("New vertex: {}", *vertex); vertices[vertex->id] = vertex; return vertex->id; } EdgeIdentifier addEdge(std::shared_ptr<EdgeType> edge, VertexIdentifier fromVertexId, VertexIdentifier toVertexId) { // allocate edge id edge->id = lastEdgeId++; // connect it edge->from = fromVertexId; edge->to = toVertexId; logger->debug("New edge {}: {} -> {}", *edge, edge->from, edge->to); // this is a directed graph, so only push edge to starting vertex getVertex(edge->from)->edges.push_back(edge->id); // add new edge to graph edges[edge->id] = edge; return edge->id; } EdgeIdentifier addDefaultEdge(VertexIdentifier fromVertexId, VertexIdentifier toVertexId) { // create a new edge std::shared_ptr<EdgeType> edge(new EdgeType); return addEdge(edge, fromVertexId, toVertexId); } void removeEdge(EdgeIdentifier edgeId) { auto edge = getEdge(edgeId); auto startVertex = getVertex(edge->from); // remove edge only from starting vertex (this is a directed graph) logger->debug("Remove edge {} from vertex {}", edgeId, edge->from); startVertex->edges.remove(edgeId); logger->debug("Remove edge {}", edgeId); edges.erase(edgeId); } void removeVertex(VertexIdentifier vertexId) { // delete every edge that start or ends at this vertex auto it = edges.begin(); while(it != edges.end()) { auto& [edgeId, edge] = *it; bool removeEdge = false; if(edge->to == vertexId) { logger->debug("Remove edge {} from vertex {}'s edge list", edgeId, edge->from); removeEdge = true; auto startVertex = getVertex(edge->from); startVertex->edges.remove(edge->id); } if((edge->from == vertexId) or removeEdge) { logger->debug("Remove edge {}", edgeId); // remove edge from global edge list it = edges.erase(it); } else { ++it; } } logger->debug("Remove vertex {}", vertexId); vertices.erase(vertexId); } const std::list<EdgeIdentifier>& vertexGetEdges(VertexIdentifier vertexId) const { return getVertex(vertexId)->edges; } bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId, std::list<EdgeIdentifier>& path) { if(fromVertexId == toVertexId) { // arrived at the destination return true; } else { auto fromVertex = getVertex(fromVertexId); for(auto& edgeId : fromVertex->edges) { auto edge = getEdge(edgeId); // loop detection bool loop = false; for(auto& edgeIdInPath : path) { auto edgeInPath = getEdge(edgeIdInPath); if(edgeInPath->from == edgeId) { loop = true; break; } } if(loop) { logger->debug("Loop detected via edge {}", edgeId); continue; } // remember the path we're investigating to detect loops path.push_back(edgeId); // recursive, depth-first search if(getPath(edge->to, toVertexId, path)) { // path found, we're done return true; } else { // tear down path that didn't lead to the destination path.pop_back(); } } } return false; } void dump() { logger->info("Vertices:"); for(auto& [vertexId, vertex] : vertices) { // format connected vertices into a list std::stringstream ssEdges; for(auto& edge : vertex->edges) { ssEdges << getEdge(edge)->to << " "; } logger->info(" {} connected to: {}", *vertex, ssEdges.str()); } logger->info("Edges:"); for(auto& [edgeId, edge] : edges) { logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); } } private: VertexIdentifier lastVertexId; EdgeIdentifier lastEdgeId; std::map<VertexIdentifier, std::shared_ptr<VertexType>> vertices; std::map<EdgeIdentifier, std::shared_ptr<EdgeType>> edges; SpdLogger logger; }; } // namespacae graph } // namespace villas