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