1 module dstruct.graph;
2 
3 import std.algorithm;
4 import std.range;
5 import std.array;
6 import std.typecons;
7 
8 import dstruct.support;
9 import dstruct.map;
10 
11 /// The directionality of a graph.
12 enum EdgeDirection : bool {
13     /// Undirected, meaning edges face either direction.
14     undirected,
15     /// Directed, meaning edges face one direction.
16     directed,
17 }
18 
19 @nogc @safe pure nothrow
20 private bool findAndRemove(T)(ref T[] arr, ref T needle) {
21     foreach(index; 0 .. arr.length) {
22         if (arr[index] == needle) {
23             foreach(newIndex; index .. arr.length - 1) {
24                 arr[newIndex] = arr[newIndex + 1];
25             }
26 
27             arr = arr[0 .. $ - 1];
28             return true;
29         }
30     }
31 
32     return false;
33 }
34 
35 @safe pure nothrow
36 private void addIfMissing(T)(ref T[] arr, ref T value) {
37     if (arr.countUntil(value) < 0) {
38         arr ~= value;
39     }
40 }
41 
42 /**
43  * This struct represents a graph type as a reference type.
44  *
45  * Graphs types have a type of vertex and a direction.
46  *
47  * The graphs are represented by adjacency lists, which have good
48  * all-around performance characteristics for sparse graphs.
49  */
50 struct BasicGraph(Vertex, EdgeDirection edgeDirection) {
51 private:
52     HashMap!(Vertex, Vertex[]) adjacencyMap;
53 public:
54     /// true if this graph is a directed graph.
55     enum bool isDirected = edgeDirection == EdgeDirection.directed;
56 
57     /**
58      * Add a vertex to the graph.
59      */
60     @safe pure nothrow
61     void addVertex(ref Vertex vertex) {
62         adjacencyMap.setDefault(vertex);
63     }
64 
65     /// ditto
66     @safe pure nothrow
67     void addVertex(Vertex vertex) {
68         addVertex(vertex);
69     }
70 
71     /**
72      * Remove the given vertex from this graph.
73      *
74      * Any edges to the given vertex will be removed.
75      *
76      * Returns: true if a vertex was removed.
77      */
78     @nogc @safe pure nothrow
79     bool removeVertex(ref Vertex vertex) {
80         // Try to remove the vertex's adjacency mapping first.
81         if (!adjacencyMap.remove(vertex)) {
82             return false;
83         }
84 
85         foreach(ref list; adjacencyMap.values) {
86             findAndRemove(list, vertex);
87         }
88 
89         return true;
90     }
91 
92     /// ditto
93     @nogc @safe pure nothrow
94     bool removeVertex(Vertex vertex) {
95         return removeVertex(vertex);
96     }
97 
98     /**
99      * Returns: true if the vertex is in the graph.
100      */
101     @nogc @safe pure nothrow
102     bool hasVertex(ref Vertex vertex) const {
103         return (vertex in adjacencyMap) !is null;
104     }
105 
106     /// ditto
107     @nogc @safe pure nothrow
108     bool hasVertex(Vertex vertex) const {
109         return hasVertex(vertex);
110     }
111 
112     /**
113      * Add an edge to the graph.
114      *
115      * New vertices will be added to the graph automatically.
116      */
117     @safe pure nothrow
118     void addEdge(ref Vertex left, ref Vertex right) {
119         adjacencyMap.setDefault(left).addIfMissing(right);
120 
121         static if (!isDirected) {
122             adjacencyMap.setDefault(right).addIfMissing(left);
123         } else {
124             addVertex(right);
125         }
126     }
127 
128     /// ditto
129     @safe pure nothrow
130     void addEdge(ref Vertex left, Vertex right) {
131         addEdge(left, right);
132     }
133 
134     /// ditto
135     @safe pure nothrow
136     void addEdge(Vertex left, ref Vertex right) {
137         addEdge(left, right);
138     }
139 
140     /// ditto
141     @safe pure nothrow
142     void addEdge(Vertex left, Vertex right) {
143         addEdge(left, right);
144     }
145 
146     /**
147      * Remove an edge from the graph.
148      *
149      * Vertices in the edge will not be removed.
150      *
151      * Returns: true if an edge was removed.
152      */
153     @nogc @safe pure nothrow
154     bool removeEdge(ref Vertex left, ref Vertex right) {
155         auto listPtr = left in adjacencyMap;
156 
157         if (listPtr is null) {
158             return false;
159         }
160 
161         if (!findAndRemove(*listPtr, right)) {
162             return false;
163         }
164 
165         static if (!isDirected) {
166             findAndRemove(adjacencyMap[right], left);
167         }
168 
169         return true;
170     }
171 
172     /// ditto
173     @nogc @safe pure nothrow
174     bool removeEdge(ref Vertex left, Vertex right) {
175         return removeEdge(left, right);
176     }
177 
178     /// ditto
179     @nogc @safe pure nothrow
180     bool removeEdge(Vertex left, ref Vertex right) {
181         return removeEdge(left, right);
182     }
183 
184     /// ditto
185     @nogc @safe pure nothrow
186     bool removeEdge(Vertex left, Vertex right) {
187         return removeEdge(left, right);
188     }
189 
190     /**
191      * Check if an edge exists in the graph.
192      *
193      * Returns: true if the edge exists in the graph.
194      */
195     @nogc @safe pure nothrow
196     bool hasEdge(ref Vertex left, ref Vertex right) const {
197         if (auto listPtr = left in adjacencyMap) {
198             return countUntil(*listPtr, right) > -1;
199         }
200 
201         return false;
202     }
203 
204     /// ditto
205     @nogc @safe pure nothrow
206     bool hasEdge(ref Vertex left, Vertex right) const {
207         return hasEdge(left, right);
208     }
209 
210     /// ditto
211     @nogc @safe pure nothrow
212     bool hasEdge(Vertex left, ref Vertex right) const {
213         return hasEdge(left, right);
214     }
215 
216     /// ditto
217     @nogc @safe pure nothrow
218     bool hasEdge(Vertex left, Vertex right) const {
219         return hasEdge(left, right);
220     }
221 
222     /**
223      * Return the number of vertices in this graph
224      * in constant time.
225      *
226      * Returns: The number of vertices in this graph.
227      */
228     @nogc @safe pure nothrow
229     @property
230     size_t vertexCount() const {
231         return adjacencyMap.length;
232     }
233 
234     /**
235      * Return the number of directed edges in this graph
236      * in linear time. If this graph is a graph with undirected
237      * edges, this will always be double the undirected
238      * edge count.
239      *
240      * Returns: The number of directed edges in this graph.
241      */
242     @nogc @safe pure nothrow
243     size_t directedEdgeCount() const {
244         size_t count = 0;
245 
246         foreach(ref list; adjacencyMap.values()) {
247             count += list.length;
248         }
249 
250         return count;
251     }
252 
253     /**
254      * Return the number of edges in this graph
255      * in linear time.
256      *
257      * Returns: The number of edges in this graph.
258      */
259     @nogc @safe pure nothrow
260     size_t edgeCount() const {
261         static if (!isDirected) {
262             return directedEdgeCount() / 2;
263         } else {
264             return directedEdgeCount();
265         }
266     }
267 }
268 
269 /**
270  * An edge in a graph.
271  */
272 struct Edge(V) {
273     V from;
274     V to;
275 }
276 
277 /// A shorthand for an undirected graph.
278 alias Graph(Vertex) = BasicGraph!(Vertex, EdgeDirection.undirected);
279 
280 /// A shorthand for a directed graph.
281 alias Digraph(Vertex) = BasicGraph!(Vertex, EdgeDirection.directed);
282 
283 /// Test if a type T is an undirected graph type with a particular vertex type.
284 enum isUndirectedGraph(T, Vertex) = is(T == BasicGraph!(Vertex, EdgeDirection.undirected));
285 
286 /// Test if a type T is a directed graph type with a particular vertex type.
287 enum isDirectedGraph(T, Vertex) = is(T == BasicGraph!(Vertex, EdgeDirection.directed));
288 
289 /// Test if a type T is any graph type with a particular vertex type.
290 enum isGraph(T, Vertex) = isUndirectedGraph!(T, Vertex) || isDirectedGraph!(T, Vertex);
291 
292 // Test the templates.
293 unittest {
294     Graph!int graph;
295 
296     assert(isUndirectedGraph!(typeof(graph), int));
297     assert(isGraph!(typeof(graph), int));
298     assert(!isGraph!(typeof(graph), short));
299 
300     Digraph!int digraph;
301 
302     assert(isDirectedGraph!(typeof(digraph), int));
303     assert(isGraph!(typeof(digraph), int));
304     assert(!isGraph!(typeof(digraph), short));
305 }
306 
307 // Test adding vertices and the vertex count on graphs
308 unittest {
309     Graph!string graph;
310 
311     foreach(symbol; ["a", "b", "c", "d", "a"]) {
312         graph.addVertex(symbol);
313     }
314 
315     assert(graph.vertexCount == 4);
316 }
317 
318 unittest {
319     Digraph!string digraph;
320 
321     foreach(symbol; ["a", "b", "c", "d", "a"]) {
322         digraph.addVertex(symbol);
323     }
324 
325     assert(digraph.vertexCount == 4);
326 }
327 
328 // Test adding edges and the edge count.
329 unittest {
330     Graph!byte graph;
331 
332     byte[2][] edgeList = [[1, 2], [2, 1], [3, 4], [5, 6]];
333 
334     foreach(edge; edgeList) {
335         graph.addEdge(edge[0], edge[1]);
336     }
337 
338     assert(graph.directedEdgeCount == 6);
339     assert(graph.edgeCount == 3);
340     assert(graph.hasVertex(1));
341 }
342 
343 // Test adding edges and the edge count.
344 unittest {
345     Digraph!byte graph;
346 
347     byte[2][] edgeList = [[1, 2], [2, 1], [3, 4], [5, 6]];
348 
349     foreach(edge; edgeList) {
350         graph.addEdge(edge[0], edge[1]);
351     }
352 
353     assert(graph.directedEdgeCount == 4);
354     assert(graph.edgeCount == 4);
355     assert(graph.hasVertex(1));
356 }
357 
358 // Test adding one undirected graph edge implies the reverse.
359 unittest {
360     Graph!byte graph;
361 
362     byte[2][] edgeList = [[1, 2], [3, 4]];
363 
364     foreach(edge; edgeList) {
365         graph.addEdge(edge[0], edge[1]);
366     }
367 
368     assert(graph.edgeCount == 2);
369     assert(graph.hasEdge(2, 1));
370     assert(graph.hasEdge(4, 3));
371 }
372 
373 // Test that removing a vertex also removes the edges.
374 unittest {
375     Digraph!byte graph;
376 
377     byte[2][] edgeList = [[1, 2], [3, 1], [2, 3]];
378 
379     foreach(edge; edgeList) {
380         graph.addEdge(edge[0], edge[1]);
381     }
382 
383     assert(graph.removeVertex(1));
384     assert(graph.edgeCount == 1);
385     assert(graph.hasEdge(2, 3));
386 }
387 
388 unittest {
389     Graph!byte graph;
390 
391     byte[2][] edgeList = [[1, 2], [3, 1], [2, 3]];
392 
393     foreach(edge; edgeList) {
394         graph.addEdge(edge[0], edge[1]);
395     }
396 
397     assert(graph.removeVertex(1));
398     assert(graph.edgeCount == 1);
399     assert(graph.hasEdge(2, 3));
400 }
401 
402 /**
403  * Given any type of graph, produce a range through the vertices of the graph.
404  *
405  * Params:
406  *     graph = A graph.
407  *
408  * Returns:
409  *     A ForwardRange through the vertices of the graph.
410  */
411 @nogc @safe pure nothrow
412 auto vertices(V, EdgeDirection edgeDirection)
413 (auto ref BasicGraph!(V, edgeDirection) graph) {
414     return graph.adjacencyMap.keys;
415 }
416 
417 /// ditto
418 @nogc @safe pure nothrow
419 auto vertices(V, EdgeDirection edgeDirection)
420 (auto ref const(BasicGraph!(V, edgeDirection)) graph) {
421     return graph.adjacencyMap.keys;
422 }
423 
424 /// ditto
425 @nogc @safe pure nothrow
426 auto vertices(V, EdgeDirection edgeDirection)
427 (auto ref immutable(BasicGraph!(V, edgeDirection)) graph) {
428     return graph.adjacencyMap.keys;
429 }
430 
431 unittest {
432     Digraph!string graph;
433 
434     graph.addEdge("a", "b");
435     graph.addEdge("a", "c");
436     graph.addEdge("a", "d");
437     graph.addEdge("b", "e");
438     graph.addEdge("b", "f");
439     graph.addEdge("b", "g");
440 
441     string[] vertexList;
442 
443     foreach(vertex; graph.vertices) {
444         vertexList ~= vertex;
445     }
446 
447     // We know we will get this order from how the hashing works.
448     assert(vertexList == ["a", "b", "c", "d", "e", "f", "g"]);
449 }
450 
451 unittest {
452     Graph!string mGraph;
453     const(Graph!string) cGraph;
454     immutable(Graph!string) iGraph;
455 
456     auto mVertices = mGraph.vertices();
457     auto cVertices = cGraph.vertices();
458     auto iVertices = iGraph.vertices();
459 
460     assert(is(typeof(mVertices.front) == string));
461     assert(is(typeof(cVertices.front) == const(string)));
462     assert(is(typeof(iVertices.front) == immutable(string)));
463 }
464 
465 /**
466  * A range through the edges of a graph.
467  */
468 struct EdgeRange(V, VArr) {
469 private:
470     ItemRange!(V, VArr) _itemRange;
471     size_t _outgoingIndex;
472 
473     @nogc @safe pure nothrow
474     this (typeof(_itemRange) itemRange) {
475         _itemRange = itemRange;
476 
477         // Advance until we find an edge.
478         while (!_itemRange.empty && _itemRange.front.value.length == 0) {
479             _itemRange.popFront;
480         }
481     }
482 public:
483     ///
484     @nogc @safe pure nothrow
485     inout(typeof(this)) save() inout {
486         return this;
487     }
488 
489     ///
490     @nogc @safe pure nothrow
491     @property
492     bool empty() const {
493         return _itemRange.empty;
494     }
495 
496     ///
497     @nogc @safe pure nothrow
498     @property
499     Edge!V front() const {
500         auto item = _itemRange.front;
501 
502         return Edge!V(item.key, item.value[_outgoingIndex]);
503     }
504 
505     ///
506     @nogc @safe pure nothrow
507     void popFront() {
508         if (++_outgoingIndex < _itemRange.front.value.length) {
509             // There's another outgoing edge in the list, so move to that.
510             return;
511         }
512 
513         // We have to find the next vertex with a non-empty adjacency list.
514         _outgoingIndex = 0;
515 
516         do {
517             _itemRange.popFront;
518         } while (!_itemRange.empty && _itemRange.front.value.length == 0);
519     }
520 }
521 
522 /**
523  * Given any type of graph, produce a range through the edges of the graph.
524  *
525  * Params:
526  *     graph = A graph.
527  *
528  * Returns:
529  *     A ForwardRange through the edges of the graph.
530  */
531 @nogc @safe pure nothrow
532 auto edges(V, EdgeDirection edgeDirection)
533 (auto ref BasicGraph!(V, edgeDirection) graph) {
534     return EdgeRange!(V, V[])(graph.adjacencyMap.items);
535 }
536 
537 /// ditto
538 @nogc @trusted pure nothrow
539 auto edges(V, EdgeDirection edgeDirection)
540 (auto ref const(BasicGraph!(V, edgeDirection)) graph) {
541     return EdgeRange!(const(V), const(V[]))(
542         cast(ItemRange!(const(V), const(V[])))
543         graph.adjacencyMap.items
544     );
545 }
546 
547 /// ditto
548 @nogc @trusted pure nothrow
549 auto edges(V, EdgeDirection edgeDirection)
550 (auto ref immutable(BasicGraph!(V, edgeDirection)) graph) {
551     return EdgeRange!(immutable(V), immutable(V[]))(
552         cast(ItemRange!(immutable(V), immutable(V[])))
553         graph.adjacencyMap.items
554     );
555 }
556 
557 unittest {
558     Digraph!string graph;
559 
560     graph.addEdge("a", "b");
561     graph.addEdge("a", "c");
562     graph.addEdge("a", "d");
563     graph.addEdge("b", "e");
564     graph.addEdge("b", "f");
565     graph.addEdge("b", "g");
566 
567     Edge!string[] edgeList;
568 
569     foreach(edge; graph.edges) {
570         edgeList ~= edge;
571     }
572 
573     // We know we will get this order from how the hashing works.
574     assert(edgeList.length);
575     assert(edgeList[0].from == "a");
576     assert(edgeList[0].to == "b");
577     assert(edgeList[1].from == "a");
578     assert(edgeList[1].to == "c");
579     assert(edgeList[2].from == "a");
580     assert(edgeList[2].to == "d");
581     assert(edgeList[3].from == "b");
582     assert(edgeList[3].to == "e");
583     assert(edgeList[4].from == "b");
584     assert(edgeList[4].to == "f");
585     assert(edgeList[5].from == "b");
586     assert(edgeList[5].to == "g");
587 }
588 
589 unittest {
590     Graph!string mGraph;
591     const(Graph!string) cGraph;
592     immutable(Graph!string) iGraph;
593 
594     auto mVertices = mGraph.edges();
595     auto cVertices = cGraph.edges();
596     auto iVertices = iGraph.edges();
597 
598     assert(is(typeof(mVertices.front) == Edge!string));
599     assert(is(typeof(cVertices.front) == Edge!(const(string))));
600     assert(is(typeof(iVertices.front) == Edge!(immutable(string))));
601 }