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