1 /**
2 Some additional mathematical functions.
3
4 Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de>
5 License: Subject to the terms of the MIT license, as written in the
6 included LICENSE file.
7 Authors: Arne Ludwig <arne.ludwig@posteo.de>
8 */
9 module dalicious.math;
10
11 import dalicious.algorithm : cmpLexicographically, sliceBy;
12 import std.algorithm :
13 all,
14 among,
15 copy,
16 countUntil,
17 cumulativeFold,
18 filter,
19 map,
20 max,
21 min,
22 maxElement,
23 sort,
24 sum,
25 swap,
26 uniq;
27 import std.array : appender, Appender, array;
28 import std.conv : to;
29 import std.exception : assertThrown;
30 import std.format : format;
31 import std.functional : binaryFun, unaryFun;
32 import std.range :
33 assumeSorted,
34 chain,
35 ElementType,
36 enumerate,
37 isForwardRange,
38 isInputRange,
39 isRandomAccessRange,
40 retro,
41 save,
42 slide,
43 StoppingPolicy,
44 walkLength,
45 zip;
46 import std.traits :
47 isCallable,
48 isIntegral,
49 isNumeric;
50 import std.typecons :
51 Flag,
52 No,
53 Yes;
54
55 debug import std.stdio : writeln;
56
57 /// Calculate the mean of range.
58 ElementType!Range mean(Range)(Range values) if (isForwardRange!Range)
59 {
60 auto sum = values.save.sum;
61 auto length = values.walkLength.to!(ElementType!Range);
62
63 return sum / length;
64 }
65
66 unittest
67 {
68 {
69 auto values = [2, 4, 8];
70 assert(values.mean == 4);
71 }
72 {
73 auto values = [1.0, 2.0, 3.0, 4.0];
74 assert(values.mean == 2.5);
75 }
76 }
77
78 /// Calculate the weighted mean of values.
79 double mean(Values, Weights)(Values values, Weights weights)
80 if (isInputRange!Values && isForwardRange!Weights)
81 {
82 enum zeroWeight = cast(ElementType!Weights) 0;
83
84 auto weightedSum = zip(StoppingPolicy.requireSameLength, values, weights)
85 .map!(pair => (pair[0] * pair[1]).to!double)
86 .sum;
87 auto totalWeight = weights.sum;
88
89 return weightedSum / totalWeight;
90 }
91
92 unittest
93 {
94 {
95 auto values = [2, 4, 6];
96 auto equalWeights = [1, 1, 1];
97 auto weights = [3, 4, 1];
98
99 assert(mean(values, equalWeights) == mean(values));
100 assert(mean(values, weights) == 3.5);
101 }
102 {
103 auto values = [1.0, 2.0, 3.0, 4.0];
104 auto weights = [4.0, 3.0, 2.0, 1.0];
105
106 assert(mean(values, weights) == 2.0);
107 }
108 }
109
110 /// Calculate the median of range.
111 ElementType!Range median(Range)(Range values) if (__traits(compiles, sort(values)))
112 {
113 assert(values.length > 0, "median is undefined for empty set");
114 auto middleIdx = values.length / 2;
115 auto useAverage = values.length > 1 && values.length % 2 == 0;
116 auto sortedValues = values.sort;
117
118 if (useAverage)
119 return (sortedValues[middleIdx - 1] + sortedValues[middleIdx]) / 2;
120 else
121 return values[middleIdx];
122 }
123
124 unittest
125 {
126 {
127 auto values = [4, 2, 8];
128 assert(values.median == 4);
129 }
130 {
131 auto values = [4, 3, 2, 8];
132 assert(values.median == 3);
133 }
134 {
135 auto values = [4, 6, 2, 8];
136 assert(values.median == 5);
137 }
138 {
139 auto values = [2, 1, 3, 0, 4, 9, 8, 5, 6, 3, 9];
140 assert(values.median == 4);
141 }
142 {
143 auto values = [2.0, 1.0, 4.0, 3.0];
144 assert(values.median == 2.5);
145 }
146 {
147 auto values = [2.0, 1.0, 4.0, 3.0, 5.0];
148 assert(values.median == 3.0);
149 }
150 }
151
152 /// Calculate the Nxx (e.g. N50) of values.
153 ElementType!Range N(real xx, Range, Num)(Range values, Num totalSize) if (__traits(compiles, sort(values)))
154 {
155 static assert(0 < xx && xx < 100, "N" ~ xx.to!string ~ " is undefined");
156 assert(values.length > 0, "N" ~ xx.to!string ~ " is undefined for empty set");
157 auto xxPercentile = xx/100.0 * totalSize;
158 auto sortedValues = values.sort;
159 auto targetIndex = sortedValues
160 .retro
161 .cumulativeFold!"a + b"(cast(ElementType!Range) 0)
162 .countUntil!"a >= b"(xxPercentile);
163
164 if (targetIndex.among(-1, values.length))
165 return 0;
166 else
167 return sortedValues[$ - targetIndex - 1];
168 }
169
170 unittest
171 {
172 {
173 auto totalSize = 54;
174 auto values = [2, 3, 4, 5, 6, 7, 8, 9, 10];
175 enum N50 = 8;
176 enum N10 = 10;
177
178 assert(N!50(values, totalSize) == N50);
179 assert(N!10(values, totalSize) == N10);
180 }
181 {
182 auto totalSize = 32;
183 auto values = [2, 2, 2, 3, 3, 4, 8, 8];
184 enum N50 = 8;
185
186 assert(N!50(values, totalSize) == N50);
187 }
188 }
189
190 enum RoundingMode : byte
191 {
192 floor,
193 round,
194 ceil,
195 }
196
197 /**
198 Round x upward according to base, ie. returns the next integer larger or
199 equal to x which is divisible by base.
200
201 Returns: x rounded upward according to base.
202 */
203 Integer ceil(Integer)(in Integer x, in Integer base) pure nothrow
204 if (isIntegral!Integer)
205 {
206 return x % base == 0
207 ? x
208 : (x / base + 1) * base;
209 }
210
211 ///
212 unittest
213 {
214 assert(ceil(8, 10) == 10);
215 assert(ceil(32, 16) == 32);
216 assert(ceil(101, 100) == 200);
217 }
218
219 /**
220 Round x downward according to base, ie. returns the next integer smaller or
221 equal to x which is divisible by base.
222
223 Returns: x rounded downward according to base.
224 */
225 Integer floor(Integer)(in Integer x, in Integer base) pure nothrow
226 if (isIntegral!Integer)
227 {
228 return (x / base) * base;
229 }
230
231 ///
232 unittest
233 {
234 assert(floor(8, 10) == 0);
235 assert(floor(32, 16) == 32);
236 assert(floor(101, 100) == 100);
237 }
238
239 /// Returns the absolute difference between two numbers.
240 Num absdiff(Num)(in Num a, in Num b) pure nothrow if (isNumeric!Num)
241 {
242 return a > b
243 ? a - b
244 : b - a;
245 }
246
247 ///
248 unittest
249 {
250 assert(absdiff(2UL, 3UL) == 1UL);
251 assert(absdiff(-42, 13) == 55);
252 assert(absdiff(2.5, 5) == 2.5);
253 }
254
255 /// Returns the result of `ceil(a / b)` but uses integer arithmetic only.
256 Integer ceildiv(Integer)(in Integer a, in Integer b) pure nothrow if (isIntegral!Integer)
257 {
258 Integer resultSign = (a < 0) ^ (b < 0) ? -1 : 1;
259
260 return resultSign < 0 || a % b == 0
261 ? a / b
262 : a / b + resultSign;
263 }
264
265 ///
266 unittest
267 {
268 assert(ceildiv(0, 3) == 0);
269 assert(ceildiv(1UL, 3UL) == 1UL);
270 assert(ceildiv(2L, 3L) == 1L);
271 assert(ceildiv(3U, 3U) == 1U);
272 assert(ceildiv(4, 3) == 2);
273 assert(ceildiv(-4, 4) == -1);
274 assert(ceildiv(-4, 3) == -1);
275 }
276
277 class EdgeExistsException : Exception
278 {
279 pure nothrow @nogc @safe this(
280 string file = __FILE__,
281 size_t line = __LINE__,
282 Throwable nextInChain = null,
283 )
284 {
285 super("edge cannot be inserted: edge already exists", file, line, nextInChain);
286 }
287 }
288
289 class MissingEdgeException : Exception
290 {
291 pure nothrow @nogc @safe this(
292 string file = __FILE__,
293 size_t line = __LINE__,
294 Throwable nextInChain = null,
295 )
296 {
297 super("edge not found", file, line, nextInChain);
298 }
299 }
300
301 class MissingNodeException : Exception
302 {
303 pure nothrow @nogc @safe this(
304 string file = __FILE__,
305 size_t line = __LINE__,
306 Throwable nextInChain = null,
307 )
308 {
309 super("node not found", file, line, nextInChain);
310 }
311 }
312
313 /// This structure represents a graph with optional edge
314 /// payloads. The graph is represented as a list of edges which is
315 /// particularly suited for sparse graphs. While the set of nodes is fixed the
316 /// set of edges is mutable.
317 struct Graph(Node, Weight = void, Flag!"isDirected" isDirected = No.isDirected, EdgePayload = void)
318 {
319 static enum isWeighted = !is(Weight == void);
320 static enum hasEdgePayload = !is(EdgePayload == void);
321
322 static struct Edge
323 {
324 protected Node _start;
325 protected Node _end;
326
327 static if (isWeighted)
328 Weight weight;
329
330 static if (hasEdgePayload)
331 EdgePayload payload;
332
333 /// Construct an edge.
334 this(Node start, Node end)
335 {
336 this._start = start;
337 this._end = end;
338
339 static if (!isDirected)
340 {
341 if (end < start)
342 {
343 swap(this._start, this._end);
344 }
345 }
346 }
347
348 static if (isWeighted)
349 {
350 /// ditto
351 this(Node start, Node end, Weight weight)
352 {
353 this(start, end);
354 this.weight = weight;
355 }
356 }
357
358 static if (hasEdgePayload && !is(EdgePayload : Weight))
359 {
360 /// ditto
361 this(Node start, Node end, EdgePayload payload)
362 {
363 this(start, end);
364 this.payload = payload;
365 }
366 }
367
368 static if (isWeighted && hasEdgePayload)
369 {
370 /// ditto
371 this(Node start, Node end, Weight weight, EdgePayload payload)
372 {
373 this(start, end);
374 this.weight = weight;
375 this.payload = payload;
376 }
377 }
378
379 /// Get the start of this edge. For undirected graphs this is the
380 /// smaller of both incident nodes.
381 @property Node start() const pure nothrow
382 {
383 return _start;
384 }
385
386 /// Get the end of this edge. For undirected graphs this is the
387 /// larger of both incident nodes.
388 @property Node end() const pure nothrow
389 {
390 return _end;
391 }
392
393 /**
394 Get target of this edge beginning at node `from`. For undirected
395 graphs returns the other node of this edge.
396
397 Throws: MissingNodeException if this edge does not start in node `from`.
398 */
399 Node target(Node from) const
400 {
401 static if (isDirected)
402 {
403 if (start == from)
404 {
405 return end;
406 }
407 else
408 {
409 throw new MissingNodeException();
410 }
411 }
412 else
413 {
414 if (start == from)
415 {
416 return end;
417 }
418 else if (end == from)
419 {
420 return start;
421 }
422 else
423 {
424 throw new MissingNodeException();
425 }
426 }
427 }
428
429 /**
430 Get source of this edge beginning at node `from`. For undirected
431 graphs returns the other node of this edge.
432
433 Throws: MissingNodeException if this edge does not end in node `from`.
434 */
435 static if (isDirected)
436 {
437 Node source(Node from) const
438 {
439 if (end == from)
440 {
441 return start;
442 }
443 else
444 {
445 throw new MissingNodeException();
446 }
447 }
448 }
449 else
450 {
451 alias source = target;
452 }
453
454 /// Two edges are equal iff their incident nodes (and weight) are the
455 /// same.
456 bool opEquals(in Edge other) const pure nothrow
457 {
458 static if (isWeighted)
459 {
460 return this.start == other.start && this.end == other.end
461 && this.weight == other.weight;
462 }
463 else
464 {
465 return this.start == other.start && this.end == other.end;
466 }
467 }
468
469 /// Orders edge lexicographically by `start`, `end`(, `weight`).
470 int opCmp(in Edge other) const pure nothrow
471 {
472 static if (isWeighted)
473 {
474 return cmpLexicographically!(
475 typeof(this),
476 "a.start",
477 "a.end",
478 "a.weight",
479 )(this, other);
480 }
481 else
482 {
483 return cmpLexicographically!(
484 typeof(this),
485 "a.start",
486 "a.end",
487 )(this, other);
488 }
489 }
490
491 private int compareNodes(in Edge other) const pure nothrow
492 {
493 return cmpLexicographically!(
494 typeof(this),
495 "a.start",
496 "a.end",
497 )(this, other);
498 }
499
500 /**
501 Returns the node that is connects this edge with other edge. In
502 case of undirected graphs this is just the common node of both
503 edges; in directed case this is the end node of this edge if it
504 matches the start node of other edge.
505
506 Throws: MissingNodeException if the connecting node is undefined.
507 */
508 Node getConnectingNode(in Edge other) const
509 {
510 static if (isDirected)
511 {
512 if (this.end == other.start)
513 {
514 return this.end;
515 }
516 }
517 else
518 {
519 if (this.end == other.start || this.end == other.end)
520 {
521 return this.end;
522 }
523 else if (this.start == other.start || this.start == other.end)
524 {
525 return this.start;
526 }
527 }
528
529 throw new MissingNodeException();
530 }
531 }
532
533 static bool orderByNodes(in Edge a, in Edge b) nothrow pure
534 {
535 return a.compareNodes(b) < 0;
536 }
537
538 static bool groupByNodes(in Edge a, in Edge b) nothrow pure
539 {
540 return a.compareNodes(b) == 0;
541 }
542
543 /// Construct an edge for this graph.
544 static Edge edge(T...)(T args)
545 {
546 return Edge(args);
547 }
548
549 protected Node[] _nodes;
550 protected Appender!(Edge[]) _edges;
551
552 /// The set (ordered list) of nodes.
553 @property const(Node[]) nodes() const nothrow pure
554 {
555 return _nodes;
556 }
557
558 private @property void nodes(Node[] nodes)
559 {
560 nodes.sort();
561
562 this._nodes = nodes.uniq.array;
563 }
564
565 /// Get the set (ordered list) of edges in this graph.
566 @property auto edges() nothrow pure
567 {
568 return chain(_edges.data);
569 }
570
571 /// ditto
572 @property auto edges() const nothrow pure
573 {
574 return chain(_edges.data);
575 }
576
577 /**
578 Construct a graph from a set of nodes (and edges). Modifies `nodes`
579 while sorting but releases it after construction.
580
581 Throws: MissingNodeException if an edge has a node that is not present
582 in this graph .
583 Throws: EdgeExistsException if an edge already exists when trying
584 inserting it.
585 */
586 this(Node[] nodes)
587 {
588 this.nodes = nodes;
589 }
590
591 /// ditto
592 this(Node[] nodes, Edge[] edges)
593 {
594 this(nodes);
595
596 _edges.reserve(edges.length);
597 foreach (edge; edges)
598 {
599 add(this, edge);
600 }
601 }
602
603 this(this)
604 {
605 _nodes = _nodes.dup;
606 }
607
608 /// Add a set of edges to this graph without any checks.
609 void bulkAddForce(R)(R edges) if (isForwardRange!R && is(ElementType!R == Edge))
610 {
611 this._edges ~= edges;
612 _edges.data.sort;
613 }
614
615 /// Add an edge to this graph.
616 /// See_Also: `Edge add(Graph, Edge)`
617 void opOpAssign(string op)(Edge edge) if (op == "~")
618 {
619 add(this, edge);
620 }
621
622 /// Some pre-defined conflict handlers for `add`.
623 static struct ConflictStrategy
624 {
625 static if (isWeighted)
626 {
627 /// Return an edge with sum of both weights. If given payload will be
628 /// kept from existingEdge .
629 static Edge sumWeights(Edge existingEdge, Edge newEdge)
630 {
631 existingEdge.weight += newEdge.weight;
632
633 return existingEdge;
634 }
635
636 ///
637 unittest
638 {
639 auto g1 = Graph!(int, int)([1, 2]);
640 alias CS = g1.ConflictStrategy;
641
642 g1 ~= g1.edge(1, 2, 1);
643
644 auto addedEdge = g1.add!(CS.sumWeights)(g1.edge(1, 2, 1));
645
646 assert(addedEdge.weight == 2);
647 }
648 }
649
650 /// Throw `EdgeExistsException`.
651 static inout(Edge) error(inout(Edge) existingEdge, inout(Edge) newEdge)
652 {
653 throw new EdgeExistsException();
654 }
655
656 ///
657 unittest
658 {
659 auto g1 = Graph!int([1, 2]);
660 alias CS = g1.ConflictStrategy;
661
662 g1 ~= g1.edge(1, 2);
663
664 assertThrown!EdgeExistsException(g1.add!(CS.error)(g1.edge(1, 2)));
665 }
666
667 /// Replace the existingEdge by newEdge.
668 static inout(Edge) replace(inout(Edge) existingEdge, inout(Edge) newEdge)
669 {
670 return newEdge;
671 }
672
673 ///
674 unittest
675 {
676 auto g1 = Graph!(int, int)([1, 2]);
677 alias CS = g1.ConflictStrategy;
678
679 g1 ~= g1.edge(1, 2, 1);
680
681 auto addedEdge = g1.add!(CS.replace)(g1.edge(1, 2, 2));
682
683 assert(addedEdge.weight == 2);
684 }
685
686 /// Keep existingEdge – discard newEdge.
687 static inout(Edge) keep(inout(Edge) existingEdge, inout(Edge) newEdge)
688 {
689 return existingEdge;
690 }
691
692 ///
693 unittest
694 {
695 auto g1 = Graph!(int, int)([1, 2]);
696 alias CS = g1.ConflictStrategy;
697
698 g1 ~= g1.edge(1, 2, 1);
699
700 auto addedEdge = g1.add!(CS.keep)(g1.edge(1, 2, 2));
701
702 assert(addedEdge.weight == 1);
703 }
704 }
705
706 /// Forcibly add an edge to this graph.
707 protected Edge forceAdd(Edge edge)
708 {
709 _edges ~= edge;
710 _edges.data.sort;
711
712 return edge;
713 }
714
715 /// Replace an edge in this graph.
716 protected Edge replaceEdge(in size_t edgeIdx, Edge newEdge)
717 {
718 auto shouldSort = _edges.data[edgeIdx] != newEdge;
719
720 _edges.data[edgeIdx] = newEdge;
721
722 if (shouldSort)
723 {
724 _edges.data.sort;
725 }
726
727 return newEdge;
728 }
729
730 /// Check if edge/node exists in this graph. Ignores the weight if weighted.
731 bool opBinaryRight(string op)(in Node node) const pure nothrow if (op == "in")
732 {
733 auto sortedNodes = assumeSorted(nodes);
734
735 return sortedNodes.contains(node);
736 }
737
738 /// ditto
739 bool has(in Node node) const pure nothrow
740 {
741 return node in this;
742 }
743
744 /// Check if edge exists in this graph. Only the `start` and `end` node
745 /// will be compared.
746 bool opBinaryRight(string op)(in Edge edge) const pure nothrow if (op == "in")
747 {
748 auto sortedEdges = assumeSorted!orderByNodes(edges);
749
750 return sortedEdges.contains(edge);
751 }
752
753 /// ditto
754 bool has(in Edge edge) const pure nothrow
755 {
756 return edge in this;
757 }
758
759 /// Get the designated edge from this graph. Only the `start` and `end`
760 /// node will be compared.
761 auto ref get(in Edge edge)
762 {
763 auto sortedEdges = assumeSorted!orderByNodes(edges);
764 auto existingEdges = sortedEdges.equalRange(edge);
765
766 if (existingEdges.empty)
767 {
768 throw new MissingEdgeException();
769 }
770 else
771 {
772 return existingEdges.front;
773 }
774 }
775
776 ///
777 unittest
778 {
779 auto g1 = Graph!(int, int)([1, 2]);
780
781 auto e1 = g1.edge(1, 2, 1);
782
783 g1 ~= e1;
784
785 assert(g1.get(g1.edge(1, 2)) == e1);
786 assertThrown!MissingEdgeException(g1.get(g1.edge(1, 1)));
787 }
788
789 /// Returns the index of node `n` in the list of nodes.
790 size_t indexOf(in Node n) const
791 {
792 auto sortedNodes = assumeSorted(nodes);
793 auto tristectedNodes = sortedNodes.trisect(n);
794
795 if (tristectedNodes[1].empty)
796 {
797 throw new MissingNodeException();
798 }
799
800 return tristectedNodes[0].length;
801 }
802
803 ///
804 unittest
805 {
806 auto g1 = Graph!(int, int)([1, 2]);
807
808 assert(g1.indexOf(1) == 0);
809 assert(g1.indexOf(2) == 1);
810 assertThrown!MissingNodeException(g1.indexOf(3));
811 }
812
813 /// Returns the index of node `n` in the list of nodes.
814 size_t indexOf(in Edge edge) const
815 {
816 auto sortedEdges = assumeSorted!orderByNodes(edges);
817 auto trisectedEdges = sortedEdges.trisect(edge);
818
819 if (trisectedEdges[1].empty)
820 {
821 throw new MissingEdgeException();
822 }
823
824 return trisectedEdges[0].length;
825 }
826
827 ///
828 unittest
829 {
830 auto g1 = Graph!(int, int)([1, 2]);
831
832 auto e1 = g1.edge(1, 2, 1);
833
834 g1 ~= e1;
835
836 assert(g1.indexOf(g1.edge(1, 2)) == 0);
837 assertThrown!MissingEdgeException(g1.indexOf(g1.edge(1, 1)));
838 }
839
840 static if (isDirected)
841 {
842 /// Returns a range of in/outgoing edges of node `n`.
843 auto inEdges(Node n) nothrow pure
844 {
845 return _edges.data[].filter!(e => e.end == n);
846 }
847
848 /// ditto
849 auto inEdges(Node n) const nothrow pure
850 {
851 return edges[].filter!(e => e.end == n);
852 }
853
854 /// ditto
855 auto outEdges(Node n) nothrow pure
856 {
857 return _edges.data[].filter!(e => e.start == n);
858 }
859
860 /// ditto
861 auto outEdges(Node n) const nothrow pure
862 {
863 return edges[].filter!(e => e.start == n);
864 }
865
866 ///
867 unittest
868 {
869 import std.algorithm : equal;
870
871 auto g1 = Graph!(int, void, Yes.isDirected)([1, 2, 3]);
872
873 g1 ~= g1.edge(1, 1);
874 g1 ~= g1.edge(1, 2);
875 g1 ~= g1.edge(2, 2);
876 g1 ~= g1.edge(2, 3);
877
878 assert(g1.inEdges(1).equal([
879 g1.edge(1, 1),
880 ]));
881 assert(g1.outEdges(1).equal([
882 g1.edge(1, 1),
883 g1.edge(1, 2),
884 ]));
885 assert(g1.inEdges(2).equal([
886 g1.edge(1, 2),
887 g1.edge(2, 2),
888 ]));
889 assert(g1.outEdges(2).equal([
890 g1.edge(2, 2),
891 g1.edge(2, 3),
892 ]));
893 assert(g1.inEdges(3).equal([
894 g1.edge(2, 3),
895 ]));
896 assert(g1.outEdges(3).empty);
897 }
898
899 /// Get the in/out degree of node `n`.
900 size_t inDegree(Node n) const nothrow pure
901 {
902 return inEdges(n).walkLength;
903 }
904
905 /// ditto
906 size_t outDegree(Node n) const nothrow pure
907 {
908 return outEdges(n).walkLength;
909 }
910
911 ///
912 unittest
913 {
914 auto g1 = Graph!(int, void, Yes.isDirected)([1, 2, 3]);
915
916 g1 ~= g1.edge(1, 1);
917 g1 ~= g1.edge(1, 2);
918 g1 ~= g1.edge(2, 2);
919 g1 ~= g1.edge(2, 3);
920
921 assert(g1.inDegree(1) == 1);
922 assert(g1.outDegree(1) == 2);
923 assert(g1.inDegree(2) == 2);
924 assert(g1.outDegree(2) == 2);
925 assert(g1.inDegree(3) == 1);
926 assert(g1.outDegree(3) == 0);
927 }
928 }
929 else
930 {
931 /// Returns a range of all edges incident to node `n`.
932 auto incidentEdges(Node n) nothrow pure
933 {
934 return _edges.data[].filter!(e => e.start == n || e.end == n);
935 }
936
937 /// ditto
938 auto incidentEdges(Node n) const nothrow pure
939 {
940 return edges[].filter!(e => e.start == n || e.end == n);
941 }
942
943 /// ditto
944 alias inEdges = incidentEdges;
945
946 /// ditto
947 alias outEdges = incidentEdges;
948
949 ///
950 unittest
951 {
952 import std.algorithm : equal;
953
954 auto g1 = Graph!int([1, 2, 3]);
955
956 g1 ~= g1.edge(1, 1);
957 g1 ~= g1.edge(1, 2);
958 g1 ~= g1.edge(2, 2);
959 g1 ~= g1.edge(2, 3);
960
961 assert(g1.incidentEdges(1).equal([
962 g1.edge(1, 1),
963 g1.edge(1, 2),
964 ]));
965 assert(g1.incidentEdges(2).equal([
966 g1.edge(1, 2),
967 g1.edge(2, 2),
968 g1.edge(2, 3),
969 ]));
970 assert(g1.incidentEdges(3).equal([
971 g1.edge(2, 3),
972 ]));
973 }
974
975 IncidentEdgesCache allIncidentEdges()
976 {
977 return IncidentEdgesCache(this);
978 }
979
980 static struct IncidentEdgesCache
981 {
982 alias G = Graph!(Node, Weight, isDirected, EdgePayload);
983 G graph;
984 Edge[][] incidentEdges;
985
986 this(G graph)
987 {
988 this.graph = graph;
989 collectAllIncidentEdges();
990 }
991
992 private void collectAllIncidentEdges()
993 {
994 preallocateMemory();
995
996 size_t startIdx;
997 size_t endIdx;
998 foreach (edge; graph._edges.data)
999 {
1000 if (graph._nodes[startIdx] < edge.start)
1001 endIdx = startIdx;
1002 while (graph._nodes[startIdx] < edge.start)
1003 ++startIdx;
1004 if (endIdx < startIdx)
1005 endIdx = startIdx;
1006 while (graph._nodes[endIdx] < edge.end)
1007 ++endIdx;
1008
1009 incidentEdges[startIdx] ~= edge;
1010 // Avoid double-counting of loops
1011 if (startIdx != endIdx)
1012 incidentEdges[endIdx] ~= edge;
1013 }
1014 }
1015
1016 void preallocateMemory()
1017 {
1018 auto degreesCache = graph.allDegrees();
1019 Edge[] buffer;
1020 buffer.length = degreesCache.degrees.sum;
1021 incidentEdges.length = degreesCache.degrees.length;
1022
1023 size_t sliceBegin;
1024 size_t startIdx;
1025 foreach (degree; degreesCache)
1026 {
1027 incidentEdges[startIdx] = buffer[sliceBegin .. sliceBegin + degree];
1028 incidentEdges[startIdx].length = 0;
1029
1030 sliceBegin += degree;
1031 ++startIdx;
1032 }
1033 }
1034
1035 ref inout(Edge[]) opIndex(in Node node) inout
1036 {
1037 return incidentEdges[graph.indexOf(node)];
1038 }
1039
1040 ref inout(Edge[]) opIndex(in size_t nodeIdx) inout
1041 {
1042 return incidentEdges[nodeIdx];
1043 }
1044
1045 int opApply(scope int delegate(Edge[]) yield)
1046 {
1047 int result = 0;
1048
1049 foreach (currentIncidentEdges; incidentEdges)
1050 {
1051 result = yield(currentIncidentEdges);
1052 if (result)
1053 break;
1054 }
1055
1056 return result;
1057 }
1058
1059 int opApply(scope int delegate(Node, Edge[]) yield)
1060 {
1061 int result = 0;
1062
1063 foreach (i, currentIncidentEdges; incidentEdges)
1064 {
1065 result = yield(graph._nodes[i], currentIncidentEdges);
1066 if (result)
1067 break;
1068 }
1069
1070 return result;
1071 }
1072 }
1073
1074 /// Get the `adjacencyList` of this graph where nodes are represented
1075 /// by their index in the nodes list.
1076 size_t[][] adjacencyList() const
1077 {
1078 size_t[][] _adjacencyList;
1079 _adjacencyList.length = nodes.length;
1080 size_t[] targetsBuffer;
1081 targetsBuffer.length = 2 * edges.length;
1082
1083 foreach (i, node; _nodes)
1084 {
1085 auto bufferRest = edges
1086 .filter!(e => e.start == node || e.end == node)
1087 .map!(edge => indexOf(edge.target(node)))
1088 .copy(targetsBuffer);
1089 _adjacencyList[i] = targetsBuffer[0 .. $ - bufferRest.length];
1090 _adjacencyList[i].sort;
1091 targetsBuffer = bufferRest;
1092 }
1093
1094 return _adjacencyList;
1095 }
1096
1097 ///
1098 unittest
1099 {
1100 auto g1 = Graph!int([1, 2, 3, 4]);
1101
1102 g1 ~= g1.edge(1, 1);
1103 g1 ~= g1.edge(1, 2);
1104 g1 ~= g1.edge(2, 2);
1105 g1 ~= g1.edge(2, 3);
1106 g1 ~= g1.edge(2, 4);
1107 g1 ~= g1.edge(3, 4);
1108
1109 assert(g1.adjacencyList() == [
1110 [0, 1],
1111 [0, 1, 2, 3],
1112 [1, 3],
1113 [1, 2],
1114 ]);
1115 }
1116
1117 /// Get the degree of node `n`.
1118 size_t degree(Node n) const nothrow pure
1119 {
1120 return incidentEdges(n).walkLength;
1121 }
1122
1123 /// ditto
1124 alias inDegree = degree;
1125
1126 /// ditto
1127 alias outDegree = degree;
1128
1129 DegreesCache allDegrees() const
1130 {
1131 return DegreesCache(this);
1132 }
1133
1134 static struct DegreesCache
1135 {
1136 alias G = Graph!(Node, Weight, isDirected, EdgePayload);
1137 const(G) graph;
1138 size_t[] degrees;
1139
1140 this(in G graph)
1141 {
1142 this.graph = graph;
1143 collectAllDegrees();
1144 }
1145
1146 private void collectAllDegrees()
1147 {
1148 degrees.length = graph._nodes.length;
1149
1150 size_t startIdx;
1151 size_t endIdx;
1152 foreach (edge; graph._edges.data)
1153 {
1154 if (graph._nodes[startIdx] < edge.start)
1155 endIdx = startIdx;
1156 while (graph._nodes[startIdx] < edge.start)
1157 ++startIdx;
1158 if (endIdx < startIdx)
1159 endIdx = startIdx;
1160 while (graph._nodes[endIdx] < edge.end)
1161 ++endIdx;
1162
1163 ++degrees[startIdx];
1164 // Avoid double-counting of loops
1165 if (startIdx != endIdx)
1166 ++degrees[endIdx];
1167 }
1168 }
1169
1170 size_t opIndex(in Node node) const
1171 {
1172 return degrees[graph.indexOf(node)];
1173 }
1174
1175 size_t opIndex(in size_t nodeIdx) const
1176 {
1177 return degrees[nodeIdx];
1178 }
1179
1180 int opApply(scope int delegate(size_t) yield) const
1181 {
1182 int result = 0;
1183
1184 foreach (degree; degrees)
1185 {
1186 result = yield(degree);
1187 if (result)
1188 break;
1189 }
1190
1191 return result;
1192 }
1193
1194 int opApply(scope int delegate(Node, size_t) yield) const
1195 {
1196 int result = 0;
1197
1198 foreach (i, degree; degrees)
1199 {
1200 result = yield(graph._nodes[i], degree);
1201 if (result)
1202 break;
1203 }
1204
1205 return result;
1206 }
1207 }
1208 }
1209 }
1210
1211 ///
1212 unittest
1213 {
1214 // +-+ +-+
1215 // \ / \ /
1216 // (1)--(2)
1217 auto g1 = Graph!int([1, 2]);
1218
1219 g1 ~= g1.edge(1, 1);
1220 g1 ~= g1.edge(1, 2);
1221 g1.add(g1.edge(2, 2));
1222
1223 assert(g1.edge(1, 1) in g1);
1224 assert(g1.edge(1, 2) in g1);
1225 assert(g1.edge(2, 1) in g1);
1226 assert(g1.has(g1.edge(2, 2)));
1227 assert(g1.allDegrees().degrees == [2, 2]);
1228 assert(g1.allIncidentEdges().incidentEdges == [
1229 [g1.edge(1, 1), g1.edge(1, 2)],
1230 [g1.edge(1, 2), g1.edge(2, 2)],
1231 ]);
1232
1233 // 0.5 0.5
1234 // +-+ +-+
1235 // \ / \ /
1236 // (1)-----(2)
1237 // 1.0
1238 auto g2 = Graph!(int, double)([1, 2]);
1239
1240 g2 ~= g2.edge(1, 1, 0.5);
1241 g2 ~= g2.edge(1, 2, 1.0);
1242 g2.add(g2.edge(2, 2, 0.5));
1243
1244 assert(g2.edge(1, 1) in g2);
1245 assert(g2.edge(1, 2) in g2);
1246 assert(g2.edge(2, 1) in g2);
1247 assert(g2.has(g2.edge(2, 2)));
1248 assert(g2.allDegrees().degrees == [2, 2]);
1249 assert(g2.allIncidentEdges().incidentEdges == [
1250 [g2.edge(1, 1, 0.5), g2.edge(1, 2, 1.0)],
1251 [g2.edge(1, 2, 1.0), g2.edge(2, 2, 0.5)],
1252 ]);
1253
1254 // 0.5 0.5
1255 // +-+ +-+
1256 // \ v v /
1257 // (1)---->(2)
1258 // 1.0
1259 auto g3 = Graph!(int, double, Yes.isDirected)([1, 2]);
1260
1261 g3 ~= g3.edge(1, 1, 0.5);
1262 g3 ~= g3.edge(1, 2, 1.0);
1263 g3.add(g3.edge(2, 2, 0.5));
1264
1265 assert(g3.edge(1, 1) in g3);
1266 assert(g3.edge(1, 2) in g3);
1267 assert(!(g3.edge(2, 1) in g3));
1268 assert(g3.has(g3.edge(2, 2)));
1269
1270 // +-+ +-+
1271 // \ v v /
1272 // (1)-->(2)
1273 auto g4 = Graph!(int, void, Yes.isDirected)([1, 2]);
1274
1275 g4 ~= g4.edge(1, 1);
1276 g4 ~= g4.edge(1, 2);
1277 g4.add(g4.edge(2, 2));
1278
1279 assert(g4.edge(1, 1) in g4);
1280 assert(g4.edge(1, 2) in g4);
1281 assert(!(g4.edge(2, 1) in g4));
1282 assert(g4.has(g4.edge(2, 2)));
1283
1284 // +-+ +-+
1285 // \ / \ /
1286 // (1)--(2)
1287 //
1288 // payload(1, 1) = [1];
1289 // payload(1, 2) = [2];
1290 // payload(2, 2) = [3];
1291 auto g5 = Graph!(int, void, No.isDirected, int[])([1, 2]);
1292
1293 g5 ~= g5.edge(1, 1, [1]);
1294 g5 ~= g5.edge(1, 2, [2]);
1295 g5.add(g5.edge(2, 2, [3]));
1296
1297 assert(g5.edge(1, 1) in g5);
1298 assert(g5.get(g5.edge(1, 1)).payload == [1]);
1299 assert(g5.edge(1, 2) in g5);
1300 assert(g5.get(g5.edge(1, 2)).payload == [2]);
1301 assert(g5.edge(2, 1) in g5);
1302 assert(g5.get(g5.edge(2, 1)).payload == [2]);
1303 assert(g5.has(g5.edge(2, 2)));
1304 assert(g5.get(g5.edge(2, 2)).payload == [3]);
1305 assert(g5.allDegrees().degrees == [2, 2]);
1306 assert(g5.allIncidentEdges().incidentEdges == [
1307 [g5.edge(1, 1), g5.edge(1, 2)],
1308 [g5.edge(1, 2), g5.edge(2, 2)],
1309 ]);
1310 }
1311
1312 ///
1313 unittest
1314 {
1315 // -1 1 1
1316 // (1)----(2)---(3) (4)---(5) (6)
1317 size_t[] contigs = [1, 2, 3, 4, 5, 6];
1318 auto contigGraph = Graph!(size_t, int)([1, 2, 3, 4, 5, 6]);
1319
1320 contigGraph.add(contigGraph.edge(1, 2, -1));
1321 contigGraph.add(contigGraph.edge(2, 3, 1));
1322 contigGraph.add(contigGraph.edge(4, 5, 1));
1323
1324 foreach (contig; contigs)
1325 {
1326 assert(contigGraph.degree(contig) <= 2);
1327 }
1328 assert(contigGraph.allDegrees().degrees == [1, 2, 1, 1, 1, 0]);
1329 assert(contigGraph.allIncidentEdges().incidentEdges == [
1330 [contigGraph.edge(1, 2, -1)],
1331 [contigGraph.edge(1, 2, -1), contigGraph.edge(2, 3, 1)],
1332 [contigGraph.edge(2, 3, 1)],
1333 [contigGraph.edge(4, 5, 1)],
1334 [contigGraph.edge(4, 5, 1)],
1335 [],
1336 ]);
1337 }
1338
1339 /// Add a set of edges to this graph and merge mutli-edges using `merge`.
1340 void bulkAdd(alias merge, G, R)(ref G graph, R edges)
1341 if (is(G : Graph!Params, Params...) && isForwardRange!R && is(ElementType!R == G.Edge))
1342 {
1343 alias Edge = G.Edge;
1344 alias ReturnTypeMerge = typeof(merge(new Edge[0]));
1345 static assert(is(ReturnTypeMerge == Edge), "expected `Edge merge(Edge[] multiEdge)`");
1346
1347 graph.bulkAddForce(edges);
1348
1349 auto bufferRest = graph
1350 ._edges
1351 .data
1352 .sliceBy!(G.groupByNodes)
1353 .map!(unaryFun!merge)
1354 .copy(graph._edges.data);
1355 graph._edges.shrinkTo(graph._edges.data.length - bufferRest.length);
1356 }
1357
1358 ///
1359 unittest
1360 {
1361 auto g1 = Graph!(int, int)([1, 2]);
1362
1363 static g1.Edge sumWeights(g1.Edge[] multiEdge)
1364 {
1365 auto sumOfWeights = multiEdge.map!"a.weight".sum;
1366 auto mergedEdge = multiEdge[0];
1367 mergedEdge.weight = sumOfWeights;
1368
1369 return mergedEdge;
1370 }
1371
1372 auto edges = [
1373 g1.edge(1, 2, 1),
1374 g1.edge(1, 2, 1),
1375 g1.edge(1, 2, 1),
1376 g1.edge(2, 3, 2),
1377 g1.edge(2, 3, 2),
1378 g1.edge(3, 4, 3),
1379 ];
1380 g1.bulkAdd!sumWeights(edges);
1381 assert(g1.edges == [
1382 g1.edge(1, 2, 3),
1383 g1.edge(2, 3, 4),
1384 g1.edge(3, 4, 3),
1385 ]);
1386 }
1387
1388 /// Add an edge to this graph and handle existing edges with `handleConflict`.
1389 /// The handler must have this signature `Edge handleConflict(Edge, Edge)`.
1390 G.Edge add(alias handleConflict = 1337, G)(ref G graph, G.Edge edge)
1391 if (is(G : Graph!Params, Params...))
1392 {
1393 static if (isCallable!handleConflict)
1394 alias handleConflict_ = binaryFun!handleConflict;
1395 else
1396 alias handleConflict_ = binaryFun!(G.ConflictStrategy.error);
1397
1398 if (!graph.has(edge.start) || !graph.has(edge.end))
1399 {
1400 throw new MissingNodeException();
1401 }
1402
1403 auto sortedEdges = assumeSorted!(G.orderByNodes)(graph._edges.data);
1404 auto trisectedEdges = sortedEdges.trisect(edge);
1405 auto existingEdges = trisectedEdges[1];
1406 auto existingEdgeIdx = trisectedEdges[0].length;
1407
1408 if (existingEdges.empty)
1409 {
1410 return graph.forceAdd(edge);
1411 }
1412 else
1413 {
1414 auto newEdge = handleConflict_(existingEdges.front, edge);
1415
1416 return graph.replaceEdge(existingEdgeIdx, newEdge);
1417 }
1418 }
1419
1420 ///
1421 unittest
1422 {
1423 auto g1 = Graph!(int, int)([1, 2]);
1424
1425 auto e1 = g1.edge(1, 2, 1);
1426 auto e2 = g1.edge(1, 2, 2);
1427
1428 g1 ~= e1;
1429
1430 assertThrown!EdgeExistsException(g1.add(e2));
1431
1432 with (g1.ConflictStrategy)
1433 {
1434 g1.add!replace(e2);
1435
1436 assert(g1.get(g1.edge(1, 2)) == e2);
1437
1438 g1.add!keep(e1);
1439
1440 assert(g1.get(g1.edge(1, 2)) == e2);
1441
1442 g1.add!sumWeights(e2);
1443
1444 assert(g1.get(g1.edge(1, 2)).weight == 2 * e2.weight);
1445 }
1446 }
1447
1448 void filterEdges(alias pred, G)(ref G graph) if (is(G : Graph!Params, Params...))
1449 {
1450 auto bufferRest = graph
1451 ._edges
1452 .data
1453 .filter!pred
1454 .copy(graph._edges.data);
1455 graph._edges.shrinkTo(graph._edges.data.length - bufferRest.length);
1456 }
1457
1458 void mapEdges(alias fun, G)(ref G graph) if (is(G : Graph!Params, Params...))
1459 {
1460 foreach (ref edge; graph._edges.data)
1461 edge = unaryFun!fun(edge);
1462
1463 graph._edges.data.sort();
1464 }
1465
1466 class EmptySetException : Exception
1467 {
1468 this(string msg)
1469 {
1470 super(msg);
1471 }
1472 }
1473
1474 struct NaturalNumberSet
1475 {
1476 private static enum partSize = 8 * size_t.sizeof;
1477 private static enum size_t firstBit = 1;
1478 private static enum size_t lastBit = firstBit << (partSize - 1);
1479 private static enum size_t emptyPart = 0;
1480 private static enum size_t fullPart = ~emptyPart;
1481
1482 private size_t[] parts;
1483 private size_t nMax;
1484
1485 this(size_t initialNumElements, Flag!"addAll" addAll = No.addAll)
1486 {
1487 reserveFor(initialNumElements);
1488
1489 if (addAll)
1490 {
1491 foreach (i; 0 .. initialNumElements / partSize)
1492 parts[i] = fullPart;
1493 foreach (i; initialNumElements / partSize .. initialNumElements)
1494 add(i);
1495 }
1496 }
1497
1498 static NaturalNumberSet create(size_t[] initialElements...)
1499 {
1500 if (initialElements.length == 0)
1501 return NaturalNumberSet();
1502
1503 auto set = NaturalNumberSet(initialElements.maxElement);
1504
1505 foreach (i; initialElements)
1506 set.add(i);
1507
1508 return set;
1509 }
1510
1511 this(this)
1512 {
1513 parts = parts.dup;
1514 }
1515
1516 private this(size_t[] parts)
1517 {
1518 this.parts = parts;
1519 }
1520
1521 private bool inBounds(in size_t n) const pure nothrow
1522 {
1523 return n < nMax;
1524 }
1525
1526 void reserveFor(in size_t n)
1527 {
1528 if (parts.length == 0)
1529 {
1530 parts.length = max(1, ceildiv(n, partSize));
1531 nMax = parts.length * partSize;
1532 }
1533
1534 while (!inBounds(n))
1535 {
1536 parts.length *= 2;
1537 nMax = parts.length * partSize;
1538 }
1539 }
1540
1541 @property size_t capacity() pure const nothrow
1542 {
1543 return nMax;
1544 }
1545
1546 private size_t partIdx(in size_t n) const pure nothrow
1547 {
1548 return n / partSize;
1549 }
1550
1551 private size_t idxInPart(in size_t n) const pure nothrow
1552 {
1553 return n % partSize;
1554 }
1555
1556 private size_t itemMask(in size_t n) const pure nothrow
1557 {
1558 return firstBit << idxInPart(n);
1559 }
1560
1561 static size_t inverse(in size_t n) pure nothrow
1562 {
1563 return n ^ fullPart;
1564 }
1565
1566 void add(in size_t n)
1567 {
1568 reserveFor(n);
1569
1570 parts[partIdx(n)] |= itemMask(n);
1571 }
1572
1573 void remove(in size_t n)
1574 {
1575 if (!inBounds(n))
1576 {
1577 return;
1578 }
1579
1580 parts[partIdx(n)] &= inverse(itemMask(n));
1581 }
1582
1583 bool has(in size_t n) const pure nothrow
1584 {
1585 if (!inBounds(n))
1586 {
1587 return false;
1588 }
1589
1590 return (parts[partIdx(n)] & itemMask(n)) != emptyPart;
1591 }
1592
1593 bool opBinaryRight(string op)(in size_t n) const pure nothrow if (op == "in")
1594 {
1595 return this.has(n);
1596 }
1597
1598 bool empty() const pure nothrow
1599 {
1600 return parts.all!(part => part == emptyPart);
1601 }
1602
1603 void clear() pure nothrow
1604 {
1605 foreach (ref part; parts)
1606 part = emptyPart;
1607 }
1608
1609 bool opBinary(string op)(in NaturalNumberSet other) const pure nothrow if (op == "==")
1610 {
1611 auto numCommonParts = min(this.parts.length, other.parts.length);
1612
1613 foreach (i; 0 .. numCommonParts)
1614 {
1615 if (this.parts[i] != other.parts[i])
1616 return false;
1617 }
1618
1619 static bool hasEmptyTail(ref in NaturalNumberSet set, in size_t tailStart)
1620 {
1621 foreach (i; tailStart .. set.parts.length)
1622 if (set.parts[i] != emptyPart)
1623 return false;
1624
1625 return true;
1626 }
1627
1628 if (this.parts.length > numCommonParts)
1629 return hasEmptyTail(this, numCommonParts);
1630 if (other.parts.length > numCommonParts)
1631 return hasEmptyTail(other, numCommonParts);
1632
1633 return true;
1634 }
1635
1636 bool opBinary(string op)(in NaturalNumberSet other) const pure nothrow if (op == "in")
1637 {
1638 auto numCommonParts = min(this.parts.length, other.parts.length);
1639
1640 foreach (i; 0 .. numCommonParts)
1641 if ((this.parts[i] & other.parts[i]) != this.parts[i])
1642 return false;
1643
1644 static bool hasEmptyTail(ref in NaturalNumberSet set, in size_t tailStart)
1645 {
1646 foreach (i; tailStart .. set.parts.length)
1647 if (set.parts[i] != emptyPart)
1648 return false;
1649
1650 return true;
1651 }
1652
1653 if (this.parts.length > numCommonParts)
1654 return hasEmptyTail(this, numCommonParts);
1655
1656 return true;
1657 }
1658
1659 NaturalNumberSet opBinary(string op)(in NaturalNumberSet other) const pure nothrow if (op.among("|", "^", "&"))
1660 {
1661 NaturalNumberSet result;
1662 result.parts.length = max(this.parts.length, other.parts.length);
1663 result.nMax = max(this.nMax, other.nMax);
1664
1665 auto numCommonParts = min(this.parts.length, other.parts.length);
1666
1667 foreach (i; 0 .. numCommonParts)
1668 result.parts[i] = mixin("this.parts[i] " ~ op ~ " other.parts[i]");
1669
1670 static if (op.among("|", "^"))
1671 {
1672 if (this.parts.length > numCommonParts)
1673 result.parts[numCommonParts .. $] = this.parts[numCommonParts .. $];
1674 if (other.parts.length > numCommonParts)
1675 result.parts[numCommonParts .. $] = other.parts[numCommonParts .. $];
1676 }
1677
1678 return result;
1679 }
1680
1681 bool intersects(in NaturalNumberSet other) const pure nothrow
1682 {
1683 auto numCommonParts = min(this.parts.length, other.parts.length);
1684
1685 foreach (i; 0 .. numCommonParts)
1686 {
1687 if ((this.parts[i] & other.parts[i]) != emptyPart)
1688 return true;
1689 }
1690
1691 return false;
1692 }
1693
1694 @property size_t size() const pure nothrow
1695 {
1696 size_t numSetBits;
1697
1698 foreach (i, part; parts)
1699 {
1700 size_t j = 0;
1701
1702 while ((part >> j) != emptyPart && j < partSize)
1703 {
1704 while (((part >> j) & firstBit) != firstBit)
1705 ++j;
1706 ++numSetBits;
1707 ++j;
1708 }
1709 }
1710
1711 return numSetBits;
1712 }
1713
1714 size_t minElement() const
1715 {
1716 foreach (i, part; parts)
1717 {
1718 if (part != emptyPart)
1719 {
1720 size_t j = 0;
1721
1722 while (((part >> j) & firstBit) != firstBit)
1723 {
1724 ++j;
1725 }
1726
1727 return i * partSize + j;
1728 }
1729 }
1730
1731 throw new EmptySetException("empty set has no minElement");
1732 }
1733
1734 size_t maxElement() const
1735 {
1736 foreach (i, part; parts.retro.enumerate)
1737 {
1738 if (part != emptyPart)
1739 {
1740 size_t j = 0;
1741
1742 while (((part << j) & lastBit) != lastBit)
1743 {
1744 ++j;
1745 }
1746
1747 return (parts.length - i - 1) * partSize + (partSize - j - 1);
1748 }
1749 }
1750
1751 throw new EmptySetException("empty set has no maxElement");
1752 }
1753
1754 unittest
1755 {
1756 foreach (i; 0 .. 2 * NaturalNumberSet.partSize)
1757 {
1758 NaturalNumberSet set;
1759
1760 set.add(i + 5);
1761 set.add(i + 7);
1762
1763 assert(set.minElement() == i + 5);
1764 assert(set.maxElement() == i + 7);
1765 }
1766 }
1767
1768 /// Returns a range of the elements in this set. The elements are ordered
1769 /// ascending.
1770 @property auto elements() const pure nothrow
1771 {
1772 static struct ElementsRange
1773 {
1774 const NaturalNumberSet* set;
1775 bool _empty = false;
1776 size_t i = 0;
1777 size_t part;
1778 size_t j = 0;
1779
1780 this(const NaturalNumberSet* set) pure nothrow
1781 {
1782 this.set = set;
1783 this._empty = set.empty;
1784
1785 if (!this.empty)
1786 {
1787 this.part = set.parts[i];
1788 if (!set.has(front))
1789 {
1790 popFront();
1791 }
1792 }
1793 }
1794
1795 @property ElementsRange save() const pure nothrow
1796 {
1797 return this;
1798 }
1799
1800 void popFront() pure nothrow
1801 {
1802 assert(!empty, "Attempting to popFront an empty elements range");
1803 ++j;
1804
1805 while (shiftedPartEmpty)
1806 {
1807 nextPart();
1808
1809 if (empty)
1810 {
1811 return;
1812 }
1813 }
1814
1815 while (((part >> j) & firstBit) != firstBit && !shiftedPartEmpty)
1816 {
1817 ++j;
1818 }
1819
1820 if (shiftedPartEmpty)
1821 {
1822 popFront();
1823 }
1824 }
1825
1826 @property size_t front() const pure nothrow
1827 {
1828 assert(!empty, "Attempting to fetch the front of an empty elements range");
1829 return i * partSize + j;
1830 }
1831
1832 @property bool empty() const pure nothrow
1833 {
1834 return _empty;
1835 }
1836
1837 private @property bool shiftedPartEmpty() const pure nothrow
1838 {
1839 return (part >> j) == emptyPart || j >= partSize;
1840 }
1841
1842 private void nextPart() pure nothrow
1843 {
1844 // move to start of next part
1845 ++i;
1846 j = 0;
1847
1848 if (i < set.parts.length)
1849 {
1850 part = set.parts[i];
1851 }
1852 else
1853 {
1854 _empty = true;
1855 }
1856 }
1857 }
1858
1859 return ElementsRange(&this);
1860 }
1861
1862 ///
1863 unittest
1864 {
1865 import std.algorithm : equal;
1866 import std.range : iota;
1867
1868 NaturalNumberSet set;
1869 auto someNumbers = iota(set.partSize).filter!"a % 3 == 0";
1870
1871 foreach (i; someNumbers)
1872 {
1873 set.add(i);
1874 }
1875
1876 assert(equal(someNumbers, set.elements));
1877 }
1878
1879 /// The set may be modified while iterating:
1880 unittest
1881 {
1882 import std.algorithm : equal;
1883 import std.range : iota;
1884
1885 enum numElements = 64;
1886 auto set = NaturalNumberSet(numElements, Yes.addAll);
1887
1888 foreach (i; set.elements)
1889 {
1890 if (i % 10 == 0)
1891 set.remove(i + 1);
1892 }
1893
1894 auto expectedNumbers = iota(numElements).filter!"a == 0 || !((a - 1) % 10 == 0)";
1895 assert(equal(expectedNumbers, set.elements));
1896 }
1897
1898 string toString() const pure
1899 {
1900 return format("[%(%d,%)]", this.elements);
1901 }
1902 }
1903
1904 unittest
1905 {
1906 NaturalNumberSet set;
1907
1908 // add some numbers
1909 foreach (i; 0 .. set.partSize)
1910 {
1911 if (i % 2 == 0)
1912 {
1913 set.add(i);
1914 }
1915 }
1916
1917 // force extension of set
1918 foreach (i; set.partSize .. 2 * set.partSize)
1919 {
1920 if (i % 3 == 0)
1921 {
1922 set.add(i);
1923 }
1924 }
1925
1926 // validate presence
1927 foreach (i; 0 .. 2 * set.partSize)
1928 {
1929 if (i / set.partSize == 0 && i % 2 == 0)
1930 {
1931 assert(set.has(i));
1932 }
1933 else if (i / set.partSize == 1 && i % 3 == 0)
1934 {
1935 assert(set.has(i));
1936 }
1937 else
1938 {
1939 assert(!set.has(i));
1940 }
1941 }
1942 }
1943
1944 /**
1945 Find all maximal connected components of a graph-like structure. The
1946 predicate `isConnected` will be evaluated `O(n^^2)` times in the
1947 worst-case and `Ω(n)` in the best case. In expectation it will be
1948 evaluated `θ(n*log(n))`.
1949
1950 Params:
1951 isConnected = binary predicate that evaluates to true iff two nodes,
1952 represented as indices, are connected
1953 numNodes = total number of nodes in the graph
1954
1955 Returns: range of maxmimally connected components represented as
1956 `NaturalNumberSet`s
1957 */
1958 auto findMaximallyConnectedComponents(alias isConnected)(in size_t numNodes)
1959 {
1960 return MaximalConnectedComponents!(binaryFun!isConnected)(numNodes);
1961 }
1962
1963 ///
1964 unittest
1965 {
1966 import std.algorithm : equal;
1967 import std.range : only;
1968
1969 alias modEqv(size_t m) = (a, b) => (a % m) == (b % m);
1970 alias clusterByThreshold(size_t t) = (a, b) => (a < t) == (b < t);
1971
1972 assert(equal(
1973 findMaximallyConnectedComponents!(modEqv!5)(15),
1974 only(
1975 NaturalNumberSet.create(0, 5, 10),
1976 NaturalNumberSet.create(1, 6, 11),
1977 NaturalNumberSet.create(2, 7, 12),
1978 NaturalNumberSet.create(3, 8, 13),
1979 NaturalNumberSet.create(4, 9, 14),
1980 ),
1981 ));
1982 assert(equal(
1983 findMaximallyConnectedComponents!(modEqv!3)(15),
1984 only(
1985 NaturalNumberSet.create(0, 3, 6, 9, 12),
1986 NaturalNumberSet.create(1, 4, 7, 10, 13),
1987 NaturalNumberSet.create(2, 5, 8, 11, 14),
1988 ),
1989 ));
1990 assert(equal(
1991 findMaximallyConnectedComponents!(clusterByThreshold!10)(15),
1992 only(
1993 NaturalNumberSet.create(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
1994 NaturalNumberSet.create(10, 11, 12, 13, 14),
1995 ),
1996 ));
1997 }
1998
1999 ///
2000 unittest
2001 {
2002 import std.algorithm : equal;
2003 import std.range : only;
2004
2005 auto connectivity = [
2006 [false, false, false, true ],
2007 [false, false, true , false],
2008 [false, true , false, false],
2009 [true , false, false, false],
2010 ];
2011 alias isConnected = (i, j) => connectivity[i][j];
2012
2013 assert(equal(
2014 findMaximallyConnectedComponents!isConnected(4),
2015 only(
2016 NaturalNumberSet.create(0, 3),
2017 NaturalNumberSet.create(1, 2),
2018 ),
2019 ));
2020 }
2021
2022 private struct MaximalConnectedComponents(alias isConnected)
2023 {
2024
2025 const(size_t) numNodes;
2026 NaturalNumberSet unvisited;
2027 NaturalNumberSet currentComponent;
2028
2029 this(in size_t numNodes)
2030 {
2031 this.numNodes = numNodes;
2032 this.unvisited = NaturalNumberSet(numNodes, Yes.addAll);
2033 this.currentComponent = NaturalNumberSet(numNodes);
2034
2035 if (!empty)
2036 popFront();
2037 }
2038
2039 void popFront()
2040 {
2041 assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof);
2042
2043 currentComponent.clear();
2044
2045 if (unvisited.empty)
2046 return;
2047
2048 auto seedNode = unvisited.minElement;
2049
2050 maximizeConnectedComponent(seedNode);
2051 }
2052
2053 private void maximizeConnectedComponent(size_t node)
2054 {
2055 currentComponent.add(node);
2056 unvisited.remove(node);
2057
2058 foreach (nextNode; unvisited.elements)
2059 if (isConnected(node, nextNode))
2060 maximizeConnectedComponent(nextNode);
2061 }
2062
2063 @property NaturalNumberSet front()
2064 {
2065 assert(!empty, "Attempting to fetch the front an empty " ~ typeof(this).stringof);
2066
2067 return currentComponent;
2068 }
2069
2070 @property bool empty() const pure nothrow
2071 {
2072 return unvisited.empty && currentComponent.empty;
2073 }
2074 }
2075
2076 /**
2077 Find a cycle base of an undirected graph using the Paton's
2078 algorithm.
2079
2080 The algorithm is described in
2081
2082 > K. Paton, An algorithm for finding a fundamental set of cycles
2083 > for an undirected linear graph, Comm. ACM 12 (1969), pp. 514-518.
2084
2085 and the implementation is adapted from the [Java implementation][1] of
2086 K. Paton originally licensed under [Apache License 2.0][2].
2087
2088 [1]: https://code.google.com/archive/p/niographs/
2089 [2]: http://www.apache.org/licenses/LICENSE-2.0
2090
2091 Returns: range of cycles in the graph represented as arrays of node indices
2092 */
2093 auto findCyclicSubgraphs(G)(
2094 G graph,
2095 G.IncidentEdgesCache incidentEdgesCache = G.IncidentEdgesCache.init,
2096 )
2097 if (is(G : Graph!Params, Params...))
2098 {
2099 auto node(in size_t idx)
2100 {
2101 return graph.nodes[idx];
2102 }
2103
2104 version(assert) void assertValidCycle(in size_t[] cycle)
2105 {
2106 enum errorMsg = "not a cycle";
2107
2108 assert(
2109 cycle.length > 0 && graph.edge(node(cycle[0]), node(cycle[$ - 1])) in graph,
2110 errorMsg
2111 );
2112
2113 foreach (pair; cycle.slide!(No.withPartial)(2))
2114 assert(graph.edge(node(pair[0]), node(pair[1])) in graph, errorMsg);
2115 }
2116
2117 auto numNodes = graph.nodes.length;
2118
2119 NaturalNumberSet[] used;
2120 used.length = numNodes;
2121
2122 long[] parent;
2123 parent.length = numNodes;
2124 parent[] = -1;
2125
2126 size_t[] stack;
2127 stack.reserve(numNodes);
2128
2129 auto cycles = appender!(size_t[][]);
2130
2131 if (incidentEdgesCache == G.IncidentEdgesCache.init)
2132 incidentEdgesCache = graph.allIncidentEdges();
2133
2134 foreach (rootIdx, root; graph.nodes)
2135 {
2136 // Loop over the connected
2137 // components of the graph.
2138 if (parent[rootIdx] >= 0)
2139 continue;
2140
2141 // Prepare to walk the spanning tree.
2142 parent[rootIdx] = rootIdx;
2143 used[rootIdx].reserveFor(numNodes);
2144 used[rootIdx].add(rootIdx);
2145 stack ~= rootIdx;
2146
2147 // Do the walk. It is a BFS with
2148 // a LIFO instead of the usual
2149 // FIFO. Thus it is easier to
2150 // find the cycles in the tree.
2151 while (stack.length > 0)
2152 {
2153 auto currentIdx = stack[$ - 1];
2154 --stack.length;
2155 auto current = node(currentIdx);
2156 auto currentUsed = &used[currentIdx];
2157
2158 foreach (edge; incidentEdgesCache[currentIdx])
2159 {
2160 auto neighbour = edge.target(current);
2161 auto neighbourIdx = graph.indexOf(neighbour);
2162 auto neighbourUsed = &used[neighbourIdx];
2163
2164 if (neighbourUsed.empty)
2165 {
2166 // found a new node
2167 parent[neighbourIdx] = currentIdx;
2168 neighbourUsed.reserveFor(numNodes);
2169 neighbourUsed.add(currentIdx);
2170
2171 stack ~= neighbourIdx;
2172 }
2173 else if (neighbourIdx == currentIdx)
2174 {
2175 // found a self loop
2176 auto cycle = [currentIdx];
2177 cycles ~= cycle;
2178 version(assert) assertValidCycle(cycle);
2179 }
2180 else if (!currentUsed.has(neighbourIdx))
2181 {
2182 // found a cycle
2183 auto cycle = appender!(size_t[]);
2184 cycle ~= neighbourIdx;
2185 cycle ~= currentIdx;
2186
2187 auto p = parent[currentIdx];
2188 for (; !neighbourUsed.has(p); p = parent[p])
2189 cycle ~= p;
2190
2191 cycle ~= p;
2192 cycles ~= cycle.data;
2193 version(assert) assertValidCycle(cycle.data);
2194 neighbourUsed.add(currentIdx);
2195 }
2196 }
2197 }
2198 }
2199
2200 return cycles.data;
2201 }
2202
2203 ///
2204 unittest
2205 {
2206 alias G = Graph!int;
2207
2208 // __
2209 // \ \
2210 // `-0 -- 1 -- 2 -- 3
2211 // | / | |
2212 // | / | |
2213 // 4 -- 5 -- 6 7
2214 auto g = G([0, 1, 2, 3, 4, 5, 6, 7], [
2215 G.edge(0, 0),
2216 G.edge(0, 1),
2217 G.edge(0, 4),
2218 G.edge(1, 2),
2219 G.edge(2, 3),
2220 G.edge(2, 5),
2221 G.edge(2, 6),
2222 G.edge(3, 7),
2223 G.edge(4, 5),
2224 G.edge(5, 6),
2225 ]);
2226 auto cycles = g.findCyclicSubgraphs();
2227
2228 import std.algorithm : equal;
2229
2230 assert(cycles.equal([
2231 [0],
2232 [2, 6, 5],
2233 [1, 2, 5, 4, 0],
2234 ]));
2235 }
2236
2237 /**
2238 Find all maximal cliques in a graph represented by `adjacencyList`.
2239 The implementation is based on version 1 of the Bron-Kerbosch algorithm [1].
2240
2241 [1]: Bron, C.; Kerbosch, J. (1973), "Algorithm 457: finding all cliques
2242 of an undirected graph", Communications of the ACM, 16 (9): 575–577,
2243 doi:10.1145/362342.362367.
2244
2245 Returns: list of sets of nodes each representing a maximal clique
2246 */
2247 auto findAllCliques(in size_t[][] adjacencyList)
2248 {
2249 return BronKerboschVersion1(adjacencyList);
2250 }
2251
2252 ///
2253 unittest
2254 {
2255 auto g = Graph!int([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
2256 g.add(g.edge(0, 1));
2257 g.add(g.edge(0, 2));
2258 g.add(g.edge(1, 2));
2259 g.add(g.edge(1, 7));
2260 g.add(g.edge(1, 8));
2261 g.add(g.edge(2, 3));
2262 g.add(g.edge(3, 4));
2263 g.add(g.edge(3, 5));
2264 g.add(g.edge(3, 6));
2265 g.add(g.edge(4, 5));
2266 g.add(g.edge(4, 6));
2267 g.add(g.edge(5, 6));
2268 g.add(g.edge(6, 7));
2269 g.add(g.edge(7, 8));
2270
2271 auto cliques = array(findAllCliques(g.adjacencyList()));
2272
2273 assert(cliques == [
2274 [0, 1, 2],
2275 [1, 7, 8],
2276 [2, 3],
2277 [3, 4, 5, 6],
2278 [6, 7],
2279 [9],
2280 ]);
2281 }
2282
2283 private struct BronKerboschVersion1
2284 {
2285 const size_t[][] adjacencyList;
2286
2287 int opApply(scope int delegate(size_t[]) yield)
2288 {
2289 size_t[] clique;
2290 clique.reserve(adjacencyList.length);
2291
2292 auto candidates = NaturalNumberSet(adjacencyList.length, Yes.addAll);
2293 auto not = NaturalNumberSet(adjacencyList.length);
2294
2295 return extendClique(clique, candidates, not, yield);
2296 }
2297
2298 private int extendClique(
2299 size_t[] clique,
2300 NaturalNumberSet candidates,
2301 NaturalNumberSet not,
2302 scope int delegate(size_t[]) yield,
2303 )
2304 {
2305 import std.stdio;
2306
2307 if (not.empty && candidates.empty)
2308 return clique.length == 0 ? 0 : yield(clique);
2309
2310 int result;
2311
2312 foreach (candidate; candidates.elements)
2313 {
2314 clique ~= candidate;
2315
2316 auto reducedCandidates = NaturalNumberSet(adjacencyList.length);
2317 auto reducedNot = NaturalNumberSet(adjacencyList.length);
2318
2319 foreach (neighbourNode; adjacencyList[candidate])
2320 {
2321 if (candidates.has(neighbourNode))
2322 reducedCandidates.add(neighbourNode);
2323 if (not.has(neighbourNode))
2324 reducedNot.add(neighbourNode);
2325 }
2326
2327 result = extendClique(clique, reducedCandidates, reducedNot, yield);
2328
2329 if (result)
2330 return result;
2331
2332 candidates.remove(candidate);
2333 not.add(candidate);
2334 --clique.length;
2335 }
2336
2337 return result;
2338 }
2339 }
2340
2341 /**
2342 Calculate a longest increasing subsequence of `sequence`. This subsequence
2343 is not necessarily contiguous, or unique. Given a `sequence` of `n`
2344 elements the algorithm uses `O(n log n)` evaluation of `pred`.
2345
2346 See_Also: https://en.wikipedia.org/wiki/Longest_increasing_subsequence
2347 */
2348 auto longestIncreasingSubsequence(alias pred = "a < b", Range)(Range sequence)
2349 if (isRandomAccessRange!Range)
2350 {
2351 alias lessThan = binaryFun!pred;
2352
2353 size_t[] subseqEnds;
2354 subseqEnds.length = sequence.length;
2355 size_t[] predecessors;
2356 predecessors.length = sequence.length;
2357 size_t subseqLength;
2358
2359 foreach (i; 0 .. sequence.length)
2360 {
2361 // Binary search for the largest positive j < subseqLength
2362 // such that sequence[subseqEnds[j]] < sequence[i]
2363 long lo = 0;
2364 long hi = subseqLength - 1;
2365 auto pivot = sequence[i];
2366 assert(!lessThan(pivot, pivot), "`pred` is not anti-symmetric");
2367
2368 while (lo <= hi)
2369 {
2370 auto mid = ceildiv(lo + hi, 2);
2371
2372 if (lessThan(sequence[subseqEnds[mid]], pivot))
2373 lo = mid + 1;
2374 else
2375 hi = mid - 1;
2376 }
2377
2378 // After searching, lo + 1 is the length of the longest prefix of
2379 // sequence[i]
2380 auto newSubseqLength = lo + 1;
2381
2382 // The predecessor of sequence[i] is the last index of
2383 // the subsequence of length newSubseqLength - 1
2384 subseqEnds[lo] = i;
2385 if (lo > 0)
2386 predecessors[i] = subseqEnds[lo - 1];
2387
2388 if (newSubseqLength > subseqLength)
2389 // If we found a subsequence longer than any we've
2390 // found yet, update subseqLength
2391 subseqLength = newSubseqLength;
2392 }
2393
2394 auto subsequenceResult = subseqEnds[0 .. subseqLength];
2395
2396 if (subseqLength > 0)
2397 {
2398 // Reconstruct the longest increasing subsequence
2399 // Note: reusing memory from now unused subseqEnds
2400 auto k = subseqEnds[subseqLength - 1];
2401 foreach_reverse (i; 0 .. subseqLength)
2402 {
2403 subsequenceResult[i] = k;
2404 k = predecessors[k];
2405 }
2406 }
2407
2408 return subsequenceResult.map!(i => sequence[i]);
2409 }
2410
2411 /// Example from Wikipedia
2412 unittest
2413 {
2414 import std.algorithm : equal;
2415
2416 auto inputSequence = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
2417 auto expectedOutput = [0, 2, 6, 9, 11, 15];
2418
2419 assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2420 }
2421
2422 /// Example using a different `pred`
2423 unittest
2424 {
2425 import std.algorithm : equal;
2426 import std.range : retro;
2427
2428 auto inputSequence = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
2429 auto expectedOutput = [12, 10, 9, 5, 3];
2430
2431 assert(inputSequence.longestIncreasingSubsequence!"a > b".equal(expectedOutput));
2432 }
2433
2434 unittest
2435 {
2436 import std.algorithm : equal;
2437
2438 int[] inputSequence = [];
2439 int[] expectedOutput = [];
2440
2441 assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2442 }
2443
2444 unittest
2445 {
2446 import std.algorithm : equal;
2447
2448 auto inputSequence = [1, 2, 3, 4, 5];
2449 auto expectedOutput = [1, 2, 3, 4, 5];
2450
2451 assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2452 }
2453
2454 unittest
2455 {
2456 import std.algorithm : equal;
2457
2458 auto inputSequence = [2, 1, 3, 4, 5];
2459 auto expectedOutput = [1, 3, 4, 5];
2460
2461 assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2462 }
2463
2464 unittest
2465 {
2466 import std.algorithm : equal;
2467
2468 auto inputSequence = [1, 2, 3, 5, 4];
2469 auto expectedOutput = [1, 2, 3, 4];
2470
2471 assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2472 }