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