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 }