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