1 /** 2 * Implements depth-first search over graphs. 3 */ 4 module anansi.algorithms.dfs; 5 6 import std.exception; 7 8 import anansi.types, 9 anansi.traits, 10 anansi.container.stack; 11 12 /** 13 * Compile time test to check if a given type can be considered a DFS visitor. 14 */ 15 template isDfsVisitor (V) { 16 enum bool isDfsVisitor = is(typeof( 17 (inout int = 0) { 18 19 })); 20 } 21 22 23 /** 24 * A default implementation of the depth-first-search visitor concept. More 25 * specialised visitors can delegate the bits that they don't care about 26 * to an instance of NullVisitor without having to re-implement them. 27 * 28 * Also servers as a handy point for documenting the visitor interface. 29 */ 30 struct NullVisitor(GraphT) if (isGraph!GraphT) { 31 /// A vertex in a GraphT 32 alias Vertex = GraphT.VertexDescriptor; 33 34 /// An edge in a GraphT 35 alias Edge = GraphT.EdgeDescriptor; 36 37 /// Called when a vertex is set to its initial state, before the search. 38 void initVertex(ref const(GraphT) g, Vertex v) {}; 39 40 /// Called when a vertex is identified as the root of a depth-first spanning 41 /// tree 42 void startVertex(ref const(GraphT) g, Vertex v) {}; 43 44 /// Called when a vertex is first encountered during the search. 45 void discoverVertex(ref const(GraphT) g, Vertex v) {}; 46 47 /// Called when an edge is being expanded. 48 void examineEdge(ref const(GraphT) g, Edge e) {}; 49 50 /// Called when an edge has been identified as part of the current 51 /// spanning tree. 52 void treeEdge(ref const(GraphT) g, Edge e) {}; 53 54 /// Called when an edge has been identified as part of a cycle 55 void backEdge(ref const(GraphT) g, Edge e) {}; 56 57 /// Called when an edge crosses to a pre-existing spanning tree 58 void forwardOrCrossEdge(ref const(GraphT) g, Edge e) {}; 59 60 /// Called whan all of the adjacent nodes of a vertex have been examined. 61 void finishVertex(ref const(GraphT) g, Vertex e) {}; 62 } 63 64 /** 65 * Performs a depth-first traversal of the graph, which can be customised 66 * using a visitor object. Note that disconnected graphs will still be 67 * entirely traversed - this function will walk the spanning tree of each 68 * disconnected component (in random order). 69 * 70 * Params: 71 * GraphT = The type of the graph object to traverse. Must model the 72 * incidence graph concept. 73 * VertexDescriptorT = The descriptor type for vertices in a GraphT. 74 * VisitorT = The visitor type. 75 * ColourMapT = The type of the property map that will be used to control 76 * the graph traversal. Must model a property map that stores 77 * Colours keyed by a VertexDescriptorT. 78 */ 79 template depthFirstSearch(GraphT, 80 VertexDescriptorT, 81 VisitorT = NullVisitor!GraphT, 82 ColourMapT = Colour[VertexDescriptorT]) { 83 84 static assert (isIncidenceGraph!GraphT); 85 static assert (is(VertexDescriptorT == GraphT.VertexDescriptor)); 86 static assert (isPropertyMap!(ColourMapT, GraphT.VertexDescriptor, Colour)); 87 static assert (isDfsVisitor!VisitorT); 88 89 /** 90 * Params: 91 * graph = The graph object to traverse. 92 * root = The vertex to serve as the starting point. 93 * colourMap = The colour map used to control the expansion of edges 94 * and verices in the graph. This will be totally re- 95 * initialised before the traversal begins. 96 * visitor = A visitor object that will be notified of various events 97 * during the traversal. 98 */ 99 void depthFirstSearch(ref const(GraphT) graph, 100 VertexDescriptorT root, 101 ref ColourMapT colourMap, 102 VisitorT visitor = VisitorT.init) { 103 foreach(v; graph.vertices()) { 104 colourMap[v] = Colour.White; 105 visitor.initVertex(graph, v); 106 } 107 108 visitor.startVertex(graph, root); 109 depthFirstVisit(graph, root, colourMap, visitor); 110 111 foreach(v; graph.vertices()) { 112 if (colourMap[v] == Colour.White) { 113 visitor.startVertex(graph, root); 114 depthFirstVisit(graph, v, colourMap, visitor); 115 } 116 } 117 } 118 } 119 120 /** 121 * Performs a depth-first traversal of the graph, which can be customised 122 * using a visitor object. The main difference between this function and 123 * depthFirstSearch is that depthFirstSearch will initialise the 124 * supplied colour map, but depthFirstVisit will use it as-is. 125 * 126 * Use this function if you need to efficiently make multiple passes over 127 * non-overlappng parts of the same graph, or depthFirstSearch if you just 128 * want to walk over the whole thing. 129 * 130 * Params: 131 * GraphT = The type of the graph object to traverse. Must model the 132 * incidence graph concept. 133 * VertexDescriptorT = The descriptor type for vertices in a GraphT. 134 * VisitorT = The visitor type. 135 * ColourMapT = The type of the property map that will be used to control 136 * the graph traversal. Must model a property map that stores 137 * Colours keyed by a VertexDescriptorT. 138 */ 139 template depthFirstVisit(GraphT, 140 VertexDescriptorT, 141 VisitorT = NullVisitor!GraphT, 142 ColourMapT = Colour[VertexDescriptorT]) { 143 144 static assert (isIncidenceGraph!GraphT); 145 static assert (is(VertexDescriptorT == GraphT.VertexDescriptor)); 146 static assert (isPropertyMap!(ColourMapT, GraphT.VertexDescriptor, Colour)); 147 static assert (isDfsVisitor!VisitorT); 148 149 /** 150 * Params: 151 * graph = The graph object to traverse. 152 * root = The vertex to serve as the starting point. 153 * colourMap = The colour map used to control the expansion of edges 154 * and verices in the graph. 155 * visitor = A visitor object that will be notified of various events 156 * during the traversal. 157 */ 158 void depthFirstVisit(ref const(GraphT) graph, 159 VertexDescriptorT root, 160 ref ColourMapT colourMap, 161 VisitorT visitor = VisitorT.init) { 162 static struct VertexInfo { 163 alias EdgeRange = typeof(graph.outEdges(VertexDescriptorT.init)); 164 VertexDescriptorT vertex; 165 EdgeRange edges; 166 } 167 168 Stack!VertexInfo stack; 169 170 // set up the initial point of departure 171 stack.push(VertexInfo(root, graph.outEdges(root))); 172 colourMap[root] = Colour.Grey; 173 visitor.discoverVertex(graph, root); 174 175 while (!stack.empty) { 176 auto u = stack.front.vertex; 177 auto edges = stack.front.edges; 178 stack.pop(); 179 180 // not using foreach because we'll need access to the modified range 181 // later on... 182 while (!edges.empty) { 183 auto e = edges.front; 184 edges.popFront(); 185 186 visitor.examineEdge(graph, e); 187 auto v = graph.target(e); 188 189 switch (colourMap[v]) { 190 case Colour.White: 191 visitor.treeEdge(graph, e); 192 colourMap[v] = Colour.Grey; 193 visitor.discoverVertex(graph, v); 194 stack.push(VertexInfo(u, edges)); 195 196 edges = graph.outEdges(v); 197 u = v; 198 break; 199 200 case Colour.Grey: 201 visitor.backEdge(graph, e); 202 break; 203 204 case Colour.Black: 205 visitor.forwardOrCrossEdge(graph, e); 206 break; 207 208 default: 209 enforce(false, "Unexpected vertex colour"); 210 } 211 } 212 213 colourMap[u] = Colour.Black; 214 visitor.finishVertex(graph, u); 215 } 216 } 217 } 218 219 // ---------------------------------------------------------------------------- 220 // 221 // ---------------------------------------------------------------------------- 222 223 version (unittest) { 224 import std.array, std.algorithm, std.conv, std.stdio; 225 import anansi.adjacencylist, anansi.traits, anansi.container.set; 226 227 auto indexOf(ValueT)(ValueT[] haystack, ValueT needle) { 228 foreach(n, v; haystack) { 229 if (v == needle) return n; 230 } 231 return -1; 232 } 233 234 bool all(RangeT, DelegateT)(RangeT range, DelegateT d = DelegateT.init) { 235 foreach (x; range) { 236 if (!d(x)) return false; 237 } 238 return true; 239 } 240 241 private struct TestGraph(GraphT) { 242 GraphT graph; 243 GraphT.VertexDescriptor[char] vertices; 244 }; 245 246 private TestGraph!GraphT MakeTestGraph(GraphT)() { 247 GraphT graph; 248 auto a = graph.addVertex('a'); 249 auto b = graph.addVertex('b'); 250 auto c = graph.addVertex('c'); 251 auto d = graph.addVertex('d'); 252 auto e = graph.addVertex('e'); 253 auto f = graph.addVertex('f'); 254 auto g = graph.addVertex('g'); 255 256 // *----------------* 257 // | | 258 // | /-> b ->\ / 259 // *--> a e -> f g 260 // \-> c ->/ 261 // \-> d 262 263 graph.addEdge(a, b, "a -> b"); graph.addEdge(b, e, "b -> e"); 264 graph.addEdge(a, c, "a -> c"); graph.addEdge(c, e, "c -> e"); 265 graph.addEdge(e, f, "e -> f"); 266 graph.addEdge(e, a, "e -> a"); 267 graph.addEdge(c, d, "c -> d"); 268 269 GraphT.VertexDescriptor[char] vertices; 270 vertices = reduce!((acc, v) { acc[graph[v]] = v; return acc; })( 271 vertices, [a, b, c, d, e, f, g]); 272 273 return TestGraph!GraphT(graph, vertices); 274 } 275 276 private alias G = AdjacencyList!(VecS, VecS, DirectedS, char, string); 277 private alias Vertex = G.VertexDescriptor; 278 private alias Edge = G.EdgeDescriptor; 279 } 280 281 unittest { 282 writeln("DFS: Vertices are discovered exactly once, and siblings discovered " ~ 283 "after children."); 284 285 static struct Visitor { 286 this(ref G graph, ref char[] discoveryOrder) { 287 _graph = &graph; 288 _discoveryOrder = &discoveryOrder; 289 } 290 291 void discoverVertex(ref const(G) g, Vertex v) { 292 (*_discoveryOrder) ~= (*_graph)[v]; 293 } 294 295 NullVisitor!G impl; 296 alias impl this; 297 298 G* _graph; 299 char[]* _discoveryOrder; 300 } 301 char[] discoveryOrder; 302 Colour[Vertex] colourMap; 303 304 auto testGraph = MakeTestGraph!G(); 305 306 depthFirstSearch(testGraph.graph, 307 testGraph.vertices['a'], 308 colourMap, 309 Visitor(testGraph.graph, discoveryOrder)); 310 311 // Assert that each vertex is examined, and examined exactly once 312 assert (discoveryOrder.length == 7, 313 "Expected 7 entries in discovery order array, got " ~ 314 to!string(discoveryOrder.length)); 315 316 auto keyExists = delegate(char v) { return indexOf(discoveryOrder, v) >= 0; }; 317 318 assert (all(testGraph.vertices.keys, keyExists), 319 "Expected all vertices to appear in the discovery order list: " ~ 320 discoveryOrder); 321 322 // Assert that the source vertex is examined 323 assert (indexOf(discoveryOrder, 'a') == 0, 324 "Expected Vertex A to be the first vertex discovered."); 325 326 // Assert that the vertices are enumerated depth first 327 assert (indexOf(discoveryOrder, 'f') < indexOf(discoveryOrder, 'c'), 328 "Expected vertex F to appear before vertex C."); 329 330 assert (indexOf(discoveryOrder, 'f') < indexOf(discoveryOrder, 'd'), 331 "Expected vertex F to appear before vertex D."); 332 } 333 334 unittest { 335 writeln("DFS: Edges in a directed graph should be examined exactly once."); 336 337 static struct Visitor { 338 this(ref int[Edge] counts) { 339 _counts = &counts; 340 } 341 342 void examineEdge(ref const(G) g, Edge e) { 343 (*_counts)[e]++; 344 } 345 346 NullVisitor!G impl; 347 alias impl this; 348 349 int[Edge]* _counts; 350 } 351 352 int[Edge] counts; 353 Colour[Vertex] colourMap; 354 355 auto testGraph = MakeTestGraph!G(); 356 357 depthFirstSearch(testGraph.graph, 358 testGraph.vertices['a'], 359 colourMap, 360 Visitor(counts)); 361 362 auto edges = Set!Edge(); 363 foreach (v; testGraph.graph.vertices) 364 edges.insert(testGraph.graph.outEdges(v)); 365 366 // Assert that each edge is discovered, and discovered exactly once 367 assert (counts.length == edges.length, 368 "Expected " ~ to!string(edges.length) ~ 369 " entries in discovery count array, got " ~ to!string(counts.length)); 370 371 auto pred = (Edge e) { return (e in counts) !is null; }; 372 assert (all(edges, pred), 373 "Every edge must appear in the discovery count array"); 374 375 assert (std.algorithm.all!("a == 1")(counts.values)); 376 } 377 378 unittest { 379 writeln("DFS: Edges in an undirected graph should be examined at least once."); 380 alias UndirectedGraph = AdjacencyList!(VecS, VecS, UndirectedS, char, string); 381 alias UEdge = UndirectedGraph.EdgeDescriptor; 382 383 static struct Visitor { 384 this(ref int[UEdge] counts) { 385 _counts = &counts; 386 } 387 388 void examineEdge(ref const(UndirectedGraph) g, UEdge e) { 389 (*_counts)[e]++; 390 } 391 392 NullVisitor!UndirectedGraph impl; 393 alias impl this; 394 395 int[UEdge]* _counts; 396 } 397 398 auto testGraph = MakeTestGraph!UndirectedGraph(); 399 400 int[UEdge] counts; 401 Colour[Vertex] colourMap; 402 403 depthFirstSearch(testGraph.graph, 404 testGraph.vertices['a'], 405 colourMap, 406 Visitor(counts)); 407 408 auto edges = Set!UEdge(); 409 foreach (v; testGraph.graph.vertices) 410 edges.insert(testGraph.graph.outEdges(v)); 411 412 // Assert that each edge is discovered at least once 413 assert (counts.length == edges.length, 414 "Expected " ~ to!string(edges.length) ~ 415 " entries in discovery count array, got " ~ to!string(counts.length)); 416 417 auto pred = (UEdge e) { return (e in counts) !is null; }; 418 assert (all(edges, pred), 419 "Every edge must appear in the examination count array"); 420 421 assert (std.algorithm.all!("a > 0")(counts.values)); 422 } 423 424 unittest { 425 writeln("DFS: Tree & non-tree vertices should be identified."); 426 427 static struct Visitor { 428 this(ref Edge[] treeEdges, 429 ref Edge[] backEdges, 430 ref Edge[] forwardEdges) { 431 _treeEdges = &treeEdges; 432 _backEdges = &backEdges; 433 _forwardEdges = &forwardEdges; 434 } 435 436 /// Called when an edge has been identified as part of the current 437 /// spanning tree. 438 void treeEdge(ref const(G) g, Edge e) { 439 (*_treeEdges) ~= e; 440 }; 441 442 /// Called when an edge has been identified as part of a cycle 443 void backEdge(ref const(G) g, Edge e) { 444 (*_backEdges) ~= e; 445 }; 446 447 void forwardOrCrossEdge(ref const(G) g, Edge e) { 448 (*_forwardEdges) ~= e; 449 }; 450 451 NullVisitor!G impl; 452 alias impl this; 453 454 private Edge[]* _treeEdges; 455 private Edge[]* _backEdges; 456 private Edge[]* _forwardEdges; 457 } 458 459 Edge[] treeEdges, backEdges, fwdEdges; 460 Colour[Vertex] colourMap; 461 462 auto testGraph = MakeTestGraph!G(); 463 auto g = &testGraph.graph; 464 465 depthFirstSearch(testGraph.graph, 466 testGraph.vertices['a'], 467 colourMap, 468 Visitor(treeEdges, backEdges, fwdEdges)); 469 470 assert (treeEdges.length == 5, 471 "Expected 5 tree edges, got " ~ to!string(treeEdges.length)); 472 473 assert (backEdges.length == 1, 474 "Expected 1 back edge, got " ~ to!string(backEdges.length)); 475 476 auto backEdge = backEdges[0]; 477 assert (g.source(backEdge) == testGraph.vertices['e'], 478 "Expected the source of the fwd edge to be vertex e, instead was " ~ 479 (*g)[g.source(backEdge)]); 480 481 assert (g.target(backEdge) == testGraph.vertices['a'], 482 "Expected the target of the fwd edge to be vertex a, instead was " ~ 483 (*g)[g.target(backEdge)]); 484 485 assert (fwdEdges.length == 1, 486 "Expected 1 forward/cross edge, got " ~ to!string(fwdEdges.length)); 487 488 auto fwdEdge = fwdEdges[0]; 489 assert (g.source(fwdEdge) == testGraph.vertices['c'], 490 "Expected the source of the fwd edge to be vertex c, instead was " ~ 491 (*g)[g.source(fwdEdge)]); 492 493 assert (g.target(fwdEdge) == testGraph.vertices['e'], 494 "Expected the target of the fwd edge to be vertex c, instead was " ~ 495 (*g)[g.target(fwdEdge)]); 496 } 497 498 unittest { 499 writeln("DFS: Vertices should be finished exactly once."); 500 501 static struct Visitor { 502 this(ref int[Vertex] finishCounts) { 503 _counts = &finishCounts; 504 } 505 506 void finishVertex(ref const(G) g, Vertex v) { 507 (*_counts)[v]++; 508 } 509 510 NullVisitor!G impl; 511 alias impl this; 512 513 G* _graph; 514 int[Vertex]* _counts; 515 } 516 517 int[Vertex] counts; 518 Colour[Vertex] colourMap; 519 520 auto testGraph = MakeTestGraph!G(); 521 522 depthFirstSearch(testGraph.graph, 523 testGraph.vertices['a'], 524 colourMap, 525 Visitor(counts)); 526 527 // Assert that each vertex is discovered, and discovered exactly once 528 auto vertices = array(testGraph.graph.vertices); 529 assert (counts.length == vertices.length, 530 "Expected " ~ to!string(vertices.length) ~ 531 " entries in edge finished array, got " ~ to!string(counts.length)); 532 533 auto pred = (Vertex v) { return (v in counts) !is null; }; 534 assert (all(testGraph.vertices.values, pred), 535 "Every vertex must appear in the finish count array"); 536 537 assert (std.algorithm.all!("a == 1")(counts.values), 538 "Vertices must be finished exactly once."); 539 }