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;
12 import std.algorithm;
13 import std.algorithm : stdMean = mean;
14 import std.array;
15 import std.conv;
16 import std.exception;
17 import std.format;
18 import std.functional;
19 import std.math;
20 import std.math : stdFloor = floor;
21 import std.range;
22 import std..string;
23 import std.traits;
24 import std.typecons;
25 
26 debug import std.stdio : writeln;
27 
28 
29 /**
30     Standardized value that signifies undefined for all numeric types. It is
31     `NaN` for floating point types and the maximum representable value for
32     integer types.
33 
34     See_also: `isUndefined`
35 */
36 template undefined(T) if (isNumeric!T)
37 {
38     static if (is(typeof(T.nan)))
39         enum undefined = T.nan;
40     else static if (is(typeof(T.max)))
41         enum undefined = T.max;
42     else
43         static assert(0, "`undefined` is undefined for type " ~ E.stringof);
44 }
45 
46 
47 /**
48     Check if `value` has the standardized undefined value.
49 
50     See_also:   `undefined`
51     Returns:    True iff value is undefined according to `undefined`.
52 */
53 bool isUndefined(T)(T value) if (is(typeof(undefined!T)))
54 {
55     static if (isNaN(undefined!T))
56         return isNaN(value);
57     else
58         return value < undefined!T;
59 }
60 
61 
62 /// Calculate the mean of range.
63 ElementType!Range mean(Range)(Range values) if (isForwardRange!Range)
64 {
65     auto sum = values.save.sum;
66     auto length = values.walkLength.to!(ElementType!Range);
67 
68     return cast(typeof(return)) (sum / length);
69 }
70 
71 unittest
72 {
73     {
74         auto values = [2, 4, 8];
75         assert(values.mean == 4);
76     }
77     {
78         auto values = [1.0, 2.0, 3.0, 4.0];
79         assert(values.mean == 2.5);
80     }
81 }
82 
83 /// Calculate the weighted mean of values.
84 double mean(Values, Weights)(Values values, Weights weights)
85     if (isInputRange!Values && isForwardRange!Weights)
86 {
87     enum zeroWeight = cast(ElementType!Weights) 0;
88 
89     auto weightedSum = zip(StoppingPolicy.requireSameLength, values, weights)
90         .map!(pair => (pair[0] * pair[1]).to!double)
91         .sum;
92     auto totalWeight = weights.sum;
93 
94     return weightedSum / totalWeight;
95 }
96 
97 unittest
98 {
99     {
100         auto values = [2, 4, 6];
101         auto equalWeights = [1, 1, 1];
102         auto weights = [3, 4, 1];
103 
104         assert(mean(values, equalWeights) == mean(values));
105         assert(mean(values, weights) == 3.5);
106     }
107     {
108         auto values = [1.0, 2.0, 3.0, 4.0];
109         auto weights = [4.0, 3.0, 2.0, 1.0];
110 
111         assert(mean(values, weights) == 2.0);
112     }
113 }
114 
115 /**
116     Calculate the quantiles of values mapped with `fun`. The elements of
117     `values` are passed to `fun` before calculating the quantiles. This
118     removes the necessity of creating a proxy range.
119 
120     All quantiles are `undefined` if `values` is empty.
121 
122     Returns:    Range of quantiles. The returned range `isRandomAccessRange`,
123                 `hasSlicing` and has the same length as `ps`.
124 */
125 auto quantiles(alias fun = "a", Range, F)(Range values, F[] ps...)
126     if (isFloatingPoint!F)
127 in (ps.all!"0 < a && a < 1", "all ps must be 0 < p < 1")
128 {
129     alias f = unaryFun!fun;
130     alias E = typeof(f(values.front));
131 
132     auto sortedValues = values.sort!((a, b) => f(a) < f(b));
133     auto mappedValues = sortedValues.map!f;
134     auto n = mappedValues.length;
135 
136     auto calcQuantile(double q)
137     {
138         if (n == 0)
139         {
140             return undefined!E;
141         }
142         else if (isInteger(n * q))
143         {
144             auto i = cast(size_t) round(n * q);
145 
146             if (0 < i && i < n)
147                 return (mappedValues[i - 1] + mappedValues[i]) / 2;
148             else if (i == n)
149                 return mappedValues[n - 1];
150             else
151                 return mappedValues[0];
152         }
153         else
154         {
155             return mappedValues[cast(size_t) stdFloor(n * q)];
156         }
157     }
158 
159     auto _quantiles = ps.map!calcQuantile;
160 
161     alias Q = typeof(_quantiles);
162     static assert(isBidirectionalRange!Q);
163     static assert(isRandomAccessRange!Q);
164     static assert(hasSlicing!Q);
165     static assert(hasLength!Q);
166 
167     return _quantiles;
168 }
169 
170 unittest {
171     auto values = [4, 2, 8];
172     assert(values.quantiles(
173         1.0/3.0,
174         2.0/3.0,
175     ).equal([
176         3,
177         6,
178     ]));
179 }
180 
181 unittest {
182     auto values = [4, 2, 8, 0, 13];
183     assert(values.quantiles(
184         1/4f,
185         2/4f,
186         3/4f,
187     ).equal([
188         2,
189         4,
190         8,
191     ]));
192 }
193 
194 unittest {
195     auto values = [4, 2, 8];
196     assert(values.quantiles(
197         1/3f,
198         2/3f,
199     ).equal([
200         3,
201         6,
202     ]));
203 }
204 
205 unittest {
206     auto values = [4, 2, 8];
207     assert(values.quantiles(
208         1e-6,
209         1 - 1e-6,
210     ).equal([
211         2,
212         8,
213     ]));
214 }
215 
216 unittest {
217     double[] values = [];
218     assert(values.quantiles(
219         0.36,
220         0.46,
221         0.53,
222         0.85,
223         0.95,
224     ).all!isUndefined);
225 }
226 
227 unittest {
228     auto values = [0.839124, 0.506056, 0.661209, 0.235569, 0.747409, 0.182975,
229                    0.348437, 0.322757, 0.237597, 0.624974, 0.490688];
230     assert(values.quantiles(
231         0.36,
232         0.46,
233         0.53,
234         0.85,
235         0.95,
236     ).equal!approxEqual([
237         0.322757,
238         0.490688,
239         0.490688,
240         0.747409,
241         0.839124,
242     ]));
243 }
244 
245 
246 /**
247     Calculate the median of `fun`ed values. The elements of `values` are
248     passed to `fun` before calculating the median. This removes the necessity
249     of creating a proxy range.
250 
251     Returns:    Median of range or `undefined` iff `values` is empty.
252 */
253 auto median(alias fun = "a", Range)(Range values)
254 {
255     return quantiles!fun(values, 0.5)[0];
256 }
257 
258 unittest {
259     auto values = [4, 2, 8];
260     assert(values.median == 4);
261 }
262 
263 unittest {
264     auto values = [4, 3, 2, 8];
265     assert(values.median == 3);
266 }
267 
268 unittest {
269     auto values = [4, 6, 2, 8];
270     assert(values.median == 5);
271 }
272 
273 unittest {
274     auto values = [2, 1, 3, 0, 4, 9, 8, 5, 6, 3, 9];
275     assert(values.median == 4);
276 }
277 
278 unittest {
279     auto values = [2.0, 1.0, 4.0, 3.0];
280     assert(values.median == 2.5);
281 }
282 
283 unittest {
284     auto values = [2.0, 1.0, 4.0, 3.0, 5.0];
285     assert(values.median == 3.0);
286 }
287 
288 unittest {
289     double[] values = [];
290     assert(isUndefined(values.median));
291 }
292 
293 
294 /// Calculate the standard deviation (`sigma^^2`) of the sample.
295 ElementType!Range stddev(Range)(Range values) if (isForwardRange!Range)
296 {
297     auto sampleMean = mean(values.save);
298 
299     return stddev(values, sampleMean);
300 }
301 
302 /// ditto
303 ElementType!Range stddev(Range, M)(Range values, M sampleMean) if (isForwardRange!Range)
304 {
305     return values.save.map!(n => (n - sampleMean)^^2).sum / cast(ElementType!Range) values.save.walkLength;
306 }
307 
308 
309 /**
310     Eliminate outliers the deviate more than expected from the sample median.
311     A data point `x` is an outlier iff
312 
313         abs(x - median) > lambda*sqrt(stddev(values))
314 
315     Params:
316         values      Range of values.
317         lambda      Level of confidence.
318     Returns:
319         `values` without outliers.
320 */
321 auto eliminateOutliers(R, N)(R values, N lambda) if (isForwardRange!R)
322 {
323     return eliminateOutliers(values, lambda, mean(values.save));
324 }
325 
326 /// ditto
327 auto eliminateOutliers(R, N, M)(
328     R values,
329     N lambda,
330     M sampleMean,
331 ) if (isForwardRange!R)
332 {
333     return eliminateOutliers(values, lambda, sampleMean, stddev(values.save, sampleMean));
334 }
335 
336 /// ditto
337 auto eliminateOutliers(R, N, M, S)(
338     R values,
339     N lambda,
340     M sampleMean,
341     S sampleStddev,
342 ) if (isForwardRange!R)
343 {
344     return eliminateOutliers(values, lambda, sampleMean, sampleStddev, median(values.save));
345 }
346 
347 /// ditto
348 auto eliminateOutliers(R, N, M, S, D)(
349     R values,
350     N lambda,
351     M sampleMean,
352     S sampleStddev,
353     D sampleMedian,
354 ) if (isForwardRange!R)
355 {
356     auto threshold = lambda * sqrt(cast(double) sampleStddev);
357 
358     return values.filter!(n => absdiff(n, sampleMedian) <= threshold);
359 }
360 
361 unittest
362 {
363     assert(equal([1].eliminateOutliers(1), [1]));
364 }
365 
366 
367 /**
368     Calculate the Nxx (e.g. N50) of values. `values` will be `sort`ed in the
369     process. If this is undesired the range must be `dup`licated beforehands.
370     The elements of `values` are passed to `map` before calculating the
371     median. This removes the necessity of creating a proxy range.
372 
373     The CTFE-version differs from the dynamic implementation only in a static
374     check for a valid value of `xx` and the order of arguments.
375     The CTFE-version should be preferred if possible because it looks nicer.
376 
377     Returns:    Nxx statistic or `undefined` value iff `values` is empty or
378                 `map`ped `values` sum up to less than `totalSize`.
379 */
380 auto N(real xx, alias map = "a", Range, Num)(Range values, Num totalSize)
381 {
382     static assert(0 < xx && xx < 100, "N" ~ xx.to!string ~ " is undefined");
383 
384     return N!map(values, xx, totalSize);
385 }
386 
387 auto N(alias map = "a", Range, Num)(Range values, real xx, Num totalSize)
388 {
389     assert(0 < xx && xx < 100, format!"N%f is undefined"(xx));
390 
391     alias _map = unaryFun!map;
392     alias E = typeof(_map(values.front));
393 
394     if (values.length == 0)
395         return undefined!E;
396 
397     auto xxPercentile = xx/100.0 * totalSize;
398     auto sortedValues = values.sort!((a, b) => _map(a) > _map(b));
399     auto targetIndex = sortedValues
400         .cumulativeFold!((acc, el) => acc + _map(el))(cast(E) 0)
401         .countUntil!((partialSum, total) => partialSum >= total)(xxPercentile);
402 
403     if (targetIndex < 0 || values.length <= targetIndex)
404         return undefined!E;
405     else
406         return _map(sortedValues[targetIndex]);
407 }
408 
409 unittest
410 {
411     {
412         auto totalSize = 54;
413         auto values = [2, 3, 4, 5, 6, 7, 8, 9, 10];
414         enum N50 = 8;
415         enum N10 = 10;
416 
417         assert(N(values, 50, totalSize) == N50);
418         assert(N!50(values, totalSize) == N50);
419         assert(N(values, 10, totalSize) == N10);
420         assert(N!10(values, totalSize) == N10);
421     }
422     {
423         auto totalSize = 32;
424         auto values = [2, 2, 2, 3, 3, 4, 8, 8];
425         enum N50 = 8;
426 
427         assert(N(values, 50, totalSize) == N50);
428         assert(N!50(values, totalSize) == N50);
429     }
430 }
431 
432 
433 enum RoundingMode : byte
434 {
435     floor,
436     round,
437     ceil,
438 }
439 
440 /**
441     Round x upward according to base, ie. returns the next integer larger or
442     equal to x which is divisible by base.
443 
444     Returns: x rounded upward according to base.
445 */
446 Integer ceil(Integer)(in Integer x, in Integer base) pure nothrow
447         if (isIntegral!Integer)
448 {
449     return x % base == 0
450         ? x
451         : (x / base + 1) * base;
452 }
453 
454 ///
455 unittest
456 {
457     assert(ceil(8, 10) == 10);
458     assert(ceil(32, 16) == 32);
459     assert(ceil(101, 100) == 200);
460 }
461 
462 /**
463     Round x downward according to base, ie. returns the next integer smaller or
464     equal to x which is divisible by base.
465 
466     Returns: x rounded downward according to base.
467 */
468 Integer floor(Integer)(in Integer x, in Integer base) pure nothrow
469         if (isIntegral!Integer)
470 {
471     return (x / base) * base;
472 }
473 
474 ///
475 unittest
476 {
477     assert(floor(8, 10) == 0);
478     assert(floor(32, 16) == 32);
479     assert(floor(101, 100) == 100);
480 }
481 
482 /// Returns the absolute difference between two numbers.
483 Num absdiff(Num)(in Num a, in Num b) pure nothrow if (isNumeric!Num)
484 {
485     return a > b
486         ? a - b
487         : b - a;
488 }
489 
490 ///
491 unittest
492 {
493     assert(absdiff(2UL, 3UL) == 1UL);
494     assert(absdiff(-42, 13) == 55);
495     assert(absdiff(2.5, 5) == 2.5);
496 }
497 
498 /// Returns the result of `ceil(a / b)` but uses integer arithmetic only.
499 Integer ceildiv(Integer)(in Integer a, in Integer b) pure nothrow if (isIntegral!Integer)
500 {
501     Integer resultSign = (a < 0) ^ (b < 0) ? -1 : 1;
502 
503     return resultSign < 0 || a % b == 0
504         ? a / b
505         : a / b + resultSign;
506 }
507 
508 ///
509 unittest
510 {
511     assert(ceildiv(0, 3) == 0);
512     assert(ceildiv(1UL, 3UL) == 1UL);
513     assert(ceildiv(2L, 3L) == 1L);
514     assert(ceildiv(3U, 3U) == 1U);
515     assert(ceildiv(4, 3) == 2);
516     assert(ceildiv(-4, 4) == -1);
517     assert(ceildiv(-4, 3) == -1);
518 }
519 
520 
521 bool isInteger(F)(F x, F eps = 1e-5) pure nothrow @safe
522     if (isFloatingPoint!F)
523 in (0 <= eps)
524 {
525     return abs(x - round(x)) <= eps;
526 }
527 
528 
529 class EdgeExistsException : Exception
530 {
531     pure nothrow @nogc @safe this(
532         string file = __FILE__,
533         size_t line = __LINE__,
534         Throwable nextInChain = null,
535     )
536     {
537         super("edge cannot be inserted: edge already exists", file, line, nextInChain);
538     }
539 }
540 
541 class NodeExistsException : Exception
542 {
543     pure nothrow @nogc @safe this(
544         string file = __FILE__,
545         size_t line = __LINE__,
546         Throwable nextInChain = null,
547     )
548     {
549         super("node cannot be inserted: node already exists", file, line, nextInChain);
550     }
551 }
552 
553 class MissingEdgeException : Exception
554 {
555     pure nothrow @nogc @safe this(
556         string file = __FILE__,
557         size_t line = __LINE__,
558         Throwable nextInChain = null,
559     )
560     {
561         super("edge not found", file, line, nextInChain);
562     }
563 }
564 
565 class MissingNodeException : Exception
566 {
567     pure nothrow @nogc @safe this(
568         string file = __FILE__,
569         size_t line = __LINE__,
570         Throwable nextInChain = null,
571     )
572     {
573         super("node not found", file, line, nextInChain);
574     }
575 }
576 
577 /// This structure represents a graph with optional edge
578 /// payloads. The graph is represented as a list of edges which is
579 /// particularly suited for sparse graphs. While the set of nodes is fixed the
580 /// set of edges is mutable.
581 struct Graph(Node, Weight = void, Flag!"isDirected" isDirected = No.isDirected, EdgePayload = void)
582 {
583     static enum isWeighted = !is(Weight == void);
584     static enum hasEdgePayload = !is(EdgePayload == void);
585 
586     static struct Edge
587     {
588         protected Node _start;
589         protected Node _end;
590 
591         static if (isWeighted)
592             Weight weight;
593 
594         static if (hasEdgePayload)
595             EdgePayload payload;
596 
597         /// Construct an edge.
598         this(Node start, Node end)
599         {
600             this._start = start;
601             this._end = end;
602 
603             static if (!isDirected)
604             {
605                 if (end < start)
606                 {
607                     swap(this._start, this._end);
608                 }
609             }
610         }
611 
612         static if (isWeighted)
613         {
614             /// ditto
615             this(Node start, Node end, Weight weight)
616             {
617                 this(start, end);
618                 this.weight = weight;
619             }
620         }
621 
622         static if (hasEdgePayload && !is(EdgePayload : Weight))
623         {
624             /// ditto
625             this(Node start, Node end, EdgePayload payload)
626             {
627                 this(start, end);
628                 this.payload = payload;
629             }
630         }
631 
632         static if (isWeighted && hasEdgePayload)
633         {
634             /// ditto
635             this(Node start, Node end, Weight weight, EdgePayload payload)
636             {
637                 this(start, end);
638                 this.weight = weight;
639                 this.payload = payload;
640             }
641         }
642 
643         /// Get the start of this edge. For undirected graphs this is the
644         /// smaller of both incident nodes.
645         @property Node start() const pure nothrow
646         {
647             return _start;
648         }
649 
650         /// Get the end of this edge. For undirected graphs this is the
651         /// larger of both incident nodes.
652         @property Node end() const pure nothrow
653         {
654             return _end;
655         }
656 
657         /**
658             Get target of this edge beginning at node `from`. For undirected
659             graphs returns the other node of this edge.
660 
661             Throws: MissingNodeException if this edge does not start in node `from`.
662         */
663         Node target(Node from) const
664         {
665             static if (isDirected)
666             {
667                 if (start == from)
668                 {
669                     return end;
670                 }
671                 else
672                 {
673                     throw new MissingNodeException();
674                 }
675             }
676             else
677             {
678                 if (start == from)
679                 {
680                     return end;
681                 }
682                 else if (end == from)
683                 {
684                     return start;
685                 }
686                 else
687                 {
688                     throw new MissingNodeException();
689                 }
690             }
691         }
692 
693         /**
694             Get source of this edge beginning at node `from`. For undirected
695             graphs returns the other node of this edge.
696 
697             Throws: MissingNodeException if this edge does not end in node `from`.
698         */
699         static if (isDirected)
700         {
701             Node source(Node from) const
702             {
703                 if (end == from)
704                 {
705                     return start;
706                 }
707                 else
708                 {
709                     throw new MissingNodeException();
710                 }
711             }
712         }
713         else
714         {
715             alias source = target;
716         }
717 
718         /// Two edges are equal iff their incident nodes (and weight) are the
719         /// same.
720         bool opEquals(in Edge other) const pure nothrow
721         {
722             static if (isWeighted)
723             {
724                 return this.start == other.start && this.end == other.end
725                     && this.weight == other.weight;
726             }
727             else
728             {
729                 return this.start == other.start && this.end == other.end;
730             }
731         }
732 
733         /// Orders edge lexicographically by `start`, `end`(, `weight`).
734         int opCmp(in Edge other) const pure nothrow
735         {
736             static if (isWeighted)
737             {
738                 return cmpLexicographically!(
739                     typeof(this),
740                     "a.start",
741                     "a.end",
742                     "a.weight",
743                 )(this, other);
744             }
745             else
746             {
747                 return cmpLexicographically!(
748                     typeof(this),
749                     "a.start",
750                     "a.end",
751                 )(this, other);
752             }
753         }
754 
755         private int compareNodes(in Edge other) const pure nothrow
756         {
757             return cmpLexicographically!(
758                 typeof(this),
759                 "a.start",
760                 "a.end",
761             )(this, other);
762         }
763 
764         /**
765             Returns the node that is connects this edge with other edge. In
766             case of undirected graphs this is just the common node of both
767             edges; in directed case this is the end node of this edge if it
768             matches the start node of other edge.
769 
770             Throws: MissingNodeException if the connecting node is undefined.
771         */
772         Node getConnectingNode(in Edge other) const
773         {
774             static if (isDirected)
775             {
776                 if (this.end == other.start)
777                 {
778                     return this.end;
779                 }
780             }
781             else
782             {
783                 if (this.end == other.start || this.end == other.end)
784                 {
785                     return this.end;
786                 }
787                 else if (this.start == other.start || this.start == other.end)
788                 {
789                     return this.start;
790                 }
791             }
792 
793             throw new MissingNodeException();
794         }
795     }
796 
797     static bool orderByNodes(in Edge a, in Edge b) nothrow pure
798     {
799         return a.compareNodes(b) < 0;
800     }
801 
802     static bool groupByNodes(in Edge a, in Edge b) nothrow pure
803     {
804         return a.compareNodes(b) == 0;
805     }
806 
807     /// Construct an edge for this graph.
808     static Edge edge(T...)(T args)
809     {
810         return Edge(args);
811     }
812 
813     protected Node[] _nodes;
814     protected Appender!(Edge[]) _edges;
815 
816     /// The set (ordered list) of nodes.
817     @property const(Node[]) nodes() const nothrow pure
818     {
819         return _nodes;
820     }
821 
822     private @property void nodes(Node[] nodes)
823     {
824         nodes.sort();
825 
826         this._nodes = nodes.uniq.array;
827     }
828 
829     /// Get the set (ordered list) of edges in this graph.
830     @property auto edges() nothrow pure
831     {
832         return chain(_edges.data);
833     }
834 
835     /// ditto
836     @property auto edges() const nothrow pure
837     {
838         return chain(_edges.data);
839     }
840 
841     /**
842         Construct a graph from a set of nodes (and edges). Modifies `nodes`
843         while sorting but releases it after construction.
844 
845         Throws: MissingNodeException if an edge has a node that is not present
846                 in this graph .
847         Throws: EdgeExistsException if an edge already exists when trying
848                 inserting it.
849     */
850     this(Node[] nodes)
851     {
852         this.nodes = nodes;
853     }
854 
855     /// ditto
856     this(Node[] nodes, Edge[] edges)
857     {
858         this(nodes);
859 
860         _edges.reserve(edges.length);
861         foreach (edge; edges)
862         {
863             add(this, edge);
864         }
865     }
866 
867     this(this)
868     {
869         _nodes = _nodes.dup;
870     }
871 
872     /// Add a set of edges to this graph without any checks.
873     void bulkAddForce(R)(R edges) if (isForwardRange!R && is(ElementType!R == Edge))
874     {
875         this._edges ~= edges;
876         _edges.data.sort;
877     }
878 
879     /// Add an edge to this graph.
880     /// See_Also: `Edge add(Graph, Edge)`
881     void opOpAssign(string op)(Edge edge) if (op == "~")
882     {
883         add(this, edge);
884     }
885 
886     /// Some pre-defined conflict handlers for `add`.
887     static struct ConflictStrategy
888     {
889         static if (isWeighted)
890         {
891             /// Return an edge with sum of both weights. If given payload will be
892             /// kept from existingEdge .
893             static Edge sumWeights(Edge existingEdge, Edge newEdge)
894             {
895                 existingEdge.weight += newEdge.weight;
896 
897                 return existingEdge;
898             }
899 
900             ///
901             unittest
902             {
903                 auto g1 = Graph!(int, int)([1, 2]);
904                 alias CS = g1.ConflictStrategy;
905 
906                 g1 ~= g1.edge(1, 2, 1);
907 
908                 auto addedEdge = g1.add!(CS.sumWeights)(g1.edge(1, 2, 1));
909 
910                 assert(addedEdge.weight == 2);
911             }
912         }
913 
914         /// Throw `EdgeExistsException`.
915         static inout(Edge) error(inout(Edge) existingEdge, inout(Edge) newEdge)
916         {
917             throw new EdgeExistsException();
918         }
919 
920         ///
921         unittest
922         {
923             auto g1 = Graph!int([1, 2]);
924             alias CS = g1.ConflictStrategy;
925 
926             g1 ~= g1.edge(1, 2);
927 
928             assertThrown!EdgeExistsException(g1.add!(CS.error)(g1.edge(1, 2)));
929         }
930 
931         /// Replace the existingEdge by newEdge.
932         static inout(Edge) replace(inout(Edge) existingEdge, inout(Edge) newEdge)
933         {
934             return newEdge;
935         }
936 
937         ///
938         unittest
939         {
940             auto g1 = Graph!(int, int)([1, 2]);
941             alias CS = g1.ConflictStrategy;
942 
943             g1 ~= g1.edge(1, 2, 1);
944 
945             auto addedEdge = g1.add!(CS.replace)(g1.edge(1, 2, 2));
946 
947             assert(addedEdge.weight == 2);
948         }
949 
950         /// Keep existingEdge – discard newEdge.
951         static inout(Edge) keep(inout(Edge) existingEdge, inout(Edge) newEdge)
952         {
953             return existingEdge;
954         }
955 
956         ///
957         unittest
958         {
959             auto g1 = Graph!(int, int)([1, 2]);
960             alias CS = g1.ConflictStrategy;
961 
962             g1 ~= g1.edge(1, 2, 1);
963 
964             auto addedEdge = g1.add!(CS.keep)(g1.edge(1, 2, 2));
965 
966             assert(addedEdge.weight == 1);
967         }
968     }
969 
970     /// Forcibly add an edge to this graph.
971     protected Edge forceAdd(Edge edge)
972     {
973         _edges ~= edge;
974         _edges.data.sort;
975 
976         return edge;
977     }
978 
979     /// Replace an edge in this graph.
980     protected Edge replaceEdge(in size_t edgeIdx, Edge newEdge)
981     {
982         auto shouldSort = _edges.data[edgeIdx] != newEdge;
983 
984         _edges.data[edgeIdx] = newEdge;
985 
986         if (shouldSort)
987         {
988             _edges.data.sort;
989         }
990 
991         return newEdge;
992     }
993 
994     /// Check if edge/node exists in this graph. Ignores the weight if weighted.
995     bool opBinaryRight(string op)(in Node node) const pure nothrow if (op == "in")
996     {
997         auto sortedNodes = assumeSorted(nodes);
998 
999         return sortedNodes.contains(node);
1000     }
1001 
1002     /// ditto
1003     bool has(in Node node) const pure nothrow
1004     {
1005         return node in this;
1006     }
1007 
1008     /// Check if edge exists in this graph. Only the `start` and `end` node
1009     /// will be compared.
1010     bool opBinaryRight(string op)(in Edge edge) const pure nothrow if (op == "in")
1011     {
1012         auto sortedEdges = assumeSorted!orderByNodes(edges);
1013 
1014         return sortedEdges.contains(edge);
1015     }
1016 
1017     /// ditto
1018     bool has(in Edge edge) const pure nothrow
1019     {
1020         return edge in this;
1021     }
1022 
1023     /// Get the designated edge from this graph. Only the `start` and `end`
1024     /// node will be compared.
1025     auto ref get(in Edge edge)
1026     {
1027         auto sortedEdges = assumeSorted!orderByNodes(edges);
1028         auto existingEdges = sortedEdges.equalRange(edge);
1029 
1030         if (existingEdges.empty)
1031         {
1032             throw new MissingEdgeException();
1033         }
1034         else
1035         {
1036             return existingEdges.front;
1037         }
1038     }
1039 
1040     ///
1041     unittest
1042     {
1043         auto g1 = Graph!(int, int)([1, 2]);
1044 
1045         auto e1 = g1.edge(1, 2, 1);
1046 
1047         g1 ~= e1;
1048 
1049         assert(g1.get(g1.edge(1, 2)) == e1);
1050         assertThrown!MissingEdgeException(g1.get(g1.edge(1, 1)));
1051     }
1052 
1053     /// Returns the index of node `n` in the list of nodes.
1054     size_t indexOf(in Node n) const
1055     {
1056         auto sortedNodes = assumeSorted(nodes);
1057         auto tristectedNodes = sortedNodes.trisect(n);
1058 
1059         if (tristectedNodes[1].empty)
1060         {
1061             throw new MissingNodeException();
1062         }
1063 
1064         return tristectedNodes[0].length;
1065     }
1066 
1067     ///
1068     unittest
1069     {
1070         auto g1 = Graph!(int, int)([1, 2]);
1071 
1072         assert(g1.indexOf(1) == 0);
1073         assert(g1.indexOf(2) == 1);
1074         assertThrown!MissingNodeException(g1.indexOf(3));
1075     }
1076 
1077     /// Returns the index of node `n` in the list of nodes.
1078     size_t indexOf(in Edge edge) const
1079     {
1080         auto sortedEdges = assumeSorted!orderByNodes(edges);
1081         auto trisectedEdges = sortedEdges.trisect(edge);
1082 
1083         if (trisectedEdges[1].empty)
1084         {
1085             throw new MissingEdgeException();
1086         }
1087 
1088         return trisectedEdges[0].length;
1089     }
1090 
1091     ///
1092     unittest
1093     {
1094         auto g1 = Graph!(int, int)([1, 2]);
1095 
1096         auto e1 = g1.edge(1, 2, 1);
1097 
1098         g1 ~= e1;
1099 
1100         assert(g1.indexOf(g1.edge(1, 2)) == 0);
1101         assertThrown!MissingEdgeException(g1.indexOf(g1.edge(1, 1)));
1102     }
1103 
1104     static if (isDirected)
1105     {
1106         /// Returns a range of in/outgoing edges of node `n`.
1107         auto inEdges(Node n) nothrow pure
1108         {
1109             return _edges.data[].filter!(e => e.end == n);
1110         }
1111 
1112         /// ditto
1113         auto inEdges(Node n) const nothrow pure
1114         {
1115             return edges[].filter!(e => e.end == n);
1116         }
1117 
1118         /// ditto
1119         auto outEdges(Node n) nothrow pure
1120         {
1121             return _edges.data[].filter!(e => e.start == n);
1122         }
1123 
1124         /// ditto
1125         auto outEdges(Node n) const nothrow pure
1126         {
1127             return edges[].filter!(e => e.start == n);
1128         }
1129 
1130         ///
1131         unittest
1132         {
1133             import std.algorithm : equal;
1134 
1135             auto g1 = Graph!(int, void, Yes.isDirected)([1, 2, 3]);
1136 
1137             g1 ~= g1.edge(1, 1);
1138             g1 ~= g1.edge(1, 2);
1139             g1 ~= g1.edge(2, 2);
1140             g1 ~= g1.edge(2, 3);
1141 
1142             assert(g1.inEdges(1).equal([
1143                 g1.edge(1, 1),
1144             ]));
1145             assert(g1.outEdges(1).equal([
1146                 g1.edge(1, 1),
1147                 g1.edge(1, 2),
1148             ]));
1149             assert(g1.inEdges(2).equal([
1150                 g1.edge(1, 2),
1151                 g1.edge(2, 2),
1152             ]));
1153             assert(g1.outEdges(2).equal([
1154                 g1.edge(2, 2),
1155                 g1.edge(2, 3),
1156             ]));
1157             assert(g1.inEdges(3).equal([
1158                 g1.edge(2, 3),
1159             ]));
1160             assert(g1.outEdges(3).empty);
1161         }
1162 
1163         /// Get the in/out degree of node `n`.
1164         size_t inDegree(Node n) const nothrow pure
1165         {
1166             return inEdges(n).walkLength;
1167         }
1168 
1169         /// ditto
1170         size_t outDegree(Node n) const nothrow pure
1171         {
1172             return outEdges(n).walkLength;
1173         }
1174 
1175         ///
1176         unittest
1177         {
1178             auto g1 = Graph!(int, void, Yes.isDirected)([1, 2, 3]);
1179 
1180             g1 ~= g1.edge(1, 1);
1181             g1 ~= g1.edge(1, 2);
1182             g1 ~= g1.edge(2, 2);
1183             g1 ~= g1.edge(2, 3);
1184 
1185             assert(g1.inDegree(1) == 1);
1186             assert(g1.outDegree(1) == 2);
1187             assert(g1.inDegree(2) == 2);
1188             assert(g1.outDegree(2) == 2);
1189             assert(g1.inDegree(3) == 1);
1190             assert(g1.outDegree(3) == 0);
1191         }
1192     }
1193     else
1194     {
1195         /// Returns a range of all edges incident to node `n`.
1196         auto incidentEdges(Node n) nothrow pure
1197         {
1198             return _edges.data[].filter!(e => e.start == n || e.end == n);
1199         }
1200 
1201         /// ditto
1202         auto incidentEdges(Node n) const nothrow pure
1203         {
1204             return edges[].filter!(e => e.start == n || e.end == n);
1205         }
1206 
1207         /// ditto
1208         alias inEdges = incidentEdges;
1209 
1210         /// ditto
1211         alias outEdges = incidentEdges;
1212 
1213         ///
1214         unittest
1215         {
1216             import std.algorithm : equal;
1217 
1218             auto g1 = Graph!int([1, 2, 3]);
1219 
1220             g1 ~= g1.edge(1, 1);
1221             g1 ~= g1.edge(1, 2);
1222             g1 ~= g1.edge(2, 2);
1223             g1 ~= g1.edge(2, 3);
1224 
1225             assert(g1.incidentEdges(1).equal([
1226                 g1.edge(1, 1),
1227                 g1.edge(1, 2),
1228             ]));
1229             assert(g1.incidentEdges(2).equal([
1230                 g1.edge(1, 2),
1231                 g1.edge(2, 2),
1232                 g1.edge(2, 3),
1233             ]));
1234             assert(g1.incidentEdges(3).equal([
1235                 g1.edge(2, 3),
1236             ]));
1237         }
1238 
1239         IncidentEdgesCache allIncidentEdges()
1240         {
1241             return IncidentEdgesCache(this);
1242         }
1243 
1244         static struct IncidentEdgesCache
1245         {
1246             alias G = Graph!(Node, Weight, isDirected, EdgePayload);
1247             G graph;
1248             Edge[][] incidentEdges;
1249 
1250             this(G graph)
1251             {
1252                 this.graph = graph;
1253                 collectAllIncidentEdges();
1254             }
1255 
1256             private void collectAllIncidentEdges()
1257             {
1258                 preallocateMemory();
1259 
1260                 size_t startIdx;
1261                 size_t endIdx;
1262                 foreach (edge; graph._edges.data)
1263                 {
1264                     if (graph._nodes[startIdx] < edge.start)
1265                         endIdx = startIdx;
1266                     while (graph._nodes[startIdx] < edge.start)
1267                         ++startIdx;
1268                     if (endIdx < startIdx)
1269                         endIdx = startIdx;
1270                     while (graph._nodes[endIdx] < edge.end)
1271                         ++endIdx;
1272 
1273                     incidentEdges[startIdx] ~= edge;
1274                     // Avoid double-counting of loops
1275                     if (startIdx != endIdx)
1276                         incidentEdges[endIdx] ~= edge;
1277                 }
1278             }
1279 
1280             void preallocateMemory()
1281             {
1282                 auto degreesCache = graph.allDegrees();
1283                 Edge[] buffer;
1284                 buffer.length = degreesCache.degrees.sum;
1285                 incidentEdges.length = degreesCache.degrees.length;
1286 
1287                 size_t sliceBegin;
1288                 size_t startIdx;
1289                 foreach (degree; degreesCache)
1290                 {
1291                     incidentEdges[startIdx] = buffer[sliceBegin .. sliceBegin + degree];
1292                     incidentEdges[startIdx].length = 0;
1293 
1294                     sliceBegin += degree;
1295                     ++startIdx;
1296                 }
1297             }
1298 
1299             ref inout(Edge[]) opIndex(in Node node) inout
1300             {
1301                 return incidentEdges[graph.indexOf(node)];
1302             }
1303 
1304             ref inout(Edge[]) opIndex(in size_t nodeIdx) inout
1305             {
1306                 return incidentEdges[nodeIdx];
1307             }
1308 
1309             int opApply(scope int delegate(Edge[]) yield)
1310             {
1311                 int result = 0;
1312 
1313                 foreach (currentIncidentEdges; incidentEdges)
1314                 {
1315                     result = yield(currentIncidentEdges);
1316                     if (result)
1317                         break;
1318                 }
1319 
1320                 return result;
1321             }
1322 
1323             int opApply(scope int delegate(Node, Edge[]) yield)
1324             {
1325                 int result = 0;
1326 
1327                 foreach (i, currentIncidentEdges; incidentEdges)
1328                 {
1329                     result = yield(graph._nodes[i], currentIncidentEdges);
1330                     if (result)
1331                         break;
1332                 }
1333 
1334                 return result;
1335             }
1336         }
1337 
1338         /// Get the `adjacencyList` of this graph where nodes are represented
1339         /// by their index in the nodes list.
1340         size_t[][] adjacencyList() const
1341         {
1342             size_t[][] _adjacencyList;
1343             _adjacencyList.length = nodes.length;
1344             size_t[] targetsBuffer;
1345             targetsBuffer.length = 2 * edges.length;
1346 
1347             foreach (i, node; _nodes)
1348             {
1349                 auto bufferRest = edges
1350                     .filter!(e => e.start == node || e.end == node)
1351                     .map!(edge => indexOf(edge.target(node)))
1352                     .copy(targetsBuffer);
1353                 _adjacencyList[i] = targetsBuffer[0 .. $ - bufferRest.length];
1354                 _adjacencyList[i].sort;
1355                 targetsBuffer = bufferRest;
1356             }
1357 
1358             return _adjacencyList;
1359         }
1360 
1361         ///
1362         unittest
1363         {
1364             auto g1 = Graph!int([1, 2, 3, 4]);
1365 
1366             g1 ~= g1.edge(1, 1);
1367             g1 ~= g1.edge(1, 2);
1368             g1 ~= g1.edge(2, 2);
1369             g1 ~= g1.edge(2, 3);
1370             g1 ~= g1.edge(2, 4);
1371             g1 ~= g1.edge(3, 4);
1372 
1373             assert(g1.adjacencyList() == [
1374                 [0, 1],
1375                 [0, 1, 2, 3],
1376                 [1, 3],
1377                 [1, 2],
1378             ]);
1379         }
1380 
1381         /// Get the degree of node `n`.
1382         size_t degree(Node n) const nothrow pure
1383         {
1384             return incidentEdges(n).walkLength;
1385         }
1386 
1387         /// ditto
1388         alias inDegree = degree;
1389 
1390         /// ditto
1391         alias outDegree = degree;
1392 
1393         DegreesCache allDegrees() const
1394         {
1395             return DegreesCache(this);
1396         }
1397 
1398         static struct DegreesCache
1399         {
1400             alias G = Graph!(Node, Weight, isDirected, EdgePayload);
1401             const(G) graph;
1402             size_t[] degrees;
1403 
1404             this(in G graph)
1405             {
1406                 this.graph = graph;
1407                 collectAllDegrees();
1408             }
1409 
1410             private void collectAllDegrees()
1411             {
1412                 degrees.length = graph._nodes.length;
1413 
1414                 size_t startIdx;
1415                 size_t endIdx;
1416                 foreach (edge; graph._edges.data)
1417                 {
1418                     if (graph._nodes[startIdx] < edge.start)
1419                         endIdx = startIdx;
1420                     while (graph._nodes[startIdx] < edge.start)
1421                         ++startIdx;
1422                     if (endIdx < startIdx)
1423                         endIdx = startIdx;
1424                     while (graph._nodes[endIdx] < edge.end)
1425                         ++endIdx;
1426 
1427                     ++degrees[startIdx];
1428                     // Avoid double-counting of loops
1429                     if (startIdx != endIdx)
1430                         ++degrees[endIdx];
1431                 }
1432             }
1433 
1434             size_t opIndex(in Node node) const
1435             {
1436                 return degrees[graph.indexOf(node)];
1437             }
1438 
1439             size_t opIndex(in size_t nodeIdx) const
1440             {
1441                 return degrees[nodeIdx];
1442             }
1443 
1444             int opApply(scope int delegate(size_t) yield) const
1445             {
1446                 int result = 0;
1447 
1448                 foreach (degree; degrees)
1449                 {
1450                     result = yield(degree);
1451                     if (result)
1452                         break;
1453                 }
1454 
1455                 return result;
1456             }
1457 
1458             int opApply(scope int delegate(Node, size_t) yield) const
1459             {
1460                 int result = 0;
1461 
1462                 foreach (i, degree; degrees)
1463                 {
1464                     result = yield(graph._nodes[i], degree);
1465                     if (result)
1466                         break;
1467                 }
1468 
1469                 return result;
1470             }
1471         }
1472     }
1473 }
1474 
1475 ///
1476 unittest
1477 {
1478     //   +-+  +-+
1479     //   \ /  \ /
1480     //   (1)--(2)
1481     auto g1 = Graph!int([1, 2]);
1482 
1483     g1 ~= g1.edge(1, 1);
1484     g1 ~= g1.edge(1, 2);
1485     g1.add(g1.edge(2, 2));
1486 
1487     assert(g1.edge(1, 1) in g1);
1488     assert(g1.edge(1, 2) in g1);
1489     assert(g1.edge(2, 1) in g1);
1490     assert(g1.has(g1.edge(2, 2)));
1491     assert(g1.allDegrees().degrees == [2, 2]);
1492     assert(g1.allIncidentEdges().incidentEdges == [
1493         [g1.edge(1, 1), g1.edge(1, 2)],
1494         [g1.edge(1, 2), g1.edge(2, 2)],
1495     ]);
1496 
1497     //   0.5     0.5
1498     //   +-+     +-+
1499     //   \ /     \ /
1500     //   (1)-----(2)
1501     //       1.0
1502     auto g2 = Graph!(int, double)([1, 2]);
1503 
1504     g2 ~= g2.edge(1, 1, 0.5);
1505     g2 ~= g2.edge(1, 2, 1.0);
1506     g2.add(g2.edge(2, 2, 0.5));
1507 
1508     assert(g2.edge(1, 1) in g2);
1509     assert(g2.edge(1, 2) in g2);
1510     assert(g2.edge(2, 1) in g2);
1511     assert(g2.has(g2.edge(2, 2)));
1512     assert(g2.allDegrees().degrees == [2, 2]);
1513     assert(g2.allIncidentEdges().incidentEdges == [
1514         [g2.edge(1, 1, 0.5), g2.edge(1, 2, 1.0)],
1515         [g2.edge(1, 2, 1.0), g2.edge(2, 2, 0.5)],
1516     ]);
1517 
1518     //   0.5     0.5
1519     //   +-+     +-+
1520     //   \ v     v /
1521     //   (1)---->(2)
1522     //       1.0
1523     auto g3 = Graph!(int, double, Yes.isDirected)([1, 2]);
1524 
1525     g3 ~= g3.edge(1, 1, 0.5);
1526     g3 ~= g3.edge(1, 2, 1.0);
1527     g3.add(g3.edge(2, 2, 0.5));
1528 
1529     assert(g3.edge(1, 1) in g3);
1530     assert(g3.edge(1, 2) in g3);
1531     assert(!(g3.edge(2, 1) in g3));
1532     assert(g3.has(g3.edge(2, 2)));
1533 
1534     //   +-+   +-+
1535     //   \ v   v /
1536     //   (1)-->(2)
1537     auto g4 = Graph!(int, void, Yes.isDirected)([1, 2]);
1538 
1539     g4 ~= g4.edge(1, 1);
1540     g4 ~= g4.edge(1, 2);
1541     g4.add(g4.edge(2, 2));
1542 
1543     assert(g4.edge(1, 1) in g4);
1544     assert(g4.edge(1, 2) in g4);
1545     assert(!(g4.edge(2, 1) in g4));
1546     assert(g4.has(g4.edge(2, 2)));
1547 
1548     //   +-+  +-+
1549     //   \ /  \ /
1550     //   (1)--(2)
1551     //
1552     // payload(1, 1) = [1];
1553     // payload(1, 2) = [2];
1554     // payload(2, 2) = [3];
1555     auto g5 = Graph!(int, void, No.isDirected, int[])([1, 2]);
1556 
1557     g5 ~= g5.edge(1, 1, [1]);
1558     g5 ~= g5.edge(1, 2, [2]);
1559     g5.add(g5.edge(2, 2, [3]));
1560 
1561     assert(g5.edge(1, 1) in g5);
1562     assert(g5.get(g5.edge(1, 1)).payload == [1]);
1563     assert(g5.edge(1, 2) in g5);
1564     assert(g5.get(g5.edge(1, 2)).payload == [2]);
1565     assert(g5.edge(2, 1) in g5);
1566     assert(g5.get(g5.edge(2, 1)).payload == [2]);
1567     assert(g5.has(g5.edge(2, 2)));
1568     assert(g5.get(g5.edge(2, 2)).payload == [3]);
1569     assert(g5.allDegrees().degrees == [2, 2]);
1570     assert(g5.allIncidentEdges().incidentEdges == [
1571         [g5.edge(1, 1), g5.edge(1, 2)],
1572         [g5.edge(1, 2), g5.edge(2, 2)],
1573     ]);
1574 }
1575 
1576 ///
1577 unittest
1578 {
1579     //     -1     1         1
1580     // (1)----(2)---(3) (4)---(5) (6)
1581     size_t[] contigs = [1, 2, 3, 4, 5, 6];
1582     auto contigGraph = Graph!(size_t, int)([1, 2, 3, 4, 5, 6]);
1583 
1584     contigGraph.add(contigGraph.edge(1, 2, -1));
1585     contigGraph.add(contigGraph.edge(2, 3, 1));
1586     contigGraph.add(contigGraph.edge(4, 5, 1));
1587 
1588     foreach (contig; contigs)
1589     {
1590         assert(contigGraph.degree(contig) <= 2);
1591     }
1592     assert(contigGraph.allDegrees().degrees == [1, 2, 1, 1, 1, 0]);
1593     assert(contigGraph.allIncidentEdges().incidentEdges == [
1594         [contigGraph.edge(1, 2, -1)],
1595         [contigGraph.edge(1, 2, -1), contigGraph.edge(2, 3, 1)],
1596         [contigGraph.edge(2, 3, 1)],
1597         [contigGraph.edge(4, 5, 1)],
1598         [contigGraph.edge(4, 5, 1)],
1599         [],
1600     ]);
1601 }
1602 
1603 /// Add a set of edges to this graph and merge mutli-edges using `merge`.
1604 void bulkAdd(alias merge, G, R)(ref G graph, R edges)
1605         if (is(G : Graph!Params, Params...) && isForwardRange!R && is(ElementType!R == G.Edge))
1606 {
1607     alias Edge = G.Edge;
1608     alias ReturnTypeMerge = typeof(merge(new Edge[0]));
1609     static assert(is(ReturnTypeMerge == Edge), "expected `Edge merge(Edge[] multiEdge)`");
1610 
1611     graph.bulkAddForce(edges);
1612 
1613     auto bufferRest = graph
1614         ._edges
1615         .data
1616         .sliceBy!(G.groupByNodes)
1617         .map!(unaryFun!merge)
1618         .copy(graph._edges.data);
1619     graph._edges.shrinkTo(graph._edges.data.length - bufferRest.length);
1620 }
1621 
1622 ///
1623 unittest
1624 {
1625     auto g1 = Graph!(int, int)([1, 2]);
1626 
1627     static g1.Edge sumWeights(g1.Edge[] multiEdge)
1628     {
1629         auto sumOfWeights = multiEdge.map!"a.weight".sum;
1630         auto mergedEdge = multiEdge[0];
1631         mergedEdge.weight = sumOfWeights;
1632 
1633         return mergedEdge;
1634     }
1635 
1636     auto edges = [
1637         g1.edge(1, 2, 1),
1638         g1.edge(1, 2, 1),
1639         g1.edge(1, 2, 1),
1640         g1.edge(2, 3, 2),
1641         g1.edge(2, 3, 2),
1642         g1.edge(3, 4, 3),
1643     ];
1644     g1.bulkAdd!sumWeights(edges);
1645     assert(g1.edges == [
1646         g1.edge(1, 2, 3),
1647         g1.edge(2, 3, 4),
1648         g1.edge(3, 4, 3),
1649     ]);
1650 }
1651 
1652 /// Add an edge to this graph and handle existing edges with `handleConflict`.
1653 /// The handler must have this signature `Edge handleConflict(Edge, Edge)`.
1654 G.Edge add(alias handleConflict = 1337, G)(ref G graph, G.Edge edge)
1655         if (is(G : Graph!Params, Params...))
1656 {
1657     static if (isCallable!handleConflict)
1658         alias handleConflict_ = binaryFun!handleConflict;
1659     else
1660         alias handleConflict_ = binaryFun!(G.ConflictStrategy.error);
1661 
1662     if (!graph.has(edge.start) || !graph.has(edge.end))
1663     {
1664         throw new MissingNodeException();
1665     }
1666 
1667     auto sortedEdges = assumeSorted!(G.orderByNodes)(graph._edges.data);
1668     auto trisectedEdges = sortedEdges.trisect(edge);
1669     auto existingEdges = trisectedEdges[1];
1670     auto existingEdgeIdx = trisectedEdges[0].length;
1671 
1672     if (existingEdges.empty)
1673     {
1674         return graph.forceAdd(edge);
1675     }
1676     else
1677     {
1678         auto newEdge = handleConflict_(existingEdges.front, edge);
1679 
1680         return graph.replaceEdge(existingEdgeIdx, newEdge);
1681     }
1682 }
1683 
1684 ///
1685 unittest
1686 {
1687     auto g1 = Graph!(int, int)([1, 2]);
1688 
1689     auto e1 = g1.edge(1, 2, 1);
1690     auto e2 = g1.edge(1, 2, 2);
1691 
1692     g1 ~= e1;
1693 
1694     assertThrown!EdgeExistsException(g1.add(e2));
1695 
1696     with (g1.ConflictStrategy)
1697     {
1698         g1.add!replace(e2);
1699 
1700         assert(g1.get(g1.edge(1, 2)) == e2);
1701 
1702         g1.add!keep(e1);
1703 
1704         assert(g1.get(g1.edge(1, 2)) == e2);
1705 
1706         g1.add!sumWeights(e2);
1707 
1708         assert(g1.get(g1.edge(1, 2)).weight == 2 * e2.weight);
1709     }
1710 }
1711 
1712 void filterEdges(alias pred, G)(ref G graph) if (is(G : Graph!Params, Params...))
1713 {
1714     auto bufferRest = graph
1715         ._edges
1716         .data
1717         .filter!pred
1718         .copy(graph._edges.data);
1719     graph._edges.shrinkTo(graph._edges.data.length - bufferRest.length);
1720 }
1721 
1722 void mapEdges(alias fun, G)(ref G graph) if (is(G : Graph!Params, Params...))
1723 {
1724     foreach (ref edge; graph._edges.data)
1725         edge = unaryFun!fun(edge);
1726 
1727     graph._edges.data.sort();
1728 }
1729 
1730 
1731 struct UndirectedGraph(Node, Weight = void)
1732 {
1733     static enum isWeighted = !is(Weight == void);
1734 
1735     static if (isWeighted)
1736         alias adjacency_t = Weight;
1737     else
1738         alias adjacency_t = bool;
1739 
1740 
1741     static struct Edge
1742     {
1743         protected Node _start;
1744         protected Node _end;
1745 
1746         static if (isWeighted)
1747             Weight weight;
1748 
1749         /// Construct an edge.
1750         this(Node start, Node end) pure nothrow @safe
1751         {
1752             this._start = start;
1753             this._end = end;
1754 
1755             if (end < start)
1756                 swap(this._start, this._end);
1757         }
1758 
1759         static if (isWeighted)
1760         {
1761             /// ditto
1762             this(Node start, Node end, Weight weight) pure nothrow @safe
1763             {
1764                 this(start, end);
1765                 this.weight = weight;
1766             }
1767         }
1768 
1769 
1770         private @property adjacency_t adjacencyValue() pure nothrow @safe @nogc
1771         {
1772             static if (isWeighted)
1773                 return weight;
1774             else
1775                 return true;
1776         }
1777 
1778 
1779         /// Get the start of this edge, i.e. the smaller of both incident
1780         /// nodes.
1781         @property Node start() const pure nothrow @safe
1782         {
1783             return _start;
1784         }
1785 
1786         /// Get the end of this edge, i.e. the larger of both incident nodes.
1787         @property Node end() const pure nothrow @safe
1788         {
1789             return _end;
1790         }
1791 
1792         /**
1793             Returns the other node of this edge.
1794 
1795             Throws: MissingNodeException if this edge does not coincide with `from`.
1796         */
1797         Node target(Node from) const
1798         {
1799             if (from == start)
1800                 return end;
1801             else if (from == end)
1802                 return start;
1803             else
1804                 throw new MissingNodeException();
1805         }
1806 
1807         /// ditto
1808         alias source = target;
1809 
1810         /// Two edges are equal iff their incident nodes are the same.
1811         bool opEquals(in Edge other) const pure nothrow
1812         {
1813             return this.start == other.start && this.end == other.end;
1814         }
1815 
1816         /// Orders edge lexicographically by `start`, `end`.
1817         int opCmp(in Edge other) const pure nothrow
1818         {
1819             return cmpLexicographically!(
1820                 typeof(this),
1821                 "a.start",
1822                 "a.end",
1823             )(this, other);
1824         }
1825 
1826         /**
1827             Returns the node that is common to this and other.
1828 
1829             Throws: MissingNodeException if there this and other do not share
1830                 a common node.
1831         */
1832         Node getCommonNode(in Edge other) const
1833         {
1834             import std.algorithm : among;
1835 
1836             if (this.end.among(other.start, other.end))
1837                 return this.end;
1838             else if (this.start.among(other.start, other.end))
1839                 return this.start;
1840             else
1841                 throw new MissingNodeException();
1842         }
1843     }
1844 
1845     /// Construct an edge for this graph.
1846     static Edge edge(T...)(T args)
1847     {
1848         return Edge(args);
1849     }
1850 
1851 
1852     protected bool[Node] _nodes;
1853     protected adjacency_t[Node][Node] _adjacency;
1854 
1855 
1856     /// Returns a list of the nodes in this graph.
1857     @property Node[] nodes() const nothrow pure
1858     {
1859         return _nodes.keys;
1860     }
1861 
1862 
1863     /// Returns a list of all edges in this graph.
1864     @property auto edges() nothrow pure
1865     {
1866         static if (isWeighted)
1867             alias buildEdge = (start, end, weight) => Edge(start, end, weight);
1868         else
1869             alias buildEdge = (start, end, weight) => Edge(start, end);
1870 
1871         return _adjacency
1872             .keys()
1873             .map!(start => _adjacency[start]
1874                 .keys()
1875                 .map!(end => buildEdge(start, end, _adjacency[start][end]))
1876             )
1877             .joiner;
1878     }
1879 
1880     /// ditto
1881     @property auto edges() const nothrow pure
1882     {
1883         static if (isWeighted)
1884             alias buildEdge = (start, end, weight) => cast(const) Edge(start, end, cast() weight);
1885         else
1886             alias buildEdge = (start, end, weight) => cast(const) Edge(start, end);
1887 
1888         return _adjacency
1889             .keys()
1890             .map!(start => _adjacency[start]
1891                 .keys()
1892                 .map!(end => buildEdge(start, end, _adjacency[start][end]))
1893             )
1894             .joiner;
1895     }
1896 
1897 
1898     /**
1899         Construct a graph from a set of nodes (and edges).
1900 
1901         Throws: NodeExistsException if a node already exists when trying
1902                 to insert it.
1903         Throws: EdgeExistsException if an edge already exists when trying
1904                 to insert it.
1905     */
1906     this(Node[] nodes)
1907     {
1908         foreach (node; nodes)
1909             this.addNode(node);
1910     }
1911 
1912     /// ditto
1913     this(Node[] nodes, Edge[] edges)
1914     {
1915         this(nodes);
1916 
1917         foreach (edge; edges)
1918             this.addEdge(edge);
1919     }
1920 
1921 
1922     /// Inserts node into the node list if not yet present.
1923     void requireNode(Node node) nothrow @safe
1924     {
1925         requireNode(node);
1926     }
1927 
1928     /// ditto
1929     void requireNode(ref Node node) nothrow @safe
1930     {
1931         this._nodes[node] = true;
1932     }
1933 
1934 
1935     private static T _throw(T, E)(ref T _)
1936     {
1937         throw new E();
1938     }
1939 
1940 
1941     /**
1942         Inserts node into the node list throwing an exception if already
1943         present.
1944 
1945         Throws: NodeExistsException if node already present.
1946     */
1947     void addNode(Node node) @safe
1948     {
1949         addNode(node);
1950     }
1951 
1952     /// ditto
1953     void addNode(ref Node node) @safe
1954     {
1955         this._nodes.update(
1956             node,
1957             { return true; },
1958             &_throw!(bool, NodeExistsException),
1959         );
1960     }
1961 
1962 
1963     /**
1964         Inserts edge into the graph thrwoing an exception if already present.
1965 
1966         Throws: EdgeExistsException if edge already present.
1967     */
1968     void addEdge(Edge edge) @safe
1969     {
1970         addEdge(edge._start, edge._end, edge.adjacencyValue);
1971         addEdge(edge._end, edge._start, edge.adjacencyValue);
1972         requireNode(edge._start);
1973         requireNode(edge._end);
1974     }
1975 
1976 
1977     private void addEdge(ref Node start, ref Node end, adjacency_t adjacencyValue) @safe
1978     {
1979         import std.exception : enforce;
1980 
1981         _adjacency.update(start,
1982             {
1983                 adjacency_t[Node] secondLevelAdjacency;
1984 
1985                 secondLevelAdjacency[end] = adjacencyValue;
1986 
1987                 return secondLevelAdjacency;
1988             },
1989             (ref adjacency_t[Node] secondLevelAdjacency) {
1990                 secondLevelAdjacency.update(
1991                     end,
1992                     delegate () { return adjacencyValue; },
1993                     &_throw!(adjacency_t, EdgeExistsException),
1994                 );
1995 
1996                 return secondLevelAdjacency;
1997             },
1998         );
1999 
2000         requireNode(start);
2001         requireNode(end);
2002     }
2003 }
2004 
2005 
2006 class EmptySetException : Exception
2007 {
2008     this(string msg)
2009     {
2010         super(msg);
2011     }
2012 }
2013 
2014 struct NaturalNumberSet
2015 {
2016     private static enum partSize = 8 * size_t.sizeof;
2017     private static enum size_t firstBit = 1;
2018     private static enum size_t lastBit = firstBit << (partSize - 1);
2019     private static enum size_t emptyPart = 0;
2020     private static enum size_t fullPart = ~emptyPart;
2021 
2022     private size_t[] parts;
2023     private size_t nMax;
2024 
2025     this(size_t initialNumElements, Flag!"addAll" addAll = No.addAll)
2026     {
2027         reserveFor(initialNumElements);
2028 
2029         if (addAll)
2030         {
2031             foreach (i; 0 .. initialNumElements / partSize)
2032                 parts[i] = fullPart;
2033             foreach (i; initialNumElements / partSize .. initialNumElements)
2034                 add(i);
2035         }
2036     }
2037 
2038     static NaturalNumberSet create(size_t[] initialElements...)
2039     {
2040         if (initialElements.length == 0)
2041             return NaturalNumberSet();
2042 
2043         auto set = NaturalNumberSet(initialElements.maxElement);
2044 
2045         foreach (i; initialElements)
2046             set.add(i);
2047 
2048         return set;
2049     }
2050 
2051     this(this)
2052     {
2053         parts = parts.dup;
2054     }
2055 
2056     private this(size_t[] parts)
2057     {
2058         this.parts = parts;
2059     }
2060 
2061     private bool inBounds(in size_t n) const pure nothrow
2062     {
2063         return n < nMax;
2064     }
2065 
2066     void reserveFor(in size_t n)
2067     {
2068         if (parts.length == 0)
2069         {
2070             parts.length = max(1, ceildiv(n, partSize));
2071             nMax = parts.length * partSize;
2072         }
2073 
2074         while (!inBounds(n))
2075         {
2076             parts.length *= 2;
2077             nMax = parts.length * partSize;
2078         }
2079     }
2080 
2081     @property size_t capacity() pure const nothrow
2082     {
2083         return nMax;
2084     }
2085 
2086     private size_t partIdx(in size_t n) const pure nothrow
2087     {
2088         return n / partSize;
2089     }
2090 
2091     private size_t idxInPart(in size_t n) const pure nothrow
2092     {
2093         return n % partSize;
2094     }
2095 
2096     private size_t itemMask(in size_t n) const pure nothrow
2097     {
2098         return firstBit << idxInPart(n);
2099     }
2100 
2101     static size_t inverse(in size_t n) pure nothrow
2102     {
2103         return n ^ fullPart;
2104     }
2105 
2106     void add(in size_t n)
2107     {
2108         reserveFor(n);
2109 
2110         parts[partIdx(n)] |= itemMask(n);
2111     }
2112 
2113     void remove(in size_t n)
2114     {
2115         if (!inBounds(n))
2116         {
2117             return;
2118         }
2119 
2120         parts[partIdx(n)] &= inverse(itemMask(n));
2121     }
2122 
2123     bool has(in size_t n) const pure nothrow
2124     {
2125         if (!inBounds(n))
2126         {
2127             return false;
2128         }
2129 
2130         return (parts[partIdx(n)] & itemMask(n)) != emptyPart;
2131     }
2132 
2133     bool opBinaryRight(string op)(in size_t n) const pure nothrow if (op == "in")
2134     {
2135         return this.has(n);
2136     }
2137 
2138     bool empty() const pure nothrow
2139     {
2140         return parts.all!(part => part == emptyPart);
2141     }
2142 
2143     void clear() pure nothrow
2144     {
2145         foreach (ref part; parts)
2146             part = emptyPart;
2147     }
2148 
2149     bool opBinary(string op)(in NaturalNumberSet other) const pure nothrow if (op == "==")
2150     {
2151         auto numCommonParts = min(this.parts.length, other.parts.length);
2152 
2153         foreach (i; 0 .. numCommonParts)
2154         {
2155             if (this.parts[i] != other.parts[i])
2156                 return false;
2157         }
2158 
2159         static bool hasEmptyTail(ref in NaturalNumberSet set, in size_t tailStart)
2160         {
2161             foreach (i; tailStart .. set.parts.length)
2162                 if (set.parts[i] != emptyPart)
2163                     return false;
2164 
2165             return true;
2166         }
2167 
2168         if (this.parts.length > numCommonParts)
2169             return hasEmptyTail(this, numCommonParts);
2170         if (other.parts.length > numCommonParts)
2171             return hasEmptyTail(other, numCommonParts);
2172 
2173         return true;
2174     }
2175 
2176     bool opBinary(string op)(in NaturalNumberSet other) const pure nothrow if (op == "in")
2177     {
2178         auto numCommonParts = min(this.parts.length, other.parts.length);
2179 
2180         foreach (i; 0 .. numCommonParts)
2181             if ((this.parts[i] & other.parts[i]) != this.parts[i])
2182                 return false;
2183 
2184         static bool hasEmptyTail(ref in NaturalNumberSet set, in size_t tailStart)
2185         {
2186             foreach (i; tailStart .. set.parts.length)
2187                 if (set.parts[i] != emptyPart)
2188                     return false;
2189 
2190             return true;
2191         }
2192 
2193         if (this.parts.length > numCommonParts)
2194             return hasEmptyTail(this, numCommonParts);
2195 
2196         return true;
2197     }
2198 
2199     NaturalNumberSet opBinary(string op)(in NaturalNumberSet other) const pure nothrow if (op.among("|", "^", "&"))
2200     {
2201         NaturalNumberSet result;
2202         result.parts.length = max(this.parts.length, other.parts.length);
2203         result.nMax = max(this.nMax, other.nMax);
2204 
2205         auto numCommonParts = min(this.parts.length, other.parts.length);
2206 
2207         foreach (i; 0 .. numCommonParts)
2208             result.parts[i] = mixin("this.parts[i] " ~ op ~ " other.parts[i]");
2209 
2210         static if (op.among("|", "^"))
2211         {
2212             if (this.parts.length > numCommonParts)
2213                 result.parts[numCommonParts .. $] = this.parts[numCommonParts .. $];
2214             if (other.parts.length > numCommonParts)
2215                 result.parts[numCommonParts .. $] = other.parts[numCommonParts .. $];
2216         }
2217 
2218         return result;
2219     }
2220 
2221     bool intersects(in NaturalNumberSet other) const pure nothrow
2222     {
2223         auto numCommonParts = min(this.parts.length, other.parts.length);
2224 
2225         foreach (i; 0 .. numCommonParts)
2226         {
2227             if ((this.parts[i] & other.parts[i]) != emptyPart)
2228                 return true;
2229         }
2230 
2231         return false;
2232     }
2233 
2234     @property size_t size() const pure nothrow
2235     {
2236         size_t numSetBits;
2237 
2238         foreach (i, part; parts)
2239         {
2240             size_t j = 0;
2241 
2242             while ((part >> j) != emptyPart && j < partSize)
2243             {
2244                 while (((part >> j) & firstBit) != firstBit)
2245                     ++j;
2246                 ++numSetBits;
2247                 ++j;
2248             }
2249         }
2250 
2251         return numSetBits;
2252     }
2253 
2254     size_t minElement() const
2255     {
2256         foreach (i, part; parts)
2257         {
2258             if (part != emptyPart)
2259             {
2260                 size_t j = 0;
2261 
2262                 while (((part >> j) & firstBit) != firstBit)
2263                 {
2264                     ++j;
2265                 }
2266 
2267                 return i * partSize + j;
2268             }
2269         }
2270 
2271         throw new EmptySetException("empty set has no minElement");
2272     }
2273 
2274     size_t maxElement() const
2275     {
2276         foreach (i, part; parts.retro.enumerate)
2277         {
2278             if (part != emptyPart)
2279             {
2280                 size_t j = 0;
2281 
2282                 while (((part << j) & lastBit) != lastBit)
2283                 {
2284                     ++j;
2285                 }
2286 
2287                 return (parts.length - i - 1) * partSize + (partSize - j - 1);
2288             }
2289         }
2290 
2291         throw new EmptySetException("empty set has no maxElement");
2292     }
2293 
2294     unittest
2295     {
2296         foreach (i; 0 .. 2 * NaturalNumberSet.partSize)
2297         {
2298             NaturalNumberSet set;
2299 
2300             set.add(i + 5);
2301             set.add(i + 7);
2302 
2303             assert(set.minElement() == i + 5);
2304             assert(set.maxElement() == i + 7);
2305         }
2306     }
2307 
2308     /// Returns a range of the elements in this set. The elements are ordered
2309     /// ascending.
2310     @property auto elements() const pure nothrow
2311     {
2312         static struct ElementsRange
2313         {
2314             const NaturalNumberSet* set;
2315             bool _empty = false;
2316             size_t i = 0;
2317             size_t part;
2318             size_t j = 0;
2319 
2320             this(const NaturalNumberSet* set) pure nothrow
2321             {
2322                 this.set = set;
2323                 this._empty = set.empty;
2324 
2325                 if (!this.empty)
2326                 {
2327                     this.part = set.parts[i];
2328                     if (!set.has(front))
2329                     {
2330                         popFront();
2331                     }
2332                 }
2333             }
2334 
2335             @property ElementsRange save() const pure nothrow
2336             {
2337                 return this;
2338             }
2339 
2340             void popFront() pure nothrow
2341             {
2342                 assert(!empty, "Attempting to popFront an empty elements range");
2343                 ++j;
2344 
2345                 while (shiftedPartEmpty)
2346                 {
2347                     nextPart();
2348 
2349                     if (empty)
2350                     {
2351                         return;
2352                     }
2353                 }
2354 
2355                 while (((part >> j) & firstBit) != firstBit && !shiftedPartEmpty)
2356                 {
2357                     ++j;
2358                 }
2359 
2360                 if (shiftedPartEmpty)
2361                 {
2362                     popFront();
2363                 }
2364             }
2365 
2366             @property size_t front() const pure nothrow
2367             {
2368                 assert(!empty, "Attempting to fetch the front of an empty elements range");
2369                 return i * partSize + j;
2370             }
2371 
2372             @property bool empty() const pure nothrow
2373             {
2374                 return _empty;
2375             }
2376 
2377             private @property bool shiftedPartEmpty() const pure nothrow
2378             {
2379                 return (part >> j) == emptyPart || j >= partSize;
2380             }
2381 
2382             private void nextPart() pure nothrow
2383             {
2384                 // move to start of next part
2385                 ++i;
2386                 j = 0;
2387 
2388                 if (i < set.parts.length)
2389                 {
2390                     part = set.parts[i];
2391                 }
2392                 else
2393                 {
2394                     _empty = true;
2395                 }
2396             }
2397         }
2398 
2399         return ElementsRange(&this);
2400     }
2401 
2402     ///
2403     unittest
2404     {
2405         import std.algorithm : equal;
2406         import std.range : iota;
2407 
2408         NaturalNumberSet set;
2409         auto someNumbers = iota(set.partSize).filter!"a % 3 == 0";
2410 
2411         foreach (i; someNumbers)
2412         {
2413             set.add(i);
2414         }
2415 
2416         assert(equal(someNumbers, set.elements));
2417     }
2418 
2419     /// The set may be modified while iterating:
2420     unittest
2421     {
2422         import std.algorithm : equal;
2423         import std.range : iota;
2424 
2425         enum numElements = 64;
2426         auto set = NaturalNumberSet(numElements, Yes.addAll);
2427 
2428         foreach (i; set.elements)
2429         {
2430             if (i % 10 == 0)
2431                 set.remove(i + 1);
2432         }
2433 
2434         auto expectedNumbers = iota(numElements).filter!"a == 0 || !((a - 1) % 10 == 0)";
2435         assert(equal(expectedNumbers, set.elements));
2436     }
2437 
2438     string toString() const pure
2439     {
2440         return format("[%(%d,%)]", this.elements);
2441     }
2442 }
2443 
2444 unittest
2445 {
2446     NaturalNumberSet set;
2447 
2448     // add some numbers
2449     foreach (i; 0 .. set.partSize)
2450     {
2451         if (i % 2 == 0)
2452         {
2453             set.add(i);
2454         }
2455     }
2456 
2457     // force extension of set
2458     foreach (i; set.partSize .. 2 * set.partSize)
2459     {
2460         if (i % 3 == 0)
2461         {
2462             set.add(i);
2463         }
2464     }
2465 
2466     // validate presence
2467     foreach (i; 0 .. 2 * set.partSize)
2468     {
2469         if (i / set.partSize == 0 && i % 2 == 0)
2470         {
2471             assert(set.has(i));
2472         }
2473         else if (i / set.partSize == 1 && i % 3 == 0)
2474         {
2475             assert(set.has(i));
2476         }
2477         else
2478         {
2479             assert(!set.has(i));
2480         }
2481     }
2482 }
2483 
2484 /**
2485     Find all maximal connected components of a graph-like structure. The
2486     predicate `isConnected` will be evaluated `O(n^^2)` times in the
2487     worst-case and `Ω(n)` in the best case. In expectation it will be
2488     evaluated `θ(n*log(n))`.
2489 
2490     Params:
2491         isConnected =   binary predicate that evaluates to true iff two nodes,
2492                         represented as indices, are connected
2493         numNodes    =   total number of nodes in the graph
2494 
2495     Returns:    range of maxmimally connected components represented as
2496                 `NaturalNumberSet`s
2497 */
2498 auto findMaximallyConnectedComponents(alias isConnected)(in size_t numNodes)
2499 {
2500     return MaximalConnectedComponents!(binaryFun!isConnected)(numNodes);
2501 }
2502 
2503 ///
2504 unittest
2505 {
2506     import std.algorithm : equal;
2507     import std.range : only;
2508 
2509     alias modEqv(size_t m) = (a, b) => (a % m) == (b % m);
2510     alias clusterByThreshold(size_t t) = (a, b) => (a < t) == (b < t);
2511 
2512     assert(equal(
2513         findMaximallyConnectedComponents!(modEqv!5)(15),
2514         only(
2515             NaturalNumberSet.create(0, 5, 10),
2516             NaturalNumberSet.create(1, 6, 11),
2517             NaturalNumberSet.create(2, 7, 12),
2518             NaturalNumberSet.create(3, 8, 13),
2519             NaturalNumberSet.create(4, 9, 14),
2520         ),
2521     ));
2522     assert(equal(
2523         findMaximallyConnectedComponents!(modEqv!3)(15),
2524         only(
2525             NaturalNumberSet.create(0, 3, 6, 9, 12),
2526             NaturalNumberSet.create(1, 4, 7, 10, 13),
2527             NaturalNumberSet.create(2, 5, 8, 11, 14),
2528         ),
2529     ));
2530     assert(equal(
2531         findMaximallyConnectedComponents!(clusterByThreshold!10)(15),
2532         only(
2533             NaturalNumberSet.create(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
2534             NaturalNumberSet.create(10, 11, 12, 13, 14),
2535         ),
2536     ));
2537 }
2538 
2539 ///
2540 unittest
2541 {
2542     import std.algorithm : equal;
2543     import std.range : only;
2544 
2545     auto connectivity = [
2546         [false, false, false, true ],
2547         [false, false, true , false],
2548         [false, true , false, false],
2549         [true , false, false, false],
2550     ];
2551     alias isConnected = (i, j) => connectivity[i][j];
2552 
2553     assert(equal(
2554         findMaximallyConnectedComponents!isConnected(4),
2555         only(
2556             NaturalNumberSet.create(0, 3),
2557             NaturalNumberSet.create(1, 2),
2558         ),
2559     ));
2560 }
2561 
2562 private struct MaximalConnectedComponents(alias isConnected)
2563 {
2564 
2565     const(size_t) numNodes;
2566     NaturalNumberSet unvisited;
2567     NaturalNumberSet currentComponent;
2568 
2569     this(in size_t numNodes)
2570     {
2571         this.numNodes = numNodes;
2572         this.unvisited = NaturalNumberSet(numNodes, Yes.addAll);
2573         this.currentComponent = NaturalNumberSet(numNodes);
2574 
2575         if (!empty)
2576             popFront();
2577     }
2578 
2579     void popFront()
2580     {
2581         assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof);
2582 
2583         currentComponent.clear();
2584 
2585         if (unvisited.empty)
2586             return;
2587 
2588         auto seedNode = unvisited.minElement;
2589 
2590         maximizeConnectedComponent(seedNode);
2591     }
2592 
2593     private void maximizeConnectedComponent(size_t node)
2594     {
2595         currentComponent.add(node);
2596         unvisited.remove(node);
2597 
2598         foreach (nextNode; unvisited.elements)
2599             if (isConnected(node, nextNode))
2600                 maximizeConnectedComponent(nextNode);
2601     }
2602 
2603     @property NaturalNumberSet front()
2604     {
2605         assert(!empty, "Attempting to fetch the front an empty " ~ typeof(this).stringof);
2606 
2607         return currentComponent;
2608     }
2609 
2610     @property bool empty() const pure nothrow
2611     {
2612         return unvisited.empty && currentComponent.empty;
2613     }
2614 }
2615 
2616 /**
2617     Find a cycle base of an undirected graph using the Paton's
2618     algorithm.
2619 
2620     The algorithm is described in
2621 
2622     > K. Paton, An algorithm for finding a fundamental set of cycles
2623     > for an undirected linear graph, Comm. ACM 12 (1969), pp. 514-518.
2624 
2625     and the implementation is adapted from the [Java implementation][1] of
2626     K. Paton originally licensed under [Apache License 2.0][2].
2627 
2628     [1]: https://code.google.com/archive/p/niographs/
2629     [2]: http://www.apache.org/licenses/LICENSE-2.0
2630 
2631     Returns: range of cycles in the graph represented as arrays of node indices
2632 */
2633 auto findCyclicSubgraphs(G)(
2634     G graph,
2635     G.IncidentEdgesCache incidentEdgesCache = G.IncidentEdgesCache.init,
2636 )
2637     if (is(G : Graph!Params, Params...))
2638 {
2639     auto node(in size_t idx)
2640     {
2641         return graph.nodes[idx];
2642     }
2643 
2644     version(assert) void assertValidCycle(in size_t[] cycle)
2645     {
2646         enum errorMsg = "not a cycle";
2647 
2648         assert(
2649             cycle.length > 0 && graph.edge(node(cycle[0]), node(cycle[$ - 1])) in graph,
2650             errorMsg
2651         );
2652 
2653         foreach (pair; cycle.slide!(No.withPartial)(2))
2654             assert(graph.edge(node(pair[0]), node(pair[1])) in graph, errorMsg);
2655     }
2656 
2657     auto numNodes = graph.nodes.length;
2658 
2659     NaturalNumberSet[] used;
2660     used.length = numNodes;
2661 
2662     long[] parent;
2663     parent.length = numNodes;
2664     parent[] = -1;
2665 
2666     size_t[] stack;
2667     stack.reserve(numNodes);
2668 
2669     auto cycles = appender!(size_t[][]);
2670 
2671     if (incidentEdgesCache == G.IncidentEdgesCache.init)
2672         incidentEdgesCache = graph.allIncidentEdges();
2673 
2674     foreach (rootIdx, root; graph.nodes)
2675     {
2676         // Loop over the connected
2677         // components of the graph.
2678         if (parent[rootIdx] >= 0)
2679             continue;
2680 
2681         // Prepare to walk the spanning tree.
2682         parent[rootIdx] = rootIdx;
2683         used[rootIdx].reserveFor(numNodes);
2684         used[rootIdx].add(rootIdx);
2685         stack ~= rootIdx;
2686 
2687         // Do the walk. It is a BFS with
2688         // a LIFO instead of the usual
2689         // FIFO. Thus it is easier to
2690         // find the cycles in the tree.
2691         while (stack.length > 0)
2692         {
2693             auto currentIdx = stack[$ - 1];
2694             --stack.length;
2695             auto current = node(currentIdx);
2696             auto currentUsed = &used[currentIdx];
2697 
2698             foreach (edge; incidentEdgesCache[currentIdx])
2699             {
2700                 auto neighbour = edge.target(current);
2701                 auto neighbourIdx = graph.indexOf(neighbour);
2702                 auto neighbourUsed = &used[neighbourIdx];
2703 
2704                 if (neighbourUsed.empty)
2705                 {
2706                     // found a new node
2707                     parent[neighbourIdx] = currentIdx;
2708                     neighbourUsed.reserveFor(numNodes);
2709                     neighbourUsed.add(currentIdx);
2710 
2711                     stack ~= neighbourIdx;
2712                 }
2713                 else if (neighbourIdx == currentIdx)
2714                 {
2715                     // found a self loop
2716                     auto cycle = [currentIdx];
2717                     cycles ~= cycle;
2718                     version(assert) assertValidCycle(cycle);
2719                 }
2720                 else if (!currentUsed.has(neighbourIdx))
2721                 {
2722                     // found a cycle
2723                     auto cycle = appender!(size_t[]);
2724                     cycle ~= neighbourIdx;
2725                     cycle ~= currentIdx;
2726 
2727                     auto p = parent[currentIdx];
2728                     for (; !neighbourUsed.has(p); p = parent[p])
2729                         cycle ~= p;
2730 
2731                     cycle ~= p;
2732                     cycles ~= cycle.data;
2733                     version(assert) assertValidCycle(cycle.data);
2734                     neighbourUsed.add(currentIdx);
2735                 }
2736             }
2737         }
2738     }
2739 
2740     return cycles.data;
2741 }
2742 
2743 ///
2744 unittest
2745 {
2746     alias G = Graph!int;
2747 
2748     //   __
2749     //   \ \
2750     //    `-0 -- 1 -- 2 -- 3
2751     //      |       / |    |
2752     //      |      /  |    |
2753     //      4 -- 5 -- 6    7
2754     auto g = G([0, 1, 2, 3, 4, 5, 6, 7], [
2755         G.edge(0, 0),
2756         G.edge(0, 1),
2757         G.edge(0, 4),
2758         G.edge(1, 2),
2759         G.edge(2, 3),
2760         G.edge(2, 5),
2761         G.edge(2, 6),
2762         G.edge(3, 7),
2763         G.edge(4, 5),
2764         G.edge(5, 6),
2765     ]);
2766     auto cycles = g.findCyclicSubgraphs();
2767 
2768     import std.algorithm : equal;
2769 
2770     assert(cycles.equal([
2771         [0],
2772         [2, 6, 5],
2773         [1, 2, 5, 4, 0],
2774     ]));
2775 }
2776 
2777 /**
2778     Find all maximal cliques in a graph represented by `adjacencyList`.
2779     The implementation is based on version 1 of the Bron-Kerbosch algorithm [1].
2780 
2781     [1]: Bron, C.; Kerbosch, J. (1973), "Algorithm 457: finding all cliques
2782          of an undirected graph", Communications of the ACM, 16 (9): 575–577,
2783          doi:10.1145/362342.362367.
2784 
2785     Returns: list of sets of nodes each representing a maximal clique
2786 */
2787 auto findAllCliques(in size_t[][] adjacencyList)
2788 {
2789     return BronKerboschVersion1(adjacencyList);
2790 }
2791 
2792 ///
2793 unittest
2794 {
2795     auto g = Graph!int([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
2796     g.add(g.edge(0, 1));
2797     g.add(g.edge(0, 2));
2798     g.add(g.edge(1, 2));
2799     g.add(g.edge(1, 7));
2800     g.add(g.edge(1, 8));
2801     g.add(g.edge(2, 3));
2802     g.add(g.edge(3, 4));
2803     g.add(g.edge(3, 5));
2804     g.add(g.edge(3, 6));
2805     g.add(g.edge(4, 5));
2806     g.add(g.edge(4, 6));
2807     g.add(g.edge(5, 6));
2808     g.add(g.edge(6, 7));
2809     g.add(g.edge(7, 8));
2810 
2811     auto cliques = array(findAllCliques(g.adjacencyList()));
2812 
2813     assert(cliques == [
2814         [0, 1, 2],
2815         [1, 7, 8],
2816         [2, 3],
2817         [3, 4, 5, 6],
2818         [6, 7],
2819         [9],
2820     ]);
2821 }
2822 
2823 private struct BronKerboschVersion1
2824 {
2825     const size_t[][] adjacencyList;
2826 
2827     int opApply(scope int delegate(size_t[]) yield)
2828     {
2829         size_t[] clique;
2830         clique.reserve(adjacencyList.length);
2831 
2832         auto candidates = NaturalNumberSet(adjacencyList.length, Yes.addAll);
2833         auto not = NaturalNumberSet(adjacencyList.length);
2834 
2835         return extendClique(clique, candidates, not, yield);
2836     }
2837 
2838     private int extendClique(
2839         size_t[] clique,
2840         NaturalNumberSet candidates,
2841         NaturalNumberSet not,
2842         scope int delegate(size_t[]) yield,
2843     )
2844     {
2845         import std.stdio;
2846 
2847         if (not.empty && candidates.empty)
2848             return clique.length == 0 ? 0 : yield(clique);
2849 
2850         int result;
2851 
2852         foreach (candidate; candidates.elements)
2853         {
2854             clique ~= candidate;
2855 
2856             auto reducedCandidates = NaturalNumberSet(adjacencyList.length);
2857             auto reducedNot = NaturalNumberSet(adjacencyList.length);
2858 
2859             foreach (neighbourNode; adjacencyList[candidate])
2860             {
2861                 if (candidates.has(neighbourNode))
2862                     reducedCandidates.add(neighbourNode);
2863                 if (not.has(neighbourNode))
2864                     reducedNot.add(neighbourNode);
2865             }
2866 
2867             result = extendClique(clique, reducedCandidates, reducedNot, yield);
2868 
2869             if (result)
2870                 return result;
2871 
2872             candidates.remove(candidate);
2873             not.add(candidate);
2874             --clique.length;
2875         }
2876 
2877         return result;
2878     }
2879 }
2880 
2881 /**
2882     Calculate a longest increasing subsequence of `sequence`. This subsequence
2883     is not necessarily contiguous, or unique. Given a `sequence` of `n`
2884     elements the algorithm uses `O(n log n)` evaluation of `pred`.
2885 
2886     See_Also: https://en.wikipedia.org/wiki/Longest_increasing_subsequence
2887 */
2888 auto longestIncreasingSubsequence(alias pred = "a < b", Range)(Range sequence)
2889         if (isRandomAccessRange!Range)
2890 {
2891     alias lessThan = binaryFun!pred;
2892 
2893     size_t[] subseqEnds;
2894     subseqEnds.length = sequence.length;
2895     size_t[] predecessors;
2896     predecessors.length = sequence.length;
2897     size_t subseqLength;
2898 
2899     foreach (i; 0 .. sequence.length)
2900     {
2901         // Binary search for the largest positive j < subseqLength
2902         // such that sequence[subseqEnds[j]] < sequence[i]
2903         long lo = 0;
2904         long hi = subseqLength - 1;
2905         auto pivot = sequence[i];
2906         assert(!lessThan(pivot, pivot), "`pred` is not anti-symmetric");
2907 
2908         while (lo <= hi)
2909         {
2910             auto mid = ceildiv(lo + hi, 2);
2911 
2912             if (lessThan(sequence[subseqEnds[mid]], pivot))
2913                 lo = mid + 1;
2914             else
2915                 hi = mid - 1;
2916         }
2917 
2918         // After searching, lo + 1 is the length of the longest prefix of
2919         // sequence[i]
2920         auto newSubseqLength = lo + 1;
2921 
2922         // The predecessor of sequence[i] is the last index of
2923         // the subsequence of length newSubseqLength - 1
2924         subseqEnds[lo] = i;
2925         if (lo > 0)
2926             predecessors[i] = subseqEnds[lo - 1];
2927 
2928         if (newSubseqLength > subseqLength)
2929             // If we found a subsequence longer than any we've
2930             // found yet, update subseqLength
2931             subseqLength = newSubseqLength;
2932     }
2933 
2934     auto subsequenceResult = subseqEnds[0 .. subseqLength];
2935 
2936     if (subseqLength > 0)
2937     {
2938         // Reconstruct the longest increasing subsequence
2939         // Note: reusing memory from now unused subseqEnds
2940         auto k = subseqEnds[subseqLength - 1];
2941         foreach_reverse (i; 0 .. subseqLength)
2942         {
2943             subsequenceResult[i] = k;
2944             k = predecessors[k];
2945         }
2946     }
2947 
2948     return subsequenceResult.map!(i => sequence[i]);
2949 }
2950 
2951 /// Example from Wikipedia
2952 unittest
2953 {
2954     import std.algorithm : equal;
2955 
2956     auto inputSequence = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
2957     auto expectedOutput = [0, 2, 6, 9, 11, 15];
2958 
2959     assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2960 }
2961 
2962 /// Example using a different `pred`
2963 unittest
2964 {
2965     import std.algorithm : equal;
2966     import std.range : retro;
2967 
2968     auto inputSequence = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
2969     auto expectedOutput = [12, 10, 9, 5, 3];
2970 
2971     assert(inputSequence.longestIncreasingSubsequence!"a > b".equal(expectedOutput));
2972 }
2973 
2974 unittest
2975 {
2976     import std.algorithm : equal;
2977 
2978     int[] inputSequence = [];
2979     int[] expectedOutput = [];
2980 
2981     assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2982 }
2983 
2984 unittest
2985 {
2986     import std.algorithm : equal;
2987 
2988     auto inputSequence = [1, 2, 3, 4, 5];
2989     auto expectedOutput = [1, 2, 3, 4, 5];
2990 
2991     assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
2992 }
2993 
2994 unittest
2995 {
2996     import std.algorithm : equal;
2997 
2998     auto inputSequence = [2, 1, 3, 4, 5];
2999     auto expectedOutput = [1, 3, 4, 5];
3000 
3001     assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
3002 }
3003 
3004 unittest
3005 {
3006     import std.algorithm : equal;
3007 
3008     auto inputSequence = [1, 2, 3, 5, 4];
3009     auto expectedOutput = [1, 2, 3, 4];
3010 
3011     assert(inputSequence.longestIncreasingSubsequence.equal(expectedOutput));
3012 }
3013 
3014 
3015 /**
3016     Compute a logarithmic index to `base` of `value` and vice versa.
3017     The function is piecewise linear for each interval of `base` indices.
3018     For interger values, the functions are mathematically equivalent to:
3019 
3020         logIndex(x, b) = (b - 1) ⌊log_b(x)⌋ + x / b^^⌊log_b(x)⌋
3021 
3022         inverseLogIndex(y, b) = (m + 1) * b^^d
3023         where
3024             m = (y - 1) mod (b - 1)
3025             d = ⌊(y - 1) / (b - 1)⌋
3026 */
3027 size_t logIndex(size_t value, size_t base) pure nothrow @safe
3028 {
3029     size_t nDigits;
3030 
3031     while (value >= base)
3032     {
3033         value /= base;
3034         ++nDigits;
3035     }
3036 
3037     return (base - 1) * nDigits + value;
3038 }
3039 
3040 ///
3041 unittest
3042 {
3043     enum base = 10;
3044     auto testValues = [
3045         0: 0,
3046         1: 1,
3047         2: 2,
3048         9: 9,
3049         10: 10,
3050         11: 10,
3051         19: 10,
3052         20: 11,
3053         21: 11,
3054         29: 11,
3055         99: 18,
3056         100: 19,
3057         101: 19,
3058         199: 19,
3059         200: 20,
3060         1000: 28,
3061     ];
3062 
3063     foreach (value, result; testValues)
3064         assert(
3065             logIndex(value, base) == result,
3066             format!"%d != %d"(logIndex(value, base), result),
3067         );
3068 }
3069 
3070 ///
3071 unittest
3072 {
3073     enum base = 16;
3074     auto testValues = [
3075         0x0000: 0,
3076         0x0001: 0x1,
3077         0x0002: 0x2,
3078         0x000f: 0xf,
3079         0x0010: 0x10,
3080         0x0011: 0x10,
3081         0x001f: 0x10,
3082         0x0020: 0x11,
3083         0x0021: 0x11,
3084         0x002f: 0x11,
3085         0x00ff: 0x1e,
3086         0x0100: 0x1f,
3087         0x0101: 0x1f,
3088         0x01ff: 0x1f,
3089         0x0200: 0x20,
3090         0x1000: 0x2e
3091     ];
3092 
3093     foreach (value, result; testValues)
3094         assert(
3095             logIndex(value, base) == result,
3096             format!"0x%x != 0x%x"(logIndex(value, base), result),
3097         );
3098 }
3099 
3100 /// ditto
3101 size_t inverseLogIndex(size_t value, size_t base) pure nothrow @safe
3102 {
3103     if (value == 0)
3104         return 0;
3105 
3106     auto nDigits = (value - 1) / (base - 1);
3107     auto rem = (value - 1) % (base - 1) + 1;
3108 
3109     return rem * base^^nDigits;
3110 }
3111 
3112 ///
3113 unittest
3114 {
3115     enum base = 10;
3116     auto testValues = [
3117         0: 0,
3118         1: 1,
3119         2: 2,
3120         9: 9,
3121         10: 10,
3122         10: 10,
3123         11: 20,
3124         11: 20,
3125         18: 90,
3126         19: 100,
3127         20: 200,
3128         28: 1000,
3129     ];
3130 
3131     foreach (value, result; testValues)
3132         assert(
3133             inverseLogIndex(value, base) == result,
3134             format!"%d != %d"(inverseLogIndex(value, base), result),
3135         );
3136 }
3137 
3138 ///
3139 unittest
3140 {
3141     enum base = 16;
3142     auto testValues = [
3143         0x00: 0x0,
3144         0x01: 0x1,
3145         0x02: 0x2,
3146         0x0f: 0xf,
3147         0x10: 0x10,
3148         0x10: 0x10,
3149         0x10: 0x10,
3150         0x11: 0x20,
3151         0x1e: 0xf0,
3152         0x1f: 0x100,
3153         0x20: 0x200,
3154         0x2e: 0x1000
3155     ];
3156 
3157     foreach (value, result; testValues)
3158         assert(
3159             inverseLogIndex(value, base) == result,
3160             format!"0x%x != 0x%x"(inverseLogIndex(value, base), result),
3161         );
3162 }
3163 
3164 unittest
3165 {
3166     auto testValues = [
3167         0: 0,
3168         1: 1,
3169         2: 2,
3170         3: 3,
3171         4: 4,
3172         5: 5,
3173         6: 6,
3174         7: 7,
3175         8: 8,
3176         9: 9,
3177         10: 10,
3178         11: 20,
3179         12: 30,
3180         13: 40,
3181         14: 50,
3182         15: 60,
3183         16: 70,
3184         17: 80,
3185         18: 90,
3186         19: 100,
3187         20: 200,
3188         21: 300,
3189         22: 400,
3190         23: 500,
3191         24: 600,
3192         25: 700,
3193         26: 800,
3194         27: 900,
3195         28: 1000,
3196         29: 2000,
3197         30: 3000,
3198     ];
3199 
3200     foreach (value, result; testValues)
3201         assert(
3202             inverseLogIndex(value, 10) == result,
3203             format!"%d != %d"(inverseLogIndex(value, 10), result),
3204         );
3205 }
3206 
3207 
3208 struct Histogram(T, Flag!"logIndex" logIndex = No.logIndex)
3209 {
3210     static assert(isNumeric!T, "currently only built-in numeric types are supported");
3211 
3212     static if (logIndex)
3213     {
3214         alias bin_size_t = size_t;
3215         alias indexBase = _binSize;
3216     }
3217     else
3218     {
3219         alias bin_size_t = T;
3220     }
3221 
3222 
3223     static if (isFloatingPoint!T)
3224     {
3225         enum valueInf = -T.infinity;
3226         enum valueSup = T.infinity;
3227     }
3228     else
3229     {
3230         enum valueInf = T.min;
3231         enum valueSup = T.max;
3232     }
3233 
3234 
3235     private
3236     {
3237         T _histMin;
3238         T _histMax;
3239         bin_size_t _binSize;
3240         enum size_t underflowIdx = 0;
3241         size_t overflowIdx;
3242         size_t[] _counts;
3243         size_t _totalCount;
3244         T _minValue;
3245         T _maxValue;
3246         T _sum;
3247     }
3248 
3249 
3250     this(T histMin, T histMax, bin_size_t binSize)
3251     {
3252         static if (isFloatingPoint!T)
3253             assert(
3254                 -T.infinity < histMin && histMax < T.infinity,
3255                 "histMin and histMax must be finite",
3256             );
3257         assert(histMin < histMax, "histMin should be less than histMax");
3258         assert(binSize > 0, "binSize/indexBase must be positive");
3259 
3260         if (histMin > histMax)
3261             swap(histMin, histMax);
3262 
3263         this._histMin = histMin;
3264         this._histMax = histMax;
3265         this._binSize = binSize;
3266         this.overflowIdx = rawBinIdx(histMax);
3267         this._counts = new size_t[overflowIdx + 1];
3268         this._minValue = valueSup;
3269         this._maxValue = valueInf;
3270         this._sum = 0;
3271 
3272         debug assert(binCoord(overflowIdx) <= histMax && histMax <= valueSup);
3273     }
3274 
3275 
3276     @property bool hasUnderflowBin() const pure nothrow @safe
3277     {
3278         static if (isFloatingPoint!T)
3279             return true;
3280         else
3281             return valueInf < histMin;
3282     }
3283 
3284 
3285     @property bool hasOverflowBin() const pure nothrow @safe
3286     {
3287         static if (isFloatingPoint!T)
3288             return true;
3289         else
3290             return histMax < valueSup;
3291     }
3292 
3293 
3294     @property inout(size_t[]) countsWithoutOutliers() inout pure nothrow @safe
3295     {
3296         size_t begin = cast(size_t) hasUnderflowBin;
3297         size_t end = _counts.length - 1 - cast(size_t) hasOverflowBin;
3298 
3299         return _counts[begin .. end];
3300     }
3301 
3302 
3303     @property size_t numUnderflows() const pure nothrow @safe
3304     {
3305         if (hasUnderflowBin)
3306             return _counts[underflowIdx];
3307         else
3308             return 0;
3309     }
3310 
3311 
3312     @property size_t numOverflows() const pure nothrow @safe
3313     {
3314         if (hasOverflowBin)
3315             return _counts[overflowIdx];
3316         else
3317             return 0;
3318     }
3319 
3320 
3321     /// Insert value into this histogram.
3322     void insert(T value)
3323     {
3324         ++_counts[binIdx(value)];
3325         ++_totalCount;
3326         _minValue = min(_minValue, value);
3327         _maxValue = max(_maxValue, value);
3328         _sum += value;
3329     }
3330 
3331 
3332     /// Insert a range of values into this histogram. This is equivalent to
3333     ///
3334     ///     foreach (value; values)
3335     ///         insert(value);
3336     void insert(R)(R values) if (isInputRange!R && is(ElementType!R == T))
3337     {
3338         foreach (value; values)
3339             insert(value);
3340     }
3341 
3342 
3343     /// Values smaller than this value are stored in the lower overflow bin.
3344     @property const(T) histMin() const pure nothrow @safe { return _histMin; }
3345 
3346     /// Values larger than this value are stored in the lower overflow bin.
3347     @property const(T) histMax() const pure nothrow @safe { return _histMax; }
3348 
3349     /// Total number of values stored in this histogram.
3350     @property const(size_t) totalCount() const pure nothrow @safe { return _totalCount; }
3351 
3352     /// Smallest value stored in this histogram. This is not subject to
3353     /// `histMin` and `histMax`.
3354     @property const(T) minValue() const pure nothrow @safe
3355     in (_totalCount > 0, "undefined for empty histogram")
3356     {
3357         return _minValue;
3358     }
3359 
3360     /// Largest value stored in this histogram. This is not subject to
3361     /// `histMin` and `histMax`.
3362     @property const(T) maxValue() const pure nothrow @safe
3363     in (_totalCount > 0, "undefined for empty histogram")
3364     {
3365         return _maxValue;
3366     }
3367 
3368     /// Sum of all values stored in this histogram. This is not subject to
3369     /// `histMin` and `histMax`.
3370     @property const(T) sum() const pure nothrow @safe
3371     in (_totalCount > 0, "undefined for empty histogram")
3372     {
3373         return _sum;
3374     }
3375 
3376 
3377     /// Returns a value such that roughly `percent` values in the histogram
3378     /// are smaller than value. The value is linearly interpolated between
3379     /// bin coordinates. The second form stores the bin index such that no
3380     /// more the `percent` of the values in the histrogram are in the bins
3381     /// up to `index` (inclusive).
3382     double percentile(
3383         double percent,
3384         Flag!"excludeOutliers" excludeOutliers = No.excludeOutliers,
3385     ) const pure
3386     {
3387         size_t index;
3388 
3389         return percentile(percent, index, excludeOutliers);
3390     }
3391 
3392     /// ditto
3393     double percentile(
3394         double percent,
3395         out size_t index,
3396         Flag!"excludeOutliers" excludeOutliers = No.excludeOutliers,
3397     ) const pure
3398     {
3399         assert(0.0 < percent && percent < 1.0, "percent must be between 0 and 1");
3400 
3401         if (totalCount == 0)
3402             return double.nan;
3403 
3404         auto threshold = excludeOutliers
3405             ? percent * (totalCount - (numUnderflows + numOverflows))
3406             : percent * totalCount;
3407 
3408         size_t partialSum;
3409         foreach (i, ref count; excludeOutliers ? countsWithoutOutliers : _counts)
3410         {
3411             if (partialSum + count >= threshold)
3412             {
3413                 index = i;
3414 
3415                 return binCoord(i) + binSize(i) * (threshold - partialSum) / count;
3416             }
3417 
3418             partialSum += count;
3419         }
3420 
3421         assert(0, "unreachable");
3422     }
3423 
3424 
3425     /// Returns the mean of the inserted values.
3426     @property double mean() const pure nothrow
3427     {
3428         return cast(double) sum / totalCount;
3429     }
3430 
3431 
3432     /// Iterates over the histogram bins enumerating the probability
3433     /// densities `density`.
3434     int opApply(scope int delegate(T coord, double density) yield)
3435     {
3436         int result;
3437 
3438         foreach (size_t idx, T coord, double density; this)
3439         {
3440             result = yield(coord, density);
3441 
3442             if (result)
3443                 break;
3444         }
3445 
3446         return result;
3447     }
3448 
3449     /// ditto
3450     int opApply(scope int delegate(size_t index, T coord, double density) yield)
3451     {
3452         int result;
3453 
3454         foreach (i, count; _counts)
3455         {
3456             result = yield(
3457                 i,
3458                 binCoord(i),
3459                 cast(double) count / (totalCount * binSize(i)),
3460             );
3461 
3462             if (result)
3463                 break;
3464         }
3465 
3466         return result;
3467     }
3468 
3469     /// ditto
3470     int opApplyReverse(scope int delegate(T coord, double density) yield)
3471     {
3472         int result;
3473 
3474         foreach_reverse (size_t idx, T coord, double density; this)
3475         {
3476             result = yield(coord, density);
3477 
3478             if (result)
3479                 break;
3480         }
3481 
3482         return result;
3483     }
3484 
3485     /// ditto
3486     int opApplyReverse(scope int delegate(size_t index, T coord, double density) yield)
3487     {
3488         int result;
3489 
3490         foreach_reverse (i, count; _counts)
3491         {
3492             result = yield(
3493                 i,
3494                 binCoord(i),
3495                 cast(double) count / (totalCount * binSize(i)),
3496             );
3497 
3498             if (result)
3499                 break;
3500         }
3501 
3502         return result;
3503     }
3504 
3505     /// ditto
3506     auto densities() const pure nothrow @safe
3507     {
3508         return _counts
3509             .enumerate
3510             .map!(enumValue => tuple!(
3511                 "index",
3512                 "coord",
3513                 "density",
3514             )(
3515                 enumValue.index,
3516                 binCoord(enumValue.index),
3517                 cast(double) enumValue.value / (totalCount * binSize(enumValue.index)),
3518             ));
3519     }
3520 
3521 
3522     /// Iterates over the histogram bins enumerating the counts.
3523     int opApply(scope int delegate(T coord, size_t count) yield)
3524     {
3525         int result;
3526 
3527         foreach (size_t idx, T coord, size_t count; this)
3528         {
3529             result = yield(coord, count);
3530 
3531             if (result)
3532                 break;
3533         }
3534 
3535         return result;
3536     }
3537 
3538     /// ditto
3539     int opApply(scope int delegate(size_t index, T coord, size_t count) yield)
3540     {
3541         int result;
3542 
3543         foreach (i, count; _counts)
3544         {
3545             result = yield(
3546                 i,
3547                 binCoord(i),
3548                 count,
3549             );
3550 
3551             if (result)
3552                 break;
3553         }
3554 
3555         return result;
3556     }
3557 
3558     /// ditto
3559     int opApplyReverse(scope int delegate(T coord, size_t count) yield)
3560     {
3561         int result;
3562 
3563         foreach_reverse (size_t idx, T coord, size_t count; this)
3564         {
3565             result = yield(coord, count);
3566 
3567             if (result)
3568                 break;
3569         }
3570 
3571         return result;
3572     }
3573 
3574     /// ditto
3575     int opApplyReverse(scope int delegate(size_t index, T coord, size_t count) yield)
3576     {
3577         int result;
3578 
3579         foreach_reverse (i, count; _counts)
3580         {
3581             result = yield(
3582                 i,
3583                 binCoord(i),
3584                 count,
3585             );
3586 
3587             if (result)
3588                 break;
3589         }
3590 
3591         return result;
3592     }
3593 
3594 
3595     /// Calculate the bin index corresponding to `value`.
3596     size_t binIdx(T value) const pure @safe
3597     {
3598         if (value < histMin)
3599             return underflowIdx;
3600         else if (histMax <= value)
3601             return overflowIdx;
3602         else
3603             return rawBinIdx(value);
3604     }
3605 
3606     /// ditto
3607     auto counts() const pure nothrow @safe
3608     {
3609         return _counts
3610             .enumerate
3611             .map!(enumValue => tuple!(
3612                 "index",
3613                 "coord",
3614                 "count",
3615             )(
3616                 enumValue.index,
3617                 binCoord(enumValue.index),
3618                 enumValue.value,
3619             ));
3620     }
3621 
3622 
3623     private size_t rawBinIdx(T value) const pure @safe
3624     {
3625         size_t idx;
3626 
3627         static if (logIndex)
3628         {
3629             static if (isFloatingPoint!T)
3630                 idx = dalicious.math.logIndex(stdFloor(normalizedValue(value)).to!size_t, indexBase);
3631             else
3632                 idx = dalicious.math.logIndex(normalizedValue(value).to!size_t, indexBase);
3633         }
3634         else
3635         {
3636             static if (isFloatingPoint!T)
3637                 idx = stdFloor(normalizedValue(value) / binSize).to!size_t;
3638             else
3639                 idx = (normalizedValue(value) / binSize).to!size_t;
3640         }
3641 
3642         return idx + cast(size_t) hasUnderflowBin;
3643     }
3644 
3645 
3646     /// Calculate the bin size at of bin `idx`.
3647     T binSize(size_t idx) const pure @safe
3648     {
3649         if (hasUnderflowBin && idx == underflowIdx)
3650             return minValue - valueInf;
3651         else if (hasOverflowBin && idx == overflowIdx)
3652             return valueSup - histMax;
3653 
3654         static if (logIndex)
3655         {
3656             idx -= cast(size_t) hasUnderflowBin;
3657 
3658             return to!T(
3659                 dalicious.math.inverseLogIndex(idx + 1, indexBase)
3660                 -
3661                 dalicious.math.inverseLogIndex(idx, indexBase)
3662             );
3663         }
3664         else
3665         {
3666             return binSize;
3667         }
3668     }
3669 
3670     /// ditto
3671     static if (!logIndex)
3672         T binSize() const pure @safe
3673         {
3674             return _binSize;
3675         }
3676 
3677 
3678     /// Calculate the value corresponding to the bin index.
3679     T binCoord(size_t idx) const pure @safe
3680     {
3681         if (hasUnderflowBin && idx == underflowIdx)
3682             return valueInf;
3683         else if (hasOverflowBin && idx == overflowIdx)
3684             return histMax;
3685 
3686         idx -= cast(size_t) hasUnderflowBin;
3687 
3688         static if (logIndex)
3689             return primalValue(to!T(dalicious.math.inverseLogIndex(idx, indexBase)));
3690         else
3691             return primalValue(to!T(idx * binSize));
3692     }
3693 
3694 
3695     private T normalizedValue(T value) const pure nothrow @safe
3696     out (nValue; nValue >= 0, "normalizedValue must be non-negative")
3697     {
3698         static if (isUnsigned!T)
3699             assert(histMin <= value, "subtraction overflow");
3700 
3701         return cast(T) (value - histMin);
3702     }
3703 
3704 
3705     private T primalValue(T nValue) const pure nothrow @safe
3706     in (nValue >= 0, "nValue must be non-negative")
3707     {
3708         return cast(T) (nValue + histMin);
3709     }
3710 
3711 
3712     /// Returns a space-separated table of the histogram bins and header lines
3713     /// with `totalCount`, `minValue`, `maxValue` and `sum`. The header lines
3714     /// begin with a has sign (`#`) so they may be treated as comments
3715     /// by other programs.
3716     string toString() const
3717     {
3718         enum histFmt = format!(`
3719             # totalCount=%%d
3720             # minValue=%%%1$c
3721             # maxValue=%%%1$c
3722             # sum=%%%1$c
3723             # mean=%%g
3724             %%-(%%s\%%)
3725         `.outdent.strip.tr(`\`, "\n"))(isFloatingPoint!T ? 'g' : 'd');
3726         enum histLineFmt = isFloatingPoint!T
3727             ? "%g %g"
3728             : "%d %g";
3729 
3730         return format!histFmt(
3731             totalCount,
3732             minValue,
3733             maxValue,
3734             sum,
3735             mean,
3736             densities.map!(v => format!histLineFmt(v.coord, v.density)),
3737         );
3738     }
3739 }
3740 
3741 unittest
3742 {
3743     auto h = logHistogram(0U, 10000U, 10U);
3744 
3745     with (h)
3746     {
3747         assert(binIdx(0) == 0);
3748         assert(binIdx(1) == 1);
3749         assert(binIdx(2) == 2);
3750         assert(binIdx(9) == 9);
3751         assert(binIdx(10) == 10);
3752         assert(binIdx(11) == 10);
3753         assert(binIdx(19) == 10);
3754         assert(binIdx(20) == 11);
3755         assert(binIdx(21) == 11);
3756         assert(binIdx(29) == 11);
3757         assert(binIdx(99) == 18);
3758         assert(binIdx(100) == 19);
3759         assert(binIdx(101) == 19);
3760         assert(binIdx(199) == 19);
3761         assert(binIdx(200) == 20);
3762         assert(binIdx(1000) == 28);
3763 
3764         assert(binSize(0) == 1);
3765         assert(binSize(1) == 1);
3766         assert(binSize(2) == 1);
3767         assert(binSize(3) == 1);
3768         assert(binSize(4) == 1);
3769         assert(binSize(5) == 1);
3770         assert(binSize(6) == 1);
3771         assert(binSize(7) == 1);
3772         assert(binSize(8) == 1);
3773         assert(binSize(9) == 1);
3774         assert(binSize(10) == 10);
3775         assert(binSize(11) == 10);
3776         assert(binSize(12) == 10);
3777         assert(binSize(13) == 10);
3778         assert(binSize(14) == 10);
3779         assert(binSize(15) == 10);
3780         assert(binSize(16) == 10);
3781         assert(binSize(17) == 10);
3782         assert(binSize(18) == 10);
3783         assert(binSize(19) == 100);
3784         assert(binSize(20) == 100);
3785         assert(binSize(21) == 100);
3786         assert(binSize(22) == 100);
3787         assert(binSize(23) == 100);
3788         assert(binSize(24) == 100);
3789         assert(binSize(25) == 100);
3790         assert(binSize(26) == 100);
3791         assert(binSize(27) == 100);
3792         assert(binSize(28) == 1000);
3793         assert(binSize(29) == 1000);
3794         assert(binSize(30) == 1000);
3795 
3796         assert(binCoord(0) == 0);
3797         assert(binCoord(1) == 1);
3798         assert(binCoord(2) == 2);
3799         assert(binCoord(3) == 3);
3800         assert(binCoord(4) == 4);
3801         assert(binCoord(5) == 5);
3802         assert(binCoord(6) == 6);
3803         assert(binCoord(7) == 7);
3804         assert(binCoord(8) == 8);
3805         assert(binCoord(9) == 9);
3806         assert(binCoord(10) == 10);
3807         assert(binCoord(11) == 20);
3808         assert(binCoord(12) == 30);
3809         assert(binCoord(13) == 40);
3810         assert(binCoord(14) == 50);
3811         assert(binCoord(15) == 60);
3812         assert(binCoord(16) == 70);
3813         assert(binCoord(17) == 80);
3814         assert(binCoord(18) == 90);
3815         assert(binCoord(19) == 100);
3816         assert(binCoord(20) == 200);
3817         assert(binCoord(21) == 300);
3818         assert(binCoord(22) == 400);
3819         assert(binCoord(23) == 500);
3820         assert(binCoord(24) == 600);
3821         assert(binCoord(25) == 700);
3822         assert(binCoord(26) == 800);
3823         assert(binCoord(27) == 900);
3824         assert(binCoord(28) == 1000);
3825         assert(binCoord(29) == 2000);
3826         assert(binCoord(30) == 3000);
3827     }
3828 }
3829 
3830 
3831 /**
3832     Creates a histogram of values. Additional values can be inserted into the
3833     histogram using the `insert` method. The second form `logHistogram`
3834     creates a histogram with logarithmic bin sizes.
3835 
3836     See_also:
3837         Histogram,
3838         dalicious.math.logIndex
3839 */
3840 Histogram!T histogram(R, T = ElementType!R)(T histMin, T histMax, T binSize, R values) if (isInputRange!R)
3841 {
3842     auto hist = typeof(return)(histMin, histMax, binSize);
3843 
3844     hist.insert(values);
3845 
3846     return hist;
3847 }
3848 
3849 /// ditto
3850 Histogram!T histogram(T)(T histMin, T histMax, T binSize)
3851 {
3852     return typeof(return)(histMin, histMax, binSize);
3853 }
3854 
3855 /// ditto
3856 Histogram!(T, Yes.logIndex) logHistogram(R, T = ElementType!R)(T histMin, T histMax, size_t indexBase, R values) if (isInputRange!R)
3857 {
3858     auto hist = typeof(return)(histMin, histMax, indexBase);
3859 
3860     hist.insert(values);
3861 
3862     return hist;
3863 }
3864 
3865 /// ditto
3866 Histogram!(T, Yes.logIndex) logHistogram(T)(T histMin, T histMax, size_t indexBase)
3867 {
3868     return typeof(return)(histMin, histMax, indexBase);
3869 }
3870 
3871 ///
3872 unittest
3873 {
3874     // Generate a histogram of standard-normal-distributed numbers
3875     // with 7+2 bins from -2.0 to 2.0. The additional two bins are
3876     // the overflow bins.
3877     auto h = histogram(-2.0, 2.0, 0.5, [
3878         0.697108, 0.019264, -1.838430, 1.831528, -0.804880, -1.558828,
3879         -0.131643, -0.306090, -0.397831, 0.037725, 0.328819, -0.640064,
3880         0.664097, 1.156503, -0.837012, -0.969499, -1.410276, 0.501637,
3881         1.521720, 1.392988, -0.619393, -0.039576, 1.937708, -1.325983,
3882         -0.677214, 1.390584, 1.798133, -1.094093, 2.263360, -0.462949,
3883         1.993554, 2.243889, 1.606391, 0.153866, 1.945514, 1.007849,
3884         -0.663765, -0.304843, 0.617464, 0.674804, 0.038555, 1.696985,
3885         1.473917, -0.244211, -1.410381, 0.201184, -0.923119, -0.220677,
3886         0.045521, -1.966340,
3887     ]);
3888 
3889     assert(h.totalCount == 50);
3890     assert(approxEqual(h.minValue, -1.96634));
3891     assert(approxEqual(h.maxValue, 2.26336));
3892     assert(approxEqual(h.sum, 10.3936));
3893     assert(approxEqual(h.mean, 0.2079));
3894     assert(approxEqual(h.percentile(0.5), 0.1429));
3895 
3896     enum inf = double.infinity;
3897     auto expectedHist = [
3898         [-inf, 0.00],
3899         [-2.0, 0.12],
3900         [-1.5, 0.16],
3901         [-1.0, 0.32],
3902         [-0.5, 0.32],
3903         [ 0.0, 0.28],
3904         [ 0.5, 0.20],
3905         [ 1.0, 0.20],
3906         [ 1.5, 0.32],
3907         [ 2.0, 0.00],
3908     ];
3909 
3910     foreach (idx, coord, double density; h)
3911     {
3912         assert(approxEqual(expectedHist[idx][0], coord));
3913         assert(approxEqual(expectedHist[idx][1], density));
3914     }
3915 }
3916 
3917 ///
3918 unittest
3919 {
3920     // Generate a histogram of geometric-distributed numbers.
3921     auto h = logHistogram(0U, 50U, 10U, [
3922         3U, 4U, 23U, 2U, 0U, 2U, 9U, 0U, 17U, 2U, 0U, 5U, 5U, 35U, 0U, 16U,
3923         17U, 3U, 7U, 14U, 3U, 9U, 1U, 17U, 13U, 10U, 38U, 2U, 1U, 29U, 1U,
3924         5U, 49U, 40U, 2U, 1U, 13U, 5U, 1U, 1U, 2U, 4U, 1U, 0U, 0U, 7U, 7U,
3925         34U, 3U, 2U,
3926     ]);
3927 
3928     assert(h.totalCount == 50);
3929     assert(h.minValue == 0);
3930     assert(h.maxValue == 49);
3931     assert(h.sum == 465);
3932     assert(approxEqual(h.mean, 9.3));
3933     assert(approxEqual(h.percentile(0.5), 4.5));
3934 
3935     auto expectedHist = [
3936         [ 0, 0.120],
3937         [ 1, 0.140],
3938         [ 2, 0.140],
3939         [ 3, 0.080],
3940         [ 4, 0.040],
3941         [ 5, 0.080],
3942         [ 6, 0.000],
3943         [ 7, 0.060],
3944         [ 8, 0.000],
3945         [ 9, 0.040],
3946         [10, 0.016],
3947         [20, 0.004],
3948         [30, 0.006],
3949         [40, 0.004],
3950         [50, 0.000],
3951     ];
3952 
3953     foreach (idx, coord, double density; h)
3954     {
3955         assert(approxEqual(expectedHist[idx][0], coord));
3956         assert(approxEqual(expectedHist[idx][1], density));
3957     }
3958 }
3959 
3960 unittest
3961 {
3962     auto h = histogram(0U, 50U, 1U, [
3963         3U, 4U, 23U, 2U, 0U, 2U, 9U, 0U, 17U, 2U, 0U, 5U, 5U, 35U, 0U, 16U,
3964         17U, 3U, 7U, 14U, 3U, 9U, 1U, 17U, 13U, 10U, 38U, 2U, 1U, 29U, 1U,
3965         5U, 49U, 40U, 2U, 1U, 13U, 5U, 1U, 1U, 2U, 4U, 1U, 0U, 0U, 7U, 7U,
3966         34U, 3U, 2U,
3967     ]);
3968 
3969     assert(h.totalCount == 50);
3970     assert(h.minValue == 0);
3971     assert(h.maxValue == 49);
3972     assert(h.sum == 465);
3973     assert(approxEqual(h.mean, 9.3));
3974     assert(approxEqual(h.percentile(0.5), 4.5));
3975 
3976     assert(equal(h.counts.map!"a.count", [
3977         6, 7, 7, 4, 2, 4, 0, 3, 0, 2, 1, 0, 0, 2, 1, 0, 1, 3, 0, 0, 0, 0, 0,
3978         1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
3979         0, 0, 0, 1, 0,
3980     ]));
3981 }