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 }