/** * SPDX license identifier: MPL-2.0 * * Copyright (C) 2012, BMW AG * * This file is part of GENIVI Project AudioManager. * * Contributions are licensed to the GENIVI Alliance under one or more * Contribution License Agreements. * * \copyright * This Source Code Form is subject to the terms of the * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with * this file, You can obtain one at http://mozilla.org/MPL/2.0/. * * * \author Aleksandar Donchev, aleksander.donchev@partner.bmw.de BMW 2014 * * \file CAmGraph.h * For further information see http://www.genivi.org/. * */ #ifndef GRAPH_H #define GRAPH_H #include #include #include #include #include #include #include #include #include #include #include #include namespace am { /** * Graph element status. */ typedef enum : uint8_t { GES_NOT_VISITED, GES_IN_PROGRESS, GES_VISITED } am_GraphElementStatus_e; /** * Callback parameter telling on which position in the path we are. */ typedef enum : uint8_t { GRAPH_PATH_START, // at the beginning of the path GRAPH_PATH_MIDDLE, // in middle of the path GRAPH_PATH_END // at the end of the path } am_GraphPathPosition_e; /** * This class is base class for nodes and vertices. */ class CAmGraphElement { am_GraphElementStatus_e mStatus; //!< item status public: CAmGraphElement() : mStatus(GES_NOT_VISITED) { } ~CAmGraphElement() { } /** * Setter and getter. */ void setStatus(const am_GraphElementStatus_e s) { mStatus = s; } am_GraphElementStatus_e getStatus() const { return mStatus; } }; template class CAmNode : public CAmGraphElement { uint16_t mIndex; //!< uint16_t index used for direct access NodeData mData; //!< NodeData user data public: CAmNode(const NodeData &in) : CAmGraphElement() , mIndex(0) , mData(in) { } CAmNode(const NodeData &in, const uint16_t index) : CAmGraphElement() , mIndex(index) , mData(in) { } ~CAmNode() { } /** * Setters and getters. */ NodeData &getData() { return mData; } const NodeData &getData() const { return mData; } uint16_t getIndex() const { return mIndex; } void setIndex(uint16_t index) { mIndex = index; } }; template class CAmVertex : public CAmGraphElement { CAmNode *mpNode; //!< CAmNode* pointer to a node VertexData mVertexData; //!< VertexData vertex user data uint16_t mWeight; //!< uint16_t a positive value used in the shortest path algorithms public: CAmVertex(CAmNode *aNode, const VertexData &vertexData, const uint16_t weight) : CAmGraphElement() , mpNode(aNode) , mVertexData(vertexData) , mWeight(weight) { } ~CAmVertex() { } /** * Setters and getters. */ CAmNode *getNode() const { return mpNode; } VertexData &getData() { return mVertexData; } uint16_t getWeight() const { return mWeight; } void setWeight(const uint16_t weight) { mWeight = weight; } }; /** * Class representing a directed or undirected graph. It contains nodes and connections. * T, V are types for custom user data. */ template class CAmGraph { typedef typename std::vector *> CAmListNodePtrs; typedef typename std::list > CAmListVertices; typedef typename std::list >::iterator CAmListVerticesItr; typedef typename std::list >::const_iterator CAmListVerticesItrConst; typedef typename std::list CAmNodesAdjList; typedef typename std::list::iterator CAmNodesAdjListItr; typedef typename std::list::const_iterator CAmNodesAdjListItrConst; typedef typename std::list > CAmListNodes; typedef typename std::list >::iterator CAmListNodesItr; typedef typename std::list >::const_iterator CAmListNodesItrConst; typedef typename std::vector *> CAmNodeReferenceList; typedef typename std::vector CAmVertexReferenceList; CAmListNodes mStoreNodes; //!< CAmListNodes list with all nodes CAmNodesAdjList mStoreAdjList; //!< CAmNodesAdjList adjacency list CAmNodeReferenceList mPointersNodes; //!< CAmNodeReferenceList vector with pointers to nodes for direct access CAmVertexReferenceList mPointersAdjList; //!< CAmVertexReferenceList vector with pointers to vertices for direct access bool mIsCyclic; //!< bool the graph has cycles or not struct IterateThroughAllNodesDelegate { CAmNode *source; CAmNode *destination; CAmNodeReferenceList visited; std::function *)> shouldVisitNode; std::function *)> willVisitNode; std::function *)> didVisitNode; std::function didFindPath; }; struct VisitNodeDelegate { CAmNode *source; CAmNode *destination; std::function &)> visitedNode; }; /** * Updates the node indexes after adding or removing nodes. * * @param fromIndex updates all nodes from given index. */ void updateIndexes(const int16_t fromIndex) { if ( fromIndex < mPointersNodes.size()) { for (auto iter = mPointersNodes.begin() + fromIndex; iter != mPointersNodes.end(); iter++) { (*iter)->setIndex(iter - mPointersNodes.begin()); } } } /** * Finds the shortest path and the minimal weights from given node. * * @param node start node. * @param minDistance vector with all result distances. * @param previous vector with previous nodes. */ typedef uint16_t vertex_t; typedef uint16_t weight_t; void findShortestPathsFromNode(const CAmNode &node, std::vector &minDistance, std::vector *> &previous) { typename CAmListVertices::const_iterator nIter; CAmListVertices *neighbors; weight_t dist, weight, v, distanceThroughU; CAmNode *pU; CAmVertex *pVertex; CAmNode *pDstNode; size_t n = mPointersAdjList.size(); std::set *> > vertexQueue; minDistance.clear(); minDistance.resize(n, std::numeric_limits::max()); minDistance[node.getIndex()] = 0; previous.clear(); previous.resize(n, NULL); vertexQueue.insert(std::make_pair(minDistance[node.getIndex()], (CAmNode *) & node)); while (!vertexQueue.empty()) { dist = vertexQueue.begin()->first; pU = vertexQueue.begin()->second; vertexQueue.erase(vertexQueue.begin()); // todo: terminate the search at this position if you want the path to a target node ( if(pU==target)break; ) // Visit each edge exiting u neighbors = mPointersAdjList[pU->getIndex()]; nIter = neighbors->begin(); for (; nIter != neighbors->end(); nIter++) { pVertex = (CAmVertex *) & (*nIter); pDstNode = pVertex->getNode(); v = pDstNode->getIndex(); weight = pVertex->getWeight(); distanceThroughU = dist + weight; if (distanceThroughU < minDistance[pDstNode->getIndex()]) { vertexQueue.erase(std::make_pair(minDistance[v], pDstNode)); minDistance[v] = distanceThroughU; previous[v] = pU; vertexQueue.insert(std::make_pair(minDistance[v], pDstNode)); } } } } /** * Constructs a path to given node after findShortestsPathsFromNode has been called. * * @param node end node. * @param previous vector with previous nodes. * @param result result path. */ void constructShortestPathTo(const CAmNode &node, const std::vector *> &previous, CAmListNodePtrs &result) { CAmNode *vertex = (CAmNode *) & node; int i = 0; while ((vertex = previous[vertex->getIndex()]) != NULL ) { result.insert(result.begin(), vertex); i++; } if (i) { result.push_back((CAmNode *) & node); } } /** * Calls a function with every node from this path after findShortestsPathsFromNode has been called. * The construction of the path is delegated to the caller. * * @param node end node. * @param previous vector with previous nodes. * @param cb callback which is mostly used for constructing. */ void constructShortestPathTo(const CAmNode &node, const std::vector *> &previous, std::function &)> cb) { CAmNode *vertex = (CAmNode *) & node; CAmNode *prev = vertex; int i = 0; while ((vertex = previous[vertex->getIndex()]) != NULL ) { cb(i == 0 ? GRAPH_PATH_START : GRAPH_PATH_MIDDLE, *prev); prev = vertex; i++; } if (i) { cb(GRAPH_PATH_END, *prev); } } /** * Iterate through the nodes and generate all paths to given node. * * @param dst end node. * @param visited vector with current path. * @param delegate enumeration delegate. */ void findAllPaths(IterateThroughAllNodesDelegate &delegate) { CAmListVertices *nodes = mPointersAdjList[delegate.visited.back()->getIndex()]; CAmListVerticesItrConst vItr(nodes->begin()); CAmVertex *pNextVertex; CAmNode *pNextNode; for (; vItr != nodes->end(); ++vItr) { pNextVertex = (CAmVertex *) & (*vItr); pNextNode = pNextVertex->getNode(); if ( pNextNode->getStatus() != GES_NOT_VISITED || !delegate.shouldVisitNode(pNextNode) ) { continue; } if (pNextNode == delegate.destination) { delegate.willVisitNode(pNextNode); pNextNode->setStatus(GES_IN_PROGRESS); delegate.visited.push_back(pNextNode); // notify observer delegate.didFindPath(delegate.visited); // remove last node from the list auto last = delegate.visited.end() - 1; delegate.visited.erase(last); pNextNode->setStatus(GES_NOT_VISITED); delegate.didVisitNode(pNextNode); break; } } vItr = nodes->begin(); // bfs like loop for (; vItr != nodes->end(); ++vItr) { pNextVertex = (CAmVertex *) & (*vItr); pNextNode = pNextVertex->getNode(); if (pNextNode->getStatus() != GES_NOT_VISITED || pNextNode == delegate.destination || !delegate.shouldVisitNode(pNextNode) ) { continue; } delegate.willVisitNode(pNextNode); pNextNode->setStatus(GES_IN_PROGRESS); delegate.visited.push_back(pNextNode); findAllPaths(delegate); // remove last node from the list auto last = delegate.visited.end() - 1; delegate.visited.erase(last); pNextNode->setStatus(GES_NOT_VISITED); delegate.didVisitNode(pNextNode); } } public: explicit CAmGraph(const std::vector &v) : mStoreNodes() , mStoreAdjList() , mPointersNodes() , mPointersAdjList() { typedef typename std::vector::const_iterator inItr; inItr itr(v.begin()); for (; itr != v.end(); ++itr) { addNode(*itr); } mIsCyclic = false; } CAmGraph() : mStoreNodes() , mStoreAdjList() , mPointersNodes() , mPointersAdjList() , mIsCyclic(false){} ~CAmGraph(){} const CAmListNodes &getNodes() const { return mStoreNodes; } const CAmVertexReferenceList &getVertexList() const { return mPointersAdjList; } /** * Returns pointer to a node which data is equal to the given. * @return pointer to a node or NULL. */ const CAmNode *findNode(const T &in) { typename CAmNodeReferenceList::const_iterator itr(mPointersNodes.begin()); for (; itr != mPointersNodes.end(); ++itr) { if ((*itr)->getData() == in) { return (*itr); } } return NULL; } /** * Returns pointer to a vertex which two ends are equal to the given nodes. * @return pointer to a vertex or NULL. */ const CAmVertex *findVertex(const CAmNode &edge1, const CAmNode &edge2) const { const CAmNode *pEdge2 = (CAmNode *) & edge2; const CAmListVertices *list = mPointersAdjList[edge1.getIndex()]; CAmListVerticesItrConst result = std::find_if(list->begin(), list->end(), [&](const CAmVertex &refObject){ return refObject.getNode() == pEdge2; }); if (result != list->end()) { return (CAmVertex *) & (*result); } return NULL; } bool hasCycles() const { return mIsCyclic; } /** * Adds a new node to the graph with given user data. * @return reference to the newly inserted node. */ CAmNode &addNode(const T &in) { size_t index = mStoreNodes.size(); mStoreNodes.emplace_back(in, index); mStoreAdjList.emplace_back(); mPointersNodes.push_back(&mStoreNodes.back()); mPointersAdjList.push_back(&mStoreAdjList.back()); return mStoreNodes.back(); } /** * Removes a vertex with two ends equal to the given nodes . */ void removeVertex(const CAmNode &edge1, const CAmNode &edge2) { const CAmListVertices *list = mPointersAdjList[edge1.getIndex()]; CAmListVerticesItr iter = std::find_if(list->begin(), list->end(), [&edge2](const CAmVertex &refVertex){ return (refVertex.getNode() == &edge2); }); if (iter != list->end()) { list->erase(iter); } } /** * Removes all vertices to given node . */ void removeAllVerticesToNode(const CAmNode &node) { auto comparator = [&node](const CAmVertex &refVertex){ return (refVertex.getNode() == &node); }; auto itr = mPointersAdjList.begin(); for (; itr != mPointersAdjList.end(); itr++) { CAmListVertices *vertices = *itr; auto iterVert = std::find_if(vertices->begin(), vertices->end(), comparator); if (iterVert != vertices->end()) { vertices->erase(iterVert); } } } /** * Removes a node with given user data . */ void removeNode(const T &in) { CAmNode *node = findNode(in); if (node != NULL) { removeNode(*node); } } /** * Removes the given node from the graph . */ void removeNode(const CAmNode &node) { uint16_t index = node.getIndex(); removeAllVerticesToNode(node); mPointersAdjList.erase(mPointersAdjList.begin() + index); mPointersNodes.erase(mPointersNodes.begin() + index); auto iter = std::find_if(mStoreNodes.begin(), mStoreNodes.end(), [&node](const CAmNode &otherNode){ return &otherNode == &node; }); if (iter != mStoreNodes.end()) { mStoreNodes.erase(iter); } updateIndexes(index); } /** * Connect first with last node and set user data and weight to the vertex. */ void connectNodes(const CAmNode &first, const CAmNode &last, const V &vertexData, const int16_t weight = 1) { CAmListVertices *list = mPointersAdjList[first.getIndex()]; CAmNode *node = mPointersNodes[last.getIndex()]; list->emplace_back(node, vertexData, weight); } /** * Exists any vertex with two given ends. * @return TRUE on successfully changed ID. */ bool isAnyVertex(const CAmNode &edge1, const CAmNode &edge2) const { return findVertex(edge1, edge2) != NULL; } /** * Sets the status of all nodes and vertices to GES_NOT_VISITED. */ void reset() { // set all nodes to GES_NOT_VISITED std::for_each(mPointersNodes.begin(), mPointersNodes.end(), [](CAmNode *refNode){ if (refNode->getStatus() != GES_NOT_VISITED) { refNode->setStatus(GES_NOT_VISITED); } }); // set all vertices to GES_NOT_VISITED auto action = [](CAmVertex &refVertex){ if (refVertex.getStatus() != GES_NOT_VISITED) { refVertex.setStatus(GES_NOT_VISITED); } }; auto itr1(mPointersAdjList.begin()); for (; itr1 != mPointersAdjList.end(); ++itr1) { CAmListVertices *vertices = *itr1; std::for_each(vertices->begin(), vertices->end(), action); } } /** * Clears all nodes and vertices. */ void clear() { mStoreNodes.clear(); mStoreAdjList.clear(); mPointersAdjList.clear(); mPointersNodes.clear(); mPointersAdjList.clear(); } /** * Goes through all nodes and vertices and calls the callback. */ void trace(std::function &, const std::vector *> &)> cb) { std::for_each(mPointersNodes.begin(), mPointersNodes.end(), [&](CAmNode *refNode){ CAmListVertices *vertices = this->mPointersAdjList[refNode->getIndex()]; std::vector *> list; std::for_each(vertices->begin(), vertices->end(), [&list](CAmVertex &refVertex){ list.push_back(&refVertex); }); cb(*refNode, list); }); } /** * Finds the shortest path from given node to all nodes in listTargets. * * @param source start node. * @param listTargets destination nodes. * @param resultPath list with all shortest paths. */ void getShortestPath(const CAmNode &source, const CAmListNodePtrs &listTargets, std::vector &resultPath) { const size_t numberOfNodes = mPointersNodes.size(); if (numberOfNodes == 0) { return; } std::vector min_distance; std::vector *> previous; findShortestPathsFromNode(source, min_distance, previous); for (auto it = listTargets.begin(); it != listTargets.end(); it++) { CAmNode *node = *it; resultPath.emplace_back(); CAmListNodePtrs &path = resultPath.back(); constructShortestPathTo(*node, previous, path); if (path.empty()) { typename std::vector::iterator iter = resultPath.end(); resultPath.erase(--iter); } } } /** * Finds the shortest path between two nodes. * * @param source start node. * @param destination destination node. * @param resultPath list with the found shortest paths. */ void getShortestPath(const CAmNode &source, const CAmNode &destination, CAmListNodePtrs &resultPath) { const size_t numberOfNodes = mPointersNodes.size(); if (numberOfNodes == 0) { return; } std::vector min_distance; std::vector *> previous; findShortestPathsFromNode(source, min_distance, previous); constructShortestPathTo(destination, previous, resultPath); } /** * Finds the shortest path from given node to all nodes in listTargets. * Delegates the construction of the path to the caller. * * @param source start node. * @param listTargets destination nodes. * @param cb callabck. */ void getShortestPath(const CAmNode &source, const CAmListNodePtrs &listTargets, std::function &)> cb) { const size_t numberOfNodes = mPointersNodes.size(); if (numberOfNodes == 0) { return; } std::vector min_distance; std::vector *> previous; findShortestPathsFromNode(source, min_distance, previous); for (auto it = listTargets.begin(); it != listTargets.end(); it++) { CAmNode *node = *it; constructShortestPathTo(*node, previous, cb); } } /** * Finds the shortest path between two given nodes. * Delegates the construction of the path to the caller. * * @param source start node. * @param destination destination node. * @param cb callabck. */ void getShortestPath(const CAmNode &source, const CAmNode &destination, std::function &)> cb) { const size_t numberOfNodes = mPointersNodes.size(); if (numberOfNodes == 0) { return; } std::vector min_distance; std::vector *> previous; findShortestPathsFromNode(source, min_distance, previous); constructShortestPathTo(destination, previous, cb); } /** * Finds all possible paths between two given nodes. * Delegates the construction of the path to the caller. * * @param src start node. * @param dst destination node. * @param cbShouldVisitNode ask the delegate if we should proceed with the current node. * @param cbWillVisitNode tell the delegate the current node will be visited. * @param cbDidVisitNode tell the delegate the current node was visited. * @param cbDidFindPath return the path to the delegate. */ void getAllPaths(CAmNode &src, CAmNode &dst, std::function *)> cbShouldVisitNode, std::function *)> cbWillVisitNode, std::function *)> cbDidVisitNode, std::function cbDidFindPath) { IterateThroughAllNodesDelegate delegate; delegate.source = &src; delegate.destination = &dst; delegate.shouldVisitNode = cbShouldVisitNode; delegate.willVisitNode = cbWillVisitNode; delegate.didVisitNode = cbDidVisitNode; delegate.didFindPath = cbDidFindPath; delegate.visited.push_back((CAmNode *) & src); ((CAmNode *) & src)->setStatus(GES_VISITED); findAllPaths(delegate); ((CAmNode *) & src)->setStatus(GES_NOT_VISITED); } }; } #endif // ifndef GRAPH_H