1 /* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #ifndef JS_CONCURRENT_MODULE_UTILS_LOCKS_GRAPH_H 17 #define JS_CONCURRENT_MODULE_UTILS_LOCKS_GRAPH_H 18 19 #include <vector> 20 #include <map> 21 #include <string> 22 #include <limits> 23 #include <stack> 24 #include <functional> 25 26 namespace Commonlibrary::Concurrent::LocksModule { 27 28 template <class VertexId = size_t, class EdgeData = void> 29 class Graph { 30 public: 31 using EdgeDataCPtr = const EdgeData *; 32 33 private: 34 struct Vertex { 35 enum class Color { WHITE = 0, GREY = 1, BLACK = 2 }; 36 Color color = Color::WHITE; 37 VertexId data = VertexId {}; 38 }; 39 using Vertices = std::vector<Vertex>; 40 using VIdx = size_t; 41 using VColor = typename Vertex::Color; 42 using AdjacencyMatrix = std::vector<std::vector<EdgeDataCPtr>>; 43 44 static constexpr VIdx INVALID_V_IDX = std::numeric_limits<VIdx>::max(); 45 46 public: 47 // from, to, what 48 using EdgeDef = std::tuple<VertexId, VertexId, EdgeDataCPtr>; 49 using AdjacencyList = std::vector<EdgeDef>; 50 // for pretty printing paths 51 using VertexPrinter = std::function<std::string(VertexId)>; 52 using EdgePrinter = std::function<std::string(EdgeDataCPtr)>; 53 54 struct Path { 55 // vertex0 -> edge0 -> vertex1 -> edge1 -> ... 56 std::vector<VertexId> vertices; 57 std::vector<EdgeDataCPtr> edges; IsEmptyPath58 bool IsEmpty() 59 { 60 return vertices.empty(); 61 } 62 }; 63 64 static constexpr auto defaultVertexPrinter = [](VertexId vid) { return std::to_string(vid); }; 65 static constexpr auto DEFAULT_EDGE_PRINTER = []() { return " <- "; }; 66 Graph(AdjacencyList && adj)67 explicit Graph(AdjacencyList &&adj) 68 { 69 std::map<VertexId, size_t> vDataPtrToVidx; 70 size_t size = v_.size() > 0 ? v_.size() - 1 : 0; 71 for (auto &edef : adj) { 72 // from 73 auto fromVertexPtr = std::get<0>(edef); 74 if (vDataPtrToVidx.find(fromVertexPtr) == vDataPtrToVidx.end()) { 75 v_.push_back({VColor::WHITE, fromVertexPtr}); 76 vDataPtrToVidx[fromVertexPtr] = size; 77 } 78 // to 79 auto toVertexPtr = std::get<1>(edef); 80 if (vDataPtrToVidx.find(toVertexPtr) == vDataPtrToVidx.end()) { 81 v_.push_back({VColor::WHITE, toVertexPtr}); 82 vDataPtrToVidx[toVertexPtr] = size; 83 } 84 } 85 // For now let's use an adjacency matrix as the storage. 86 // Will use an adjacency list later on. 87 e_.resize(v_.size()); 88 for (auto &row : e_) { 89 row.resize(v_.size()); 90 for (auto &eid : row) { 91 eid = nullptr; 92 } 93 } 94 for (auto &edef : adj) { 95 auto fromVertexPtr = std::get<0>(edef); 96 auto toVertexPtr = std::get<1>(edef); 97 auto edgeDataPtr = std::get<2>(edef); 98 e_[vDataPtrToVidx[fromVertexPtr]][vDataPtrToVidx[toVertexPtr]] = edgeDataPtr; 99 } 100 } 101 IsValid()102 bool IsValid() 103 { 104 // NOTE: maybe its worth to add more detailed verification 105 size_t n = e_.size(); 106 if (e_.empty()) { 107 return false; 108 } 109 for (auto &row : e_) { 110 if (row.size() != n) { 111 return false; 112 } 113 } 114 return true; 115 } 116 117 static std::string CycleAsString(Path cycle, std::string prompt = "L: ", std::string terminator = "|", 118 VertexPrinter vertexPrinter = defaultVertexPrinter, 119 EdgePrinter edgePrinter = DEFAULT_EDGE_PRINTER) 120 { 121 if (cycle.vertices.empty() || cycle.vertices.size() < 1 || 122 (cycle.edges.size() != (cycle.vertices.size() - 1))) { 123 return ""; 124 } 125 std::stringstream result; 126 result << prompt; 127 auto edgeIt = cycle.edges.begin(); 128 for (auto vDataPtr : cycle.vertices) { 129 result << vertexPrinter(vDataPtr); 130 if (edgeIt != cycle.edges.end()) { 131 result << edgePrinter(*edgeIt); 132 ++edgeIt; 133 } 134 } 135 result << terminator; 136 return result.str(); 137 } 138 FindFirstCycle()139 Path FindFirstCycle() 140 { 141 // verify 142 if (!IsValid()) { 143 return Path {}; 144 } 145 // run dfs 146 for (VIdx seedIdx = 0; seedIdx < NumVertices(); ++seedIdx) { 147 if (VertexColor(seedIdx) != Graph::VColor::WHITE) { 148 continue; 149 } 150 Path cycle = RunDfsFromVertex(seedIdx); 151 if (!cycle.vertices.empty()) { 152 return cycle; 153 } 154 } 155 return Path {}; 156 } 157 158 private: VertexColor(VIdx vidx)159 VColor VertexColor(VIdx vidx) 160 { 161 return v_[vidx].color; 162 } 163 HasEdge(VIdx fromIdx,VIdx toIdx)164 bool HasEdge(VIdx fromIdx, VIdx toIdx) 165 { 166 return e_[fromIdx][toIdx] != nullptr; 167 } 168 NumVertices()169 size_t NumVertices() 170 { 171 return v_.size(); 172 } 173 Mark(VIdx vidx,VColor color)174 void Mark(VIdx vidx, VColor color) 175 { 176 v_[vidx].color = color; 177 } 178 179 enum class DfsAction { RESTART = 0, FINISH = 1, PROCEED = 2 }; 180 struct DfsState { 181 VIdx currentIdx; 182 VIdx childIdx; 183 }; 184 using DfsStack = std::stack<DfsState>; 185 DfsBuildCycleInfo(DfsStack & dfsStack,DfsState state)186 Path DfsBuildCycleInfo(DfsStack &dfsStack, DfsState state) 187 { 188 Path result; 189 VIdx originIdx = state.childIdx; 190 result.vertices.push_back(v_[state.childIdx].data); 191 result.vertices.push_back(v_[state.currentIdx].data); 192 result.edges.push_back(e_[state.currentIdx][state.childIdx]); 193 VIdx prevIdx = state.currentIdx; 194 while (!dfsStack.empty()) { 195 auto s = dfsStack.top(); 196 dfsStack.pop(); 197 result.vertices.push_back(v_[s.currentIdx].data); 198 result.edges.push_back(e_[s.currentIdx][prevIdx]); 199 prevIdx = s.currentIdx; 200 if (s.currentIdx == originIdx) { 201 break; 202 } 203 } 204 return result; 205 } 206 DfsVisitChildren(DfsStack & dfsStack,DfsState & state)207 DfsAction DfsVisitChildren(DfsStack &dfsStack, DfsState &state) 208 { 209 for (; state.childIdx < NumVertices(); ++state.childIdx) { 210 if (HasEdge(state.currentIdx, state.childIdx)) { 211 // the edge v -> child exists 212 if (VertexColor(state.childIdx) == Graph::VColor::BLACK) { 213 // the child processing is already completed 214 continue; 215 } 216 if (VertexColor(state.childIdx) == Graph::VColor::GREY) { 217 // the child is visited: means a cycle has been found 218 return DfsAction::FINISH; 219 } 220 // the child is "white", i.e. not visited yet: run DFS from it 221 VIdx nextChild = state.childIdx + 1 < NumVertices() ? state.childIdx + 1 : Graph::INVALID_V_IDX; 222 dfsStack.push(DfsState {state.currentIdx, nextChild}); 223 state.currentIdx = state.childIdx; 224 state.childIdx = 0; 225 return DfsAction::RESTART; 226 } 227 } 228 state.childIdx = Graph::INVALID_V_IDX; 229 return DfsAction::PROCEED; 230 } 231 DfsPopState(DfsStack & dfsStack,DfsState & state)232 bool DfsPopState(DfsStack &dfsStack, DfsState &state) 233 { 234 while (!dfsStack.empty()) { 235 state = dfsStack.top(); 236 dfsStack.pop(); 237 if (state.childIdx != Graph::INVALID_V_IDX) { 238 return true; 239 } 240 Mark(state.currentIdx, Graph::VColor::BLACK); 241 } 242 return false; 243 } 244 RunDfsFromVertex(VIdx seedVertexIdx)245 Path RunDfsFromVertex(VIdx seedVertexIdx) 246 { 247 DfsStack dfsStack; 248 DfsState state {seedVertexIdx, 0}; 249 bool areUnprocessedVerticesLeft = true; 250 251 while (areUnprocessedVerticesLeft) { 252 Mark(state.currentIdx, Graph::VColor::GREY); 253 DfsAction a = DfsVisitChildren(dfsStack, state); 254 switch (a) { 255 case DfsAction::FINISH: 256 return DfsBuildCycleInfo(dfsStack, state); 257 case DfsAction::RESTART: 258 continue; 259 case DfsAction::PROCEED: 260 default: 261 break; 262 } 263 Mark(state.currentIdx, Graph::VColor::BLACK); 264 areUnprocessedVerticesLeft = DfsPopState(dfsStack, state); 265 } 266 // no cycles found 267 return Path {}; 268 } 269 270 Vertices v_; 271 AdjacencyMatrix e_; 272 }; 273 274 } // namespace Commonlibrary::Concurrent::LocksModule 275 276 #endif 277