1 /** 2 * Definitions for running a breadth-first search over a graph 3 */ 4 module anansi.algorithms.bfs; 5 6 import anansi.container, 7 anansi.traits, 8 anansi.types; 9 import std.algorithm, 10 std.array, 11 std.stdio; 12 13 /** 14 * Compile time test to check if a given type can be considered a BFS visitor 15 */ 16 template isBfsVisitor (V) { 17 enum bool isBfsVisitor = is(typeof( 18 (inout int = 0) { 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) { 31 alias Vertex = GraphT.VertexDescriptor; 32 alias Edge = GraphT.EdgeDescriptor; 33 34 void initVertex(ref const(GraphT) g, Vertex v) {} 35 void discoverVertex(ref const(GraphT) g, Vertex v) {} 36 void examineVertex(ref const(GraphT) g, Vertex v) {} 37 void examineEdge(ref const(GraphT) g, Edge e) {} 38 void treeEdge(ref const(GraphT) g, Edge e) {} 39 void nonTreeEdge(ref const(GraphT) g, Edge e) {} 40 void greyTarget(ref const(GraphT) g, Edge e) {} 41 void blackTarget(ref const(GraphT) g, Edge e) {} 42 void finishVertex(ref const(GraphT) g, Vertex e) {} 43 } 44 45 /** 46 * A generic breadth-first search algorithm that can be customised using a 47 * visitor. 48 * 49 * Params: 50 * GraphT = The type of the graph object to traverse. Must model the 51 * incidence graph concept. 52 * VertexDescriptorT = The descriptor type for vertices in a GraphT. 53 * VisitorT = The visitor type. 54 * ColourMapT = The type of the property map that will be used to control 55 * the graph traversal. Must model a property map that stores 56 * Colours keyed by a VertexDescriptorT. 57 * QueueT = The type of the queue used to order the expansion of vertices. 58 * Changing the queue type can be used to customise the behaviour 59 * of the search (e.g. using a priority queue rather than the 60 * default FIFO queue. 61 */ 62 template breadthFirstSearch(GraphT, 63 VertexDescriptorT, 64 VisitorT = NullVisitor!GraphT, 65 ColourMapT = Colour[VertexDescriptorT], 66 QueueT = FifoQueue!(VertexDescriptorT)) { 67 68 static assert (isIncidenceGraph!GraphT); 69 static assert (is(VertexDescriptorT == GraphT.VertexDescriptor)); 70 static assert (isPropertyMap!(ColourMapT, GraphT.VertexDescriptor, Colour)); 71 static assert (isBfsVisitor!VisitorT); 72 static assert (isQueue!(QueueT, GraphT.VertexDescriptor)); 73 74 /** 75 * Params: 76 * graph = The graph object to traverse. 77 * root = The vertex to serve as the starting point. 78 * colourMap = The colour map used to control the expansion of edges 79 * and verices in the graph. This will be totally re- 80 * initialised before the traversal begins. 81 * visitor = A visitor object that will be notified of various events 82 * during the traversal. 83 * queue = The queue object used to order the expansion of vertices. 84 */ 85 void breadthFirstSearch(ref const(GraphT) graph, 86 VertexDescriptorT source, 87 ref ColourMapT colourMap, 88 VisitorT visitor = VisitorT.init, 89 QueueT queue = QueueT.init) { 90 foreach(v; graph.vertices) { 91 visitor.initVertex(graph, v); 92 colourMap[v] = Colour.White; 93 } 94 breadthFirstVisit(graph, source, colourMap, queue, visitor); 95 } 96 } 97 98 /** 99 * Breadth-first traversal of the graph from a given starting point. This 100 * function does not reset the colourMap, so can be efficiently used 101 * repeatedly on components of the graph. 102 * 103 * Params: 104 * GraphT = The type of the graph object to traverse. Must model the 105 * incidence graph concept. 106 * VertexDescriptorT = The descriptor type for vertices in a GraphT. 107 * VisitorT = The visitor type. 108 * ColourMapT = The type of the property map that will be used to control 109 * the graph traversal. Must model a property map that stores 110 * Colours keyed by a VertexDescriptorT. 111 * QueueT = The type of the queue used to order the expansion of vertices. 112 * Changing the queue type can be used to customise the behaviour 113 * of the search (e.g. using a priority queue rather than the 114 * default FIFO queue. 115 */ 116 template breadthFirstVisit(GraphT, 117 VertexDescriptorT, 118 VisitorT = NullVisitor!GraphT, 119 ColourMapT = Colour[VertexDescriptorT], 120 QueueT = FifoQueue!VertexDescriptorT) { 121 122 static assert (isIncidenceGraph!GraphT); 123 static assert (isPropertyMap!(ColourMapT, GraphT.VertexDescriptor, Colour)); 124 static assert (isBfsVisitor!VisitorT); 125 static assert (isQueue!(QueueT, GraphT.VertexDescriptor)); 126 127 /** 128 * Params: 129 * graph = The graph object to traverse. 130 * source = The vertex to serve as the starting point. 131 * colour = The colour map used to control the expansion of edges 132 * and verices in the graph. 133 * visitor = A visitor object that will be notified of various events 134 * during the traversal. 135 * queue = The queue object used to order the expansion of vertices. 136 */ 137 void breadthFirstVisit(ref const(GraphT) graph, 138 VertexDescriptorT source, 139 ref ColourMapT colour, 140 ref QueueT queue, 141 VisitorT visitor = VisitorT.init) { 142 colour[source] = Colour.Grey; 143 queue.push(source); visitor.discoverVertex(graph, source); 144 145 while (!queue.empty) { 146 auto u = queue.front; visitor.examineVertex(graph, u); 147 queue.pop(); 148 149 foreach (e; graph.outEdges(u)) { visitor.examineEdge(graph, e); 150 auto v = graph.target(e); 151 auto c = colour[v]; 152 if (c == Colour.White) { visitor.treeEdge(graph, e); 153 colour[v] = Colour.Grey; 154 queue.push(v); visitor.discoverVertex(graph, v); 155 } 156 else { visitor.nonTreeEdge(graph, e); 157 switch (c) { 158 case Colour.Grey: visitor.greyTarget(graph, e); 159 break; 160 161 case Colour.Black: visitor.blackTarget(graph, e); 162 break; 163 164 default: 165 assert(false, "Unexpected colour value."); 166 } 167 } 168 } 169 170 colour[u] = Colour.Black; visitor.finishVertex(graph, u); 171 } 172 } 173 } 174 175 // ---------------------------------------------------------------------------- 176 // Unit tests 177 // ---------------------------------------------------------------------------- 178 179 version (unittest) { 180 import std.algorithm, std.conv, std.stdio; 181 import anansi.adjacencylist; 182 183 auto indexOf(ValueT)(ValueT[] haystack, ValueT needle) { 184 foreach(n, v; haystack) { 185 if (v == needle) return n; 186 } 187 return -1; 188 } 189 190 bool all(RangeT, DelegateT)(RangeT range, DelegateT d = DelegateT.init) { 191 foreach (x; range) { 192 if (!d(x)) return false; 193 } 194 return true; 195 } 196 197 struct TestGraph(GraphT) { 198 GraphT graph; 199 GraphT.VertexDescriptor[char] vertices; 200 }; 201 202 TestGraph!GraphT MakeTestGraph(GraphT)() { 203 GraphT g; 204 auto a = g.addVertex('a'); 205 auto b = g.addVertex('b'); 206 auto c = g.addVertex('c'); 207 auto d = g.addVertex('d'); 208 auto e = g.addVertex('e'); 209 auto f = g.addVertex('f'); 210 211 // *----------------* 212 // | | 213 // | /-> b ->\ / 214 // *--> a e -> f 215 // \-> c ->/ 216 // \-> d 217 218 g.addEdge(a, b); g.addEdge(b, e); 219 g.addEdge(a, c); g.addEdge(c, e); 220 g.addEdge(e, f); 221 g.addEdge(e, a); 222 g.addEdge(c, d); 223 224 GraphT.VertexDescriptor[char] vertices; 225 vertices = reduce!((acc, v) { acc[g[v]] = v; return acc; })( 226 vertices, [a, b, c, d, e, f]); 227 228 return TestGraph!GraphT(g, vertices); 229 } 230 231 alias G = AdjacencyList!(VecS, VecS, DirectedS, char, string); 232 alias Vertex = G.VertexDescriptor; 233 alias Edge = G.EdgeDescriptor; 234 } 235 236 unittest { 237 writeln("BFS: Vertices examined exactly once, and siblings examined " ~ 238 "before children."); 239 240 static struct Visitor { 241 this(ref G graph, ref char[] examinationOrder) { 242 _graph = &graph; 243 _vertexExaminationOrder = &examinationOrder; 244 } 245 246 void examineVertex(ref const(G) g, Vertex v) { 247 (*_vertexExaminationOrder) ~= (*_graph)[v]; 248 } 249 250 NullVisitor!G impl; 251 alias impl this; 252 253 G* _graph; 254 char[]* _vertexExaminationOrder; 255 } 256 257 char[] examinationOrder; 258 Colour[Vertex] colourMap; 259 260 auto testGraph = MakeTestGraph!G(); 261 262 breadthFirstSearch(testGraph.graph, 263 testGraph.vertices['a'], 264 colourMap, 265 Visitor(testGraph.graph, examinationOrder)); 266 267 // Assert that each vertex is examined, and examined exactly once 268 assert (examinationOrder.length == 6, 269 "Expected 6 entries in examination order array, got " ~ 270 to!string(examinationOrder.length)); 271 272 auto keyExists = delegate(char v) { return indexOf(examinationOrder, v) >= 0; }; 273 274 assert (all(testGraph.vertices.keys, keyExists), 275 "Expected all vertices to appear in the examined vertex list"); 276 277 // Assert that the source vertex is examined 278 assert (indexOf(examinationOrder, 'a') == 0, 279 "Expected Vertex A to be the first vertex examined."); 280 281 // Assert that the vertices are enumerated breadth first 282 assert (indexOf(examinationOrder, 'c') < indexOf(examinationOrder, 'e'), 283 "Expected vertex C to appear before vertex E."); 284 285 assert (indexOf(examinationOrder, 'd') < indexOf(examinationOrder, 'f'), 286 "Expected vertex D to appear before vertex F."); 287 } 288 289 unittest { 290 writeln("BFS: Vertices should be discovered exactly once."); 291 292 static struct Visitor { 293 this(ref int[Vertex] discoveryCounts) { 294 _counts = &discoveryCounts; 295 } 296 297 void examineVertex(ref const(G) g, Vertex v) { 298 (*_counts)[v]++; 299 } 300 301 NullVisitor!G impl; 302 alias impl this; 303 304 G* _graph; 305 int[Vertex]* _counts; 306 } 307 308 int[Vertex] counts; 309 Colour[Vertex] colourMap; 310 311 auto testGraph = MakeTestGraph!G(); 312 313 breadthFirstSearch(testGraph.graph, 314 testGraph.vertices['a'], 315 colourMap, 316 Visitor(counts)); 317 318 // Assert that each vertex is discovered, and discovered exactly once 319 assert (counts.length == 6, 320 "Expected 6 entries in discovery count array, got " ~ 321 to!string(counts.length)); 322 323 auto pred = (Vertex v) { return (v in counts) !is null; }; 324 assert (all(testGraph.vertices.values, pred), 325 "Every vertex must appear in the discovery count array"); 326 327 assert (std.algorithm.all!("a == 1")(counts.values)); 328 } 329 330 unittest { 331 writeln("BFS: Edges in a directed graph should be examined exactly once."); 332 333 static struct Visitor { 334 this(ref int[Edge] counts) { 335 _counts = &counts; 336 } 337 338 void examineEdge(ref const(G) g, Edge e) { 339 (*_counts)[e]++; 340 } 341 342 NullVisitor!G impl; 343 alias impl this; 344 345 int[Edge]* _counts; 346 } 347 348 int[Edge] counts; 349 Colour[Vertex] colourMap; 350 351 auto testGraph = MakeTestGraph!G(); 352 353 breadthFirstSearch(testGraph.graph, 354 testGraph.vertices['a'], 355 colourMap, 356 Visitor(counts)); 357 358 auto edges = Set!Edge(); 359 foreach (v; testGraph.graph.vertices) 360 edges.insert(testGraph.graph.outEdges(v)); 361 362 // Assert that each edge is discovered, and discovered exactly once 363 assert (counts.length == edges.length, 364 "Expected " ~ to!string(edges.length) ~ 365 " entries in discovery count array, got " ~ to!string(counts.length)); 366 367 auto pred = (Edge e) { return (e in counts) !is null; }; 368 assert (all(edges, pred), 369 "Every edge must appear in the discovery count array"); 370 371 assert (std.algorithm.all!("a == 1")(counts.values)); 372 } 373 374 unittest { 375 writeln("BFS: Edges in an undirected graph should be examined at least once."); 376 alias UndirectedGraph = AdjacencyList!(VecS, VecS, UndirectedS, char, string); 377 alias UEdge = UndirectedGraph.EdgeDescriptor; 378 379 static struct Visitor { 380 this(ref int[UEdge] counts) { 381 _counts = &counts; 382 } 383 384 void examineEdge(ref const(UndirectedGraph) g, UEdge e) { 385 (*_counts)[e]++; 386 } 387 388 NullVisitor!UndirectedGraph impl; 389 alias impl this; 390 391 int[UEdge]* _counts; 392 } 393 394 auto testGraph = MakeTestGraph!UndirectedGraph(); 395 396 int[UEdge] counts; 397 Colour[Vertex] colourMap; 398 399 breadthFirstSearch(testGraph.graph, 400 testGraph.vertices['a'], 401 colourMap, 402 Visitor(counts)); 403 404 auto edges = Set!UEdge(); 405 foreach (v; testGraph.graph.vertices) 406 edges.insert(testGraph.graph.outEdges(v)); 407 408 // Assert that each edge is discovered at least once 409 assert (counts.length == edges.length, 410 "Expected " ~ to!string(edges.length) ~ 411 " entries in discovery count array, got " ~ to!string(counts.length)); 412 413 auto pred = (UEdge e) { return (e in counts) !is null; }; 414 assert (all(edges, pred), 415 "Every edge must appear in the discovery count array"); 416 417 assert (std.algorithm.all!("a > 0")(counts.values)); 418 } 419 420 unittest { 421 writeln("BFS: Tree & non-tree vertices should be identified"); 422 423 static struct Visitor { 424 this(Vertex black, Vertex grey, 425 ref int treeCount, 426 ref int nonTreeCount) { 427 _black = black; 428 _grey = grey; 429 _treeEdgeCount = &treeCount; 430 _nonTreeCount = &nonTreeCount; 431 } 432 433 void treeEdge(ref const(G) g, Edge e) { 434 auto t = g.target(e); 435 (*_treeEdgeCount)++; 436 } 437 438 439 void nonTreeEdge(ref const(G) g, Edge e) { 440 auto t = g.target(e); 441 assert (t == _black || t == _grey); 442 (*_nonTreeCount)++; 443 } 444 445 void greyTarget(ref const(G) g, Edge e) { 446 assert (g.target(e) == _grey); 447 } 448 449 void blackTarget(ref const(G) g, Edge e) { 450 assert (g.target(e) == _black); 451 } 452 453 NullVisitor!G impl; 454 alias impl this; 455 456 Vertex _black; 457 Vertex _grey; 458 int* _treeEdgeCount; 459 int* _nonTreeCount; 460 } 461 462 int treeCount = 0; int nonTreeCount = 0; 463 Colour[Vertex] colourMap; 464 465 auto testGraph = MakeTestGraph!G(); 466 467 breadthFirstSearch(testGraph.graph, 468 testGraph.vertices['a'], 469 colourMap, 470 Visitor(testGraph.vertices['a'], 471 testGraph.vertices['e'], 472 treeCount, 473 nonTreeCount)); 474 475 assert (treeCount == 5, 476 "Expected tree hit count of 5, got " ~ to!string(nonTreeCount)); 477 478 assert (nonTreeCount == 2, 479 "Expected non-tree hit count of 2, got " ~ to!string(nonTreeCount)); 480 481 } 482 483 unittest { 484 writeln("BFS: Vertices should be finished exactly once."); 485 486 static struct Visitor { 487 this(ref int[Vertex] finishCounts) { 488 _counts = &finishCounts; 489 } 490 491 void finishVertex(ref const(G) g, Vertex v) { 492 (*_counts)[v]++; 493 } 494 495 NullVisitor!G impl; 496 alias impl this; 497 498 G* _graph; 499 int[Vertex]* _counts; 500 } 501 502 int[Vertex] counts; 503 Colour[Vertex] colourMap; 504 505 auto testGraph = MakeTestGraph!G(); 506 507 breadthFirstSearch(testGraph.graph, 508 testGraph.vertices['a'], 509 colourMap, 510 Visitor(counts)); 511 512 // Assert that each vertex is discovered, and discovered exactly once 513 auto vertices = array(testGraph.graph.vertices); 514 assert (counts.length == vertices.length, 515 "Expected " ~ to!string(vertices.length) ~ 516 " entries in edge discovery array, got " ~ to!string(counts.length)); 517 518 auto pred = (Vertex v) { return (v in counts) !is null; }; 519 assert (all(testGraph.vertices.values, pred), 520 "Every vertex must appear in the discovery count array"); 521 522 assert (std.algorithm.all!("a == 1")(counts.values)); 523 }