1 /** 2 * T-Tree. 3 * Copyright: © 2014 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.ttree; 9 10 //version(graphviz_debugging) import std.stdio; 11 12 /** 13 * Implements a binary search tree with multiple items per tree node. 14 * 15 * T-tree Nodes are (by default) sized to fit within a 64-byte 16 * cache line. The number of items stored per node can be read from the 17 * nodeCapacity field. Each node has 0, 1, or 2 children. Each node has between 18 * 1 and $(B nodeCapacity) items, or it has $(B nodeCapacity) items and 0 or 19 * more children. 20 * Params: 21 * T = the element type 22 * allowDuplicates = if true, duplicate values will be allowed in the tree 23 * less = the comparitor function to use 24 * cacheLineSize = Nodes will be sized to fit within this number of bytes. 25 * supportGC = true if the container should support holding references to 26 * GC-allocated memory. 27 * See_also: $(LINK http://en.wikipedia.org/wiki/T-tree) 28 */ 29 struct TTree(T, bool allowDuplicates = false, alias less = "a < b", 30 bool supportGC = true, size_t cacheLineSize = 64) 31 { 32 this(this) @disable; 33 34 ~this() 35 { 36 if (root is null) 37 return; 38 deallocateNode(root); 39 } 40 41 private import containers.internal.storage_type : ContainerStorageType; 42 43 private enum size_t nodeCapacity = fatNodeCapacity!(T.sizeof, 3, size_t, cacheLineSize); 44 static assert (nodeCapacity <= (size_t.sizeof * 4), "cannot fit height info and registry in size_t"); 45 46 debug(EMSI_CONTAINERS) invariant() 47 { 48 assert (root is null || _length != 0); 49 } 50 51 52 alias Value = ContainerStorageType!T; 53 54 /// $(B tree ~= item) operator overload. 55 void opOpAssign(string op)(T value) if (op == "~") 56 { 57 insert(value); 58 } 59 60 /** 61 * Returns true if any values were added. This is not a stable insert. You 62 * will get strange results if you insert into a tree while iterating over 63 * it. 64 */ 65 bool insert(T value) 66 { 67 if (root is null) 68 { 69 root = allocateNode(value, null); 70 ++_length; 71 return true; 72 } 73 immutable bool r = root.insert(value, root); 74 if (r) 75 ++_length; 76 return r; 77 } 78 79 /// ditto 80 bool insert(R)(R r) 81 { 82 bool retVal = false; 83 while (!r.empty) 84 { 85 retVal = insert(r.front()) || retVal; 86 r.popFront(); 87 } 88 return retVal; 89 } 90 91 /// ditto 92 bool insert(T[] values) 93 { 94 bool retVal = false; 95 foreach (ref v; values) 96 retVal = insert(v) || retVal; 97 return retVal; 98 } 99 100 /** 101 * Removes a value from the tree. 102 * Params: 103 * value = a value equal to the one to be removed 104 * cleanup = a function that should be run on the removed item 105 * Retuns: true if any value was removed 106 */ 107 bool remove(T value, void delegate(T) cleanup = null) 108 { 109 bool removed = root !is null && root.remove(cast(Value) value, root, cleanup); 110 if (removed) 111 --_length; 112 if (_length == 0) 113 deallocateNode(root); 114 return removed; 115 } 116 117 /** 118 * Returns true if the tree _conains the given value 119 */ 120 bool contains(T value) const 121 { 122 return root !is null && root.contains(value); 123 } 124 125 /** 126 * Returns: the number of elements in the tree. 127 */ 128 size_t length() const nothrow pure @property 129 { 130 return _length; 131 } 132 133 /** 134 * Returns: true if the tree is empty. 135 */ 136 bool empty() const nothrow pure @property 137 { 138 return _length == 0; 139 } 140 141 version(graphviz_debugging) void print(File f) 142 { 143 f.writeln("digraph g {"); 144 root.print(f); 145 f.writeln("}"); 146 } 147 148 /** 149 * Returns: a range over the tree. Do not insert into the tree while 150 * iterating because you may iterate over the same value multiple times. 151 */ 152 Range opSlice() const 153 { 154 return Range(root, Range.type.all, T.init); 155 } 156 157 /** 158 * Returns: a range of elements which are less than value. 159 */ 160 Range lowerBound(inout T value) const 161 { 162 return Range(root, Range.Type.lower, value); 163 } 164 165 /** 166 * Returns: a range of elements which are equivalent (though not necessarily 167 * equal) to value. 168 */ 169 Range equalRange(inout T value) const 170 { 171 return Range(root, Range.Type.equal, value); 172 } 173 174 /** 175 * Returns: a range of elements which are greater than value. 176 */ 177 Range upperBound(inout T value) const 178 { 179 return Range(root, Range.Type.upper, value); 180 } 181 182 /** 183 * Tree range 184 */ 185 static struct Range 186 { 187 @disable this(); 188 189 /** 190 * Standard range operations 191 */ 192 T front() const @property 193 in 194 { 195 assert (!empty); 196 } 197 body 198 { 199 return cast(typeof(return)) current.values[index]; 200 } 201 202 /// ditto 203 bool empty() const nothrow pure @property 204 { 205 return current is null; 206 } 207 208 /// ditto 209 void popFront() 210 { 211 _popFront(); 212 if (current is null) 213 return; 214 final switch (type) 215 { 216 case Type.upper: 217 case Type.all: break; 218 case Type.equal: 219 if (_less(front(), val) || _less(val, front())) 220 current = null; 221 break; 222 case Type.lower: 223 if (!_less(front(), val)) 224 current = null; 225 break; 226 } 227 } 228 229 /// ditto 230 Range save() @property 231 { 232 return this; 233 } 234 235 private: 236 237 enum Type : ubyte {all, lower, equal, upper} 238 239 void currentToLeftmost() 240 { 241 if (current is null) 242 return; 243 while (current.left !is null) 244 current = current.left; 245 } 246 247 void currentToLeastContaining(inout T val) 248 { 249 while (current !is null) 250 { 251 if (_less(val, current.values[0])) 252 current = current.left; 253 else if (current.isFull) 254 { 255 if (_less(current.values[$ - 1], cast(Value) val)) 256 current = current.right; 257 else 258 break; 259 } 260 else 261 break; 262 } 263 } 264 265 this(inout(Node)* n, Type type, inout T val) 266 { 267 current = n; 268 this.type = type; 269 this.val = val; 270 final switch(type) 271 { 272 case Type.all: 273 currentToLeftmost(); 274 break; 275 case Type.lower: 276 currentToLeftmost(); 277 if (_less(val, front())) 278 current = null; 279 break; 280 case Type.equal: 281 currentToLeastContaining(val); 282 while (current !is null && _less(front(), val)) 283 _popFront(); 284 if (current is null || _less(front(), val) || _less(val, front())) 285 current = null; 286 break; 287 case Type.upper: 288 currentToLeastContaining(val); 289 while (current !is null && !_less(val, front())) 290 _popFront(); 291 break; 292 } 293 } 294 295 void _popFront() 296 in 297 { 298 assert (!empty); 299 } 300 body 301 { 302 index++; 303 if (index >= nodeCapacity || current.isFree(index)) 304 { 305 index = 0; 306 if (current.right !is null) 307 { 308 current = current.right; 309 while (current.left !is null) 310 current = current.left; 311 } 312 else if (current.parent is null) 313 current = null; 314 else if (current.parent.left is current) 315 current = current.parent; 316 else 317 { 318 while (current.parent.right is current) 319 { 320 current = current.parent; 321 if (current.parent is null) 322 { 323 current = null; 324 return; 325 } 326 } 327 current = current.parent; 328 } 329 } 330 } 331 332 size_t index; 333 const(Node)* current; 334 Type type; 335 const T val; 336 } 337 338 private: 339 340 import containers.internal.node : fatNodeCapacity, fullBits, shouldAddGCRange, shouldNullSlot; 341 import std.algorithm : sort; 342 import std.allocator: Mallocator, allocate, deallocate; 343 import std.functional: binaryFun; 344 import std.traits: isPointer, PointerTarget; 345 346 // If we're storing a struct that defines opCmp, don't compare pointers as 347 // that is almost certainly not what the user intended. 348 static if (is(typeof(less) == string ) && less == "a < b" && isPointer!T && __traits(hasMember, PointerTarget!T, "opCmp")) 349 alias _less = binaryFun!"a.opCmp(*b) < 0"; 350 else 351 alias _less = binaryFun!less; 352 353 static Node* allocateNode(ref T value, Node* parent) 354 out (result) 355 { 356 assert (result.left is null); 357 assert (result.right is null); 358 } 359 body 360 { 361 import core.memory : GC; 362 Node* n = allocate!Node(Mallocator.it); 363 n.parent = parent; 364 n.markUsed(0); 365 n.values[0] = cast(Value) value; 366 static if (supportGC && shouldAddGCRange!T) 367 GC.addRange(n, Node.sizeof); 368 return n; 369 } 370 371 static void deallocateNode(ref Node* n) 372 in 373 { 374 assert (n !is null); 375 } 376 body 377 { 378 import core.memory : GC; 379 static if (supportGC && shouldAddGCRange!T) 380 GC.removeRange(n); 381 typeid(Node).destroy(n); 382 deallocate!Node(Mallocator.it, n); 383 n = null; 384 } 385 386 static assert (nodeCapacity <= (typeof(Node.registry).sizeof * 8)); 387 static assert (Node.sizeof <= cacheLineSize); 388 static struct Node 389 { 390 ~this() 391 { 392 if (left !is null) 393 deallocateNode(left); 394 if (right !is null) 395 deallocateNode(right); 396 } 397 398 private size_t nextAvailableIndex() const nothrow pure 399 { 400 import core.bitop : bsf; 401 immutable i = ~(registry & fullBits!nodeCapacity); 402 assert(i != 0); 403 return bsf(i); 404 } 405 406 private void markUsed(size_t index) pure nothrow 407 { 408 registry |= (1 << index); 409 } 410 411 private void markUnused(size_t index) pure nothrow 412 { 413 registry &= ~(1 << index); 414 static if (shouldNullSlot!T) 415 values[index] = null; 416 } 417 418 private bool isFree(size_t index) const pure nothrow 419 { 420 return (registry & (1 << index)) == 0; 421 } 422 423 private bool isFull() const pure nothrow 424 { 425 return (registry & fullBits!nodeCapacity) == fullBits!nodeCapacity; 426 } 427 428 private bool isEmpty() const pure nothrow 429 { 430 return (registry & fullBits!nodeCapacity) == 0; 431 } 432 433 bool contains(Value value) const 434 { 435 import std.range; 436 size_t i = nextAvailableIndex(); 437 if (_less(value, cast(Value) values[0])) 438 return left !is null && left.contains(value); 439 if (_less(values[i - 1], value)) 440 return right !is null && right.contains(value); 441 return !assumeSorted!_less(values[0 .. i]).equalRange(value).empty; 442 } 443 444 size_t calcHeight() nothrow pure 445 { 446 size_t l = left !is null ? left.height() : 0; 447 size_t r = right !is null ? right.height() : 0; 448 size_t h = 1 + (l > r ? l : r); 449 registry &= fullBits!nodeCapacity; 450 registry |= (h << (size_t.sizeof * 4)); 451 return h; 452 } 453 454 size_t height() const nothrow pure 455 { 456 return registry >>> (size_t.sizeof * 4); 457 } 458 459 int imbalanced() const nothrow pure 460 { 461 if (right !is null 462 && ((left is null && right.height() > 1) 463 || (left !is null && right.height() > left.height() + 1))) 464 return 1; 465 if (left !is null 466 && ((right is null && left.height() > 1) 467 || (right !is null && left.height() > right.height() + 1))) 468 return -1; 469 return 0; 470 } 471 472 bool insert(T value, ref Node* root) 473 in 474 { 475 static if (isPointer!T || is (T == class)) 476 assert (value !is null); 477 } 478 body 479 { 480 import std.algorithm : sort; 481 import std.range; 482 if (!isFull()) 483 { 484 immutable size_t index = nextAvailableIndex(); 485 static if (!allowDuplicates) 486 { 487 if (!assumeSorted!_less(values[0 .. index]).equalRange( 488 cast(Value) value).empty) 489 { 490 return false; 491 } 492 } 493 values[index] = cast(Value) value; 494 markUsed(index); 495 sort!_less(values[0 .. index + 1]); 496 return true; 497 } 498 if (_less(value, values[0])) 499 { 500 if (left is null) 501 { 502 left = allocateNode(value, &this); 503 calcHeight(); 504 return true; 505 } 506 bool b = left.insert(value, root); 507 if (imbalanced() == -1) 508 rotateRight(root); 509 calcHeight(); 510 return b; 511 } 512 if (_less(values[$ - 1], cast(Value) value)) 513 { 514 if (right is null) 515 { 516 right = allocateNode(value, &this); 517 calcHeight(); 518 return true; 519 } 520 bool b = right.insert(value, root); 521 if (imbalanced() == 1) 522 rotateLeft(root); 523 calcHeight(); 524 return b; 525 } 526 static if (!allowDuplicates) 527 if (!assumeSorted!_less(values[]).equalRange(cast(Value) value).empty) 528 return false; 529 Value[nodeCapacity + 1] temp = void; 530 temp[0 .. $ - 1] = values[]; 531 temp[$ - 1] = cast(Value) value; 532 sort!_less(temp[]); 533 if (right is null) 534 { 535 values[] = temp[0 .. $ - 1]; 536 right = allocateNode(temp[$ - 1], &this); 537 return true; 538 } 539 if (left is null) 540 { 541 values[] = temp[1 .. $]; 542 left = allocateNode(temp[0], &this); 543 return true; 544 } 545 if (right.height < left.height) 546 { 547 values[] = temp[0 .. $ - 1]; 548 bool b = right.insert(temp[$ - 1], root); 549 if (imbalanced() == 1) 550 rotateLeft(root); 551 calcHeight(); 552 return b; 553 } 554 values[] = temp[1 .. $]; 555 bool b = left.insert(temp[0], root); 556 if (imbalanced() == -1) 557 rotateRight(root); 558 calcHeight(); 559 return b; 560 } 561 562 bool remove(Value value, ref Node* n, void delegate(T) cleanup = null) 563 { 564 import std.range; 565 assert (!isEmpty()); 566 if (isFull() && _less(value, values[0])) 567 { 568 bool r = left !is null && left.remove(value, left, cleanup); 569 if (left.isEmpty()) 570 deallocateNode(left); 571 return r; 572 } 573 if (isFull() && _less(values[$ - 1], value)) 574 { 575 bool r = right !is null && right.remove(value, right, cleanup); 576 if (right.isEmpty()) 577 deallocateNode(right); 578 return r; 579 } 580 size_t i = nextAvailableIndex(); 581 auto sv = assumeSorted!_less(values[0 .. i]); 582 auto tri = sv.trisect(value); 583 if (tri[1].length == 0) 584 return false; 585 // Clean up removed item 586 if (cleanup !is null) 587 cleanup(tri[1][0]); 588 immutable size_t l = tri[0].length; 589 if (right is null && left is null) 590 { 591 Value[nodeCapacity - 1] temp; 592 temp[0 .. l] = values[0 .. l]; 593 temp[l .. $] = values[l + 1 .. $]; 594 values[0 .. $ - 1] = temp[]; 595 markUnused(nextAvailableIndex() - 1); 596 } 597 else if (right !is null) 598 { 599 Value[nodeCapacity - 1] temp; 600 temp[0 .. l] = values[0 .. l]; 601 temp[l .. $] = values[l + 1 .. $]; 602 values[0 .. $ - 1] = temp[]; 603 values[$ - 1] = right.removeSmallest(); 604 if (right.isEmpty()) 605 deallocateNode(right); 606 } 607 else if (left !is null) 608 { 609 Value[nodeCapacity - 1] temp; 610 temp[0 .. l] = values[0 .. l]; 611 temp[l .. $] = values[l + 1 .. $]; 612 values[1 .. $] = temp[]; 613 values[0] = left.removeLargest(); 614 if (left.isEmpty()) 615 deallocateNode(left); 616 } 617 return true; 618 } 619 620 Value removeSmallest() 621 in 622 { 623 assert (!isEmpty()); 624 } 625 body 626 { 627 if (left is null && right is null) 628 { 629 Value r = values[0]; 630 Value[nodeCapacity - 1] temp = void; 631 temp[] = values[1 .. $]; 632 values[0 .. $ - 1] = temp[]; 633 markUnused(nextAvailableIndex() - 1); 634 return r; 635 } 636 if (left !is null) 637 { 638 auto r = left.removeSmallest(); 639 if (left.isEmpty()) 640 deallocateNode(left); 641 return r; 642 } 643 Value r = values[0]; 644 Value[nodeCapacity - 1] temp = void; 645 temp[] = values[1 .. $]; 646 values[0 .. $ - 1] = temp[]; 647 values[$ - 1] = right.removeSmallest(); 648 if (right.isEmpty()) 649 deallocateNode(right); 650 return r; 651 } 652 653 Value removeLargest() 654 in 655 { 656 assert (!isEmpty()); 657 } 658 out (result) 659 { 660 static if (isPointer!T || is (T == class)) 661 assert (result !is null); 662 } 663 body 664 { 665 if (left is null && right is null) 666 { 667 size_t i = nextAvailableIndex() - 1; 668 Value r = values[i]; 669 markUnused(i); 670 return r; 671 } 672 if (right !is null) 673 { 674 auto r = right.removeLargest(); 675 if (right.isEmpty()) 676 deallocateNode(right); 677 return r; 678 } 679 Value r = values[$ - 1]; 680 Value[nodeCapacity - 1] temp = void; 681 temp[] = values[0 .. $ - 1]; 682 values[1 .. $] = temp[]; 683 values[0] = left.removeLargest(); 684 if (left.isEmpty()) 685 deallocateNode(left); 686 return r; 687 } 688 689 void rotateLeft(ref Node* root) 690 { 691 Node* newRoot = void; 692 if (right.left !is null && right.right is null) 693 { 694 newRoot = right.left; 695 newRoot.parent = this.parent; 696 newRoot.left = &this; 697 newRoot.left.parent = newRoot; 698 newRoot.right = right; 699 newRoot.right.parent = newRoot; 700 newRoot.right.left = null; 701 right = null; 702 left = null; 703 } 704 else 705 { 706 newRoot = right; 707 newRoot.parent = this.parent; 708 right = newRoot.left; 709 if (right !is null) 710 right.parent = &this; 711 newRoot.left = &this; 712 this.parent = newRoot; 713 } 714 cleanup(newRoot, root); 715 } 716 717 void rotateRight(ref Node* root) 718 { 719 Node* newRoot = void; 720 if (left.right !is null && left.left is null) 721 { 722 newRoot = left.right; 723 newRoot.parent = this.parent; 724 newRoot.right = &this; 725 newRoot.right.parent = newRoot; 726 newRoot.left = left; 727 newRoot.left.parent = newRoot; 728 newRoot.left.right = null; 729 left = null; 730 right = null; 731 } 732 else 733 { 734 newRoot = left; 735 newRoot.parent = this.parent; 736 left = newRoot.right; 737 if (left !is null) 738 left.parent = &this; 739 newRoot.right = &this; 740 this.parent = newRoot; 741 } 742 cleanup(newRoot, root); 743 } 744 745 void cleanup(Node* newRoot, ref Node* root) 746 { 747 if (newRoot.parent !is null) 748 { 749 if (newRoot.parent.right is &this) 750 newRoot.parent.right = newRoot; 751 else 752 newRoot.parent.left = newRoot; 753 } 754 else 755 root = newRoot; 756 newRoot.fillFromChildren(root); 757 if (newRoot.left !is null) 758 { 759 newRoot.left.fillFromChildren(root); 760 } 761 if (newRoot.right !is null) 762 { 763 newRoot.right.fillFromChildren(root); 764 } 765 if (newRoot.left !is null) 766 newRoot.left.calcHeight(); 767 if (newRoot.right !is null) 768 newRoot.right.calcHeight(); 769 newRoot.calcHeight(); 770 } 771 772 void fillFromChildren(ref Node* root) 773 { 774 while (!isFull()) 775 { 776 if (left !is null) 777 { 778 insert(left.removeLargest(), root); 779 if (left.isEmpty()) 780 deallocateNode(left); 781 } 782 else if (right !is null) 783 { 784 insert(right.removeSmallest(), root); 785 if (right.isEmpty()) 786 deallocateNode(right); 787 } 788 else 789 break; 790 } 791 } 792 793 version(graphviz_debugging) void print(File f) 794 { 795 f.writef("\"%016x\"[shape=record, label=\"<f0>%d|", &this, height()); 796 f.write("<f1>|"); 797 foreach (i, v; values) 798 { 799 if (isFree(i)) 800 f.write("<f> |"); 801 else 802 f.writef("<f> %s|", v); 803 } 804 f.write("<f2>\"];"); 805 if (left !is null) 806 { 807 f.writefln("\"%016x\":f1 -> \"%016x\";", &this, left); 808 left.print(f); 809 } 810 if (right !is null) 811 { 812 f.writefln("\"%016x\":f2 -> \"%016x\";", &this, right); 813 right.print(f); 814 } 815 if (parent !is null) 816 { 817 f.writefln("\"%016x\" -> \"%016x\";", &this, parent); 818 } 819 } 820 821 debug(EMSI_CONTAINERS) invariant() 822 { 823 import std.string : format; 824 assert (&this !is null); 825 assert (left !is &this, "%x, %x".format(left, &this)); 826 assert (right !is &this, "%x, %x".format(right, &this)); 827 if (left !is null) 828 { 829 assert (left.left !is &this, "%s".format(values)); 830 assert (left.right !is &this, "%x, %x".format(left.right, &this)); 831 assert (left.parent is &this, "%x, %x, %x".format(left, left.parent, &this)); 832 } 833 if (right !is null) 834 { 835 assert (right.left !is &this, "%s".format(values)); 836 assert (right.right !is &this, "%s".format(values)); 837 assert (right.parent is &this); 838 } 839 } 840 841 // version(graphviz_debugging) void print(File f) 842 // { 843 // f.writef("\"%016x\"[shape=record, label=\"", &this); 844 // f.write("<f1>|"); 845 // foreach (i, v; values) 846 // { 847 // if (isFree(i)) 848 // f.write("<f> |"); 849 // else 850 // f.writef("<f> %s|", v); 851 // } 852 // f.write("<f2>\"];"); 853 // if (left !is null) 854 // { 855 // f.writefln("\"%016x\":f1 -> \"%016x\";", &this, left); 856 // left.print(f); 857 // } 858 // if (right !is null) 859 // { 860 // f.writefln("\"%016x\":f2 -> \"%016x\";", &this, right); 861 // right.print(f); 862 // } 863 // } 864 865 Node* left; 866 Node* right; 867 Node* parent; 868 869 Value[nodeCapacity] values; 870 size_t registry = (cast(size_t) 1) << (size_t.sizeof * 4); 871 } 872 873 size_t _length = 0; 874 Node* root = null; 875 } 876 877 unittest 878 { 879 import core.memory : GC; 880 import std.algorithm : equal, sort, map, filter; 881 import std.array : array; 882 import std.range : iota, walkLength, isForwardRange; 883 import std.string : format; 884 import std.uuid : randomUUID; 885 886 { 887 TTree!int kt; 888 assert (kt.empty); 889 foreach (i; 0 .. 200) 890 { 891 assert (kt.insert(i)); 892 // version(graphviz_debugging) 893 // { 894 // File f = File("graph%04d.dot".format(i), "w"); 895 // kt.print(f); 896 // } 897 } 898 assert (!kt.empty); 899 assert (kt.length == 200); 900 assert (kt.contains(30)); 901 // writeln(__LINE__, " passed"); 902 } 903 904 { 905 TTree!int kt; 906 assert (!kt.contains(5)); 907 kt.insert(2_000); 908 assert (kt.contains(2_000)); 909 foreach_reverse (i; 0 .. 1_000) 910 { 911 assert (kt.insert(i)); 912 } 913 assert (!kt.contains(100_000)); 914 // writeln(__LINE__, " passed"); 915 } 916 917 { 918 import std.random; 919 TTree!int kt; 920 foreach (i; 0 .. 1_000) 921 { 922 kt.insert(uniform(0, 100_000)); 923 } 924 // writeln(__LINE__, " passed"); 925 } 926 927 { 928 TTree!int kt; 929 kt.insert(10); 930 assert (kt.length == 1); 931 assert (!kt.insert(10)); 932 assert (kt.length == 1); 933 // writeln(__LINE__, " passed"); 934 } 935 936 { 937 TTree!(int, true) kt; 938 assert (kt.insert(1)); 939 assert (kt.length == 1); 940 assert (kt.insert(1)); 941 assert (kt.length == 2); 942 assert (kt.contains(1)); 943 // writeln(__LINE__, " passed"); 944 } 945 946 { 947 TTree!(int) kt; 948 foreach (i; 0 .. 200) 949 { 950 assert (kt.insert(i)); 951 // version(graphviz_debugging) 952 // { 953 // File f = File("graph%04d.dot".format(i), "w"); 954 // kt.print(f); 955 // } 956 } 957 assert (kt.length == 200); 958 assert (kt.remove(79)); 959 assert (!kt.remove(79)); 960 // version(graphviz_debugging) 961 // { 962 // File f = File("graph%04d.dot".format(999), "w"); 963 // kt.print(f); 964 // } 965 assert (kt.length == 199); 966 // writeln(__LINE__, " passed"); 967 } 968 969 { 970 string[] strs = [ 971 "2c381d2a-bacd-40db-b6d8-055b144c5ee6", 972 "62104b50-e235-4c95-bcb9-a545e88e2d09", 973 "828c8fc0-a392-4738-a49c-62e991fce090", 974 "62e30465-79eb-446e-b34f-af5d7c491486", 975 "93ec245b-60d2-4422-91ff-66a6d7e299fc", 976 "c1d2f3d7-82cc-4d90-a2c5-9fba335f36cd", 977 "c9d8d980-94eb-4941-b873-00d68021522f", 978 "82dbc4df-cb3c-447a-9d73-cd6291a0ba02", 979 "8d259231-6ab6-49e4-9bb6-fe097c4153ed", 980 "f9f2d719-61e1-4f62-ae2c-bf2a24a13d5b" 981 ]; 982 TTree!string strings; 983 foreach (i, s; strs) 984 { 985 assert (strings.insert(s)); 986 // version(graphviz_debugging) 987 // { 988 // File f = File("graph%04d.dot".format(i), "w"); 989 // strings.print(f); 990 // } 991 } 992 sort(strs[]); 993 // writeln(strings[]); 994 assert (equal(strs, strings[])); 995 // writeln(__LINE__, " passed"); 996 } 997 998 foreach (x; 0 .. 1000) 999 { 1000 TTree!string strings; 1001 string[] strs = iota(10).map!(a => randomUUID().toString()).array(); 1002 foreach (i, s; strs) 1003 { 1004 assert (strings.insert(s)); 1005 // version(graphviz_debugging) 1006 // { 1007 // File f = File("graph%04d.dot".format(i), "w"); 1008 // strings.print(f); 1009 // } 1010 } 1011 assert (strings.length == strs.length); 1012 // version(graphviz_debugging) 1013 // { 1014 // File f = File("graph%04d.dot".format(1000), "w"); 1015 // strings.print(f); 1016 // } 1017 sort(strs); 1018 // writeln(strings[]); 1019 assert (equal(strs, strings[])); 1020 // writeln(__LINE__, " passed"); 1021 } 1022 1023 { 1024 TTree!string strings; 1025 strings.insert(["e", "f", "a", "b", "c", "d"]); 1026 // writeln(strings[]); 1027 assert (equal(strings[], ["a", "b", "c", "d", "e", "f"])); 1028 // writeln(__LINE__, " passed"); 1029 } 1030 1031 { 1032 TTree!(string, true) strings; 1033 assert (strings.insert("b")); 1034 assert (strings.insert("c")); 1035 assert (strings.insert("a")); 1036 assert (strings.insert("d")); 1037 assert (strings.insert("d")); 1038 assert (strings.length == 5); 1039 assert (equal(strings.equalRange("d"), ["d", "d"])); 1040 assert (equal(strings.lowerBound("d"), ["a", "b", "c"])); 1041 assert (equal(strings.upperBound("c"), ["d", "d"])); 1042 // writeln(__LINE__, " passed"); 1043 } 1044 1045 { 1046 // import std.stdio; 1047 static struct S 1048 { 1049 string x; 1050 int opCmp (ref const S other) const 1051 { 1052 if (x < other.x) 1053 return -1; 1054 if (x > other.x) 1055 return 1; 1056 return 0; 1057 } 1058 } 1059 1060 TTree!(S*, true) stringTree; 1061 auto one = S("offset"); 1062 stringTree.insert(&one); 1063 auto two = S("object"); 1064 assert (stringTree.equalRange(&two).empty); 1065 assert (!stringTree.equalRange(&one).empty); 1066 assert (stringTree[].front.x == "offset"); 1067 // writeln(__LINE__, " passed"); 1068 } 1069 1070 { 1071 static struct TestStruct 1072 { 1073 int opCmp(ref const TestStruct other) const 1074 { 1075 return x < other.x ? -1 : (x > other.x ? 1 : 0); 1076 } 1077 int x; 1078 int y; 1079 } 1080 TTree!(TestStruct*, false) tsTree; 1081 static assert (isForwardRange!(typeof(tsTree).Range)); 1082 foreach (i; 0 .. 100) 1083 { 1084 assert(tsTree.insert(new TestStruct(i, i * 2))); 1085 version(graphviz_debugging) 1086 { 1087 File f = File("graph%04d.dot".format(i), "w"); 1088 tsTree.print(f); 1089 } 1090 } 1091 assert (tsTree.length == 100); 1092 auto r = tsTree[]; 1093 TestStruct* prev = r.front(); 1094 r.popFront(); 1095 while (!r.empty) 1096 { 1097 assert (r.front.x > prev.x, format("%s %s", prev.x, r.front.x)); 1098 prev = r.front; 1099 r.popFront(); 1100 } 1101 TestStruct a = TestStruct(30, 100); 1102 auto eqArray = array(tsTree.equalRange(&a)); 1103 assert (eqArray.length == 1, format("%d", eqArray.length)); 1104 // writeln(__LINE__, " passed"); 1105 } 1106 1107 { 1108 import std.algorithm : canFind; 1109 TTree!int ints; 1110 foreach (i; 0 .. 50) 1111 ints ~= i; 1112 assert (canFind(ints[], 20)); 1113 assert (walkLength(ints[]) == 50); 1114 assert (walkLength(filter!(a => (a & 1) == 0)(ints[])) == 25); 1115 // writeln(__LINE__, " passed"); 1116 } 1117 1118 { 1119 TTree!int ints; 1120 foreach (i; 0 .. 50) 1121 ints ~= i; 1122 ints.remove(0); 1123 assert (ints.length == 49); 1124 foreach (i; 1 .. 12) 1125 { 1126 // try 1127 ints.remove(i); 1128 // catch (Error) 1129 // writeln("Failed on ", i); 1130 } 1131 assert (ints.length == 49 - 11); 1132 } 1133 1134 // This test ensures that the container works with const elements 1135 { 1136 const(TTree!(const(int))) getInts() 1137 { 1138 TTree!(const(int)) t; 1139 t.insert(1); 1140 t.insert(2); 1141 t.insert(3); 1142 return t; 1143 } 1144 auto t = getInts(); 1145 static assert (is (typeof(t[].front) == const(int))); 1146 assert (equal(t[].filter!(a => a & 1), [1, 3])); 1147 } 1148 }