1 module anansi.algorithms.components; 2 3 import std.stdio, std.meta; 4 5 import anansi.algorithms.dfs, 6 anansi.traits, 7 anansi.types; 8 9 /** 10 * Lists the connected components (a.k.a "islands") of an undirected graph. 11 * 12 * Params: 13 * GraphT = The type of the graph object to examine. Must model the 14 * incidence graph concept and be undirected. 15 * ComponentMapT = The type of the component map to use. Must model a 16 * property map of ints keyed by GraphT vertex 17 * descriptor. 18 */ 19 template connectedComponents(GraphT, ComponentMapT) { 20 static assert (isGraph!GraphT); 21 static assert (GraphT.IsUndirected); 22 static assert (isPropertyMap!(ComponentMapT, GraphT.VertexDescriptor, size_t)); 23 24 /** 25 * Params: 26 * g = The graph to examine. 27 * components = The component map to populate. 28 * 29 * Returns: 30 * Returns the number of components in the graph. 31 */ 32 size_t connectedComponents(ref const(GraphT) g, ref ComponentMapT components) { 33 alias Vertex = GraphT.VertexDescriptor; 34 alias Edge = GraphT.EdgeDescriptor; 35 36 static struct ComponentRecorder { 37 this(ref size_t count, ref ComponentMapT componentMap) { 38 _group = &count; 39 _components = &componentMap; 40 } 41 42 void startVertex(ref const(GraphT), Vertex v) { 43 if( (*_group) == size_t.max ) 44 (*_group) = 0; 45 else 46 (*_group)++; 47 } 48 49 void discoverVertex(ref const(GraphT), Vertex v) { 50 (*_components)[v] = (*_group); 51 } 52 53 private size_t* _group; 54 private ComponentMapT* _components; 55 56 NullVisitor!GraphT impl; 57 alias impl this; 58 } 59 60 if( g.vertices.empty ) 61 return 0; 62 63 // Let's try and represent the colour map as an array if we can; it'll 64 // make map access stupidly fast. 65 static if (is(Vertex == size_t)) { 66 auto colourMap = new Colour[g.vertexCount]; 67 } 68 else { 69 Colour[Vertex] colourMap; 70 } 71 72 size_t count = size_t.max; 73 depthFirstSearch(g, g.vertices.front, colourMap, 74 ComponentRecorder(count, components)); 75 return count+1; 76 } 77 } 78 79 // ---------------------------------------------------------------------------- 80 // 81 // ---------------------------------------------------------------------------- 82 83 version (unittest) { 84 import std.algorithm, std.conv, std.stdio; 85 import anansi.adjacencylist, anansi.traits; 86 87 void makeDisconnectedCycles(G)(ref G g, int cycles, int cycleSize) { 88 alias Vertex = G.VertexDescriptor; 89 foreach(i; 0 .. cycles) { 90 Vertex firstVertex = g.addVertex(); 91 Vertex prevVertex; 92 Vertex currentVertex = firstVertex; 93 foreach(j; 1 .. cycleSize) { 94 prevVertex = currentVertex; 95 currentVertex = g.addVertex(); 96 g.addEdge(prevVertex, currentVertex); 97 } 98 g.addEdge(currentVertex, firstVertex); 99 } 100 } 101 } 102 103 unittest { 104 writeln("Connected Components: Empty graph should return 0."); 105 alias G = AdjacencyList!(VecS, VecS, UndirectedS); 106 G g; 107 size_t[G.VertexDescriptor] components; 108 auto rval = connectedComponents(g, components); 109 110 assert (rval == 0, 111 "connectedComponents() should return 0 for an empty graph."); 112 113 assert (components.length == 0, 114 "connected components array should be 0 length."); 115 } 116 117 118 unittest { 119 writeln("Connected Components: Components should be identified."); 120 // check each supported configuration of storage selectors 121 foreach(VertexStorage; AliasSeq!(VecS, ListS)) { 122 foreach(EdgeStorage; AliasSeq!(VecS, ListS)) { 123 alias G = AdjacencyList!(VertexStorage, EdgeStorage, UndirectedS); 124 G g; 125 size_t[G.VertexDescriptor] components; 126 makeDisconnectedCycles(g, 5, 10); 127 auto rval = connectedComponents(g, components); 128 129 assert (rval == 5, 130 "connectedComponents should return the number of components discovered."); 131 132 int[size_t] tally; 133 foreach(k, v; components) { tally[v]++; } 134 135 assert( all!("a == 10")(tally.values), 136 "All components should have size 10." ); 137 } 138 } 139 }