1 /** 2 Vanilla B-Tree implementation. 3 Note that this is an implementation detail of 4 `dplug.core.map` and not part of of the Dplug API. 5 6 Copyright: (c) Guillaume Piolat 2024-2025. 7 License: [BSL-1.0](http://www.boost.org/LICENSE_1_0.txt) 8 */ 9 module dplug.core.btree; 10 11 import dplug.core.nogc; 12 13 import std.functional: binaryFun; 14 15 //debug = btree; 16 17 debug(btree) 18 { 19 import core.stdc.stdio; 20 } 21 22 /** 23 Vanilla [B-Tree](https://en.wikipedia.org/wiki/B-tree). 24 25 `O(lg(n))` insertion, removal, and search time, much 26 like the builtin associative arrays. 27 28 `.init` is valid, needs no initialization. 29 30 Params: 31 K = Key type, must be comparable with `less`. 32 V = Value type. 33 less = Must be a strict inequality, orders all `K`. 34 allowDuplicates = Are duplicate keys allowed? 35 duplicateKeyIsUB = Are duplicate keys UB? 36 When `true`, user MUST guarantee 37 no duplicates and `insert` is 38 faster. 39 40 Warning: keys don't need `opEquals`, but: 41 `!(a < b) && !(b > a)` 42 should imply that: 43 `a == b` 44 */ 45 struct BTree(K, V, alias less = "a < b", 46 bool allowDuplicates = false, 47 bool duplicateKeyIsUB = false) 48 { 49 public: 50 nothrow: 51 @nogc: 52 53 // TODO: map over range of keys 54 // PERF: tune minDegree vs size of K and V, there must 55 // be a sweet spot 56 // PERF: allocate nodes in memory pool, to be built in 57 // bulk. How does reclaim works in that case, in a 58 // way that doesn't explode memory? 59 // PERF: first node could be interned in the struct 60 // itself (interior pointer) however that requires 61 // first to tweak the branching factor with size 62 // of item. 63 // PERF: (requires: Node memory pool) find a way to 64 // deallocate all at once. 65 // PERF: find how to use duplicateKeyIsUB for Map and 66 // Set. 67 68 debug(btree) invariant() 69 { 70 checkInvariant(); 71 } 72 73 74 /** 75 Not copyable. 76 */ 77 @disable this(this); 78 79 80 /** 81 Constructor. 82 A new B-tree has no allocation and zero items. 83 */ 84 this(int dummy) 85 { 86 // It does nothing, because T.init is valid. 87 } 88 89 90 /** 91 Destructor. All nodes cleared. 92 */ 93 ~this() 94 { 95 if (_root) 96 { 97 _root.reclaimMemory(); 98 _root = null; 99 } 100 } 101 102 103 /** 104 Number of items in the B-Tree. 105 Returns: Number of items. 106 */ 107 size_t length() const 108 { 109 return _count; 110 } 111 112 113 /** 114 Is this B-Tree empty? 115 Returns: `true` if zero items. 116 */ 117 bool empty() const 118 { 119 return _count == 0; 120 } 121 122 123 /** 124 Insert item in tree. 125 126 Params: 127 key = Key to insert. 128 value = Value to insert associated with `key`. 129 130 Returns: `true` if the insertion took place. 131 132 Warning: Inserting duplicate keys when duplicates 133 are not supported is Undefined Behaviour. 134 Use `contains()` to avoid that case. 135 */ 136 bool insert(K key, V value) 137 { 138 lazyInitialize(); 139 140 // Runtime check to prevent accidental dupe keys. 141 static if (!allowDuplicates) 142 { 143 static if (duplicateKeyIsUB) 144 { 145 // Detect dupes, but in -release it will 146 // be Undefined Behaviour 147 assert(findKeyValue(_root, key) is null); 148 } 149 else 150 { 151 // Always detect dupes, this slows down 152 // insertion by a lot 153 if (findKeyValue(_root, key) !is null) 154 return false; 155 } 156 } 157 158 Node* r = _root; 159 160 if (r.isFull()) 161 { 162 Node* s = allocateNode(); 163 _root = s; 164 s.parent = null; 165 s.isLeaf = false; 166 s.numKeys = 0; 167 s.children[0] = r; 168 r.parent = s; 169 splitChild(s, 0, r); 170 insertNonFull(s, key, value); 171 } 172 else 173 { 174 insertNonFull(r, key, value); 175 } 176 177 _count += 1; 178 return true; 179 } 180 181 182 /** 183 Erase an item from the tree. 184 185 Params: 186 key = Key to remove. 187 188 Returns: Number of removed items, can be 0 or 1. In 189 case of dupe keys, remove only one of them. 190 */ 191 size_t remove(K key) 192 { 193 // This was surprisingly undocumented on the 194 // Internet or LLM. 195 196 if (_root is null) 197 return 0; 198 199 int keyIndex; 200 Node* node = findNode(_root, key, keyIndex); 201 if (node is null) 202 return 0; // not found 203 204 _count -= 1; 205 206 // Reference: 207 // https://www.youtube.com/watch?v=0NvlyJDfk1M 208 if (node.isLeaf) 209 { 210 // First, remove key, then eventually rebalance. 211 deleteKeyValueAtIndexAndShift(node, keyIndex); 212 rebalanceAfterDeletion(node); 213 } 214 else 215 { 216 // Exchange key with either highest of the 217 // smaller key in leaf, or largest of the 218 // highest keys. 219 // 220 // . value . 221 // / \ 222 // / \ 223 // / \ 224 // left subtree right subtree 225 // 226 // But I'm not sure why people tout this 227 // solution. 228 // Here we simply always get to the rightmost 229 // leaf node of left subtree. It seems it's 230 // always possible indeed, and it's not faster 231 // to look at the other sub-tree. 232 Node* leaf = node.children[keyIndex]; 233 while (!leaf.isLeaf) 234 leaf = leaf.children[leaf.numKeys]; 235 assert(leaf); 236 237 // Remove key from leaf, put it instead of 238 // target. 239 node.kv[keyIndex] = leaf.kv[leaf.numKeys-1]; 240 leaf.numKeys -= 1; 241 242 // and then rebalance 243 rebalanceAfterDeletion(leaf); 244 } 245 return 1; 246 } 247 248 249 /** 250 Iterate over all values in the tree. 251 252 Returns: Forward range of `V`, over the whole tree. 253 */ 254 auto byValue() 255 { 256 return BTreeRange!(RangeType.value)(this); 257 } 258 ///ditto 259 auto byValue() const 260 { 261 return const(BTreeRange!(RangeType.value))(this); 262 } 263 264 /** 265 Iterate over all keys in the tree. 266 267 Returns: Forward range of `K`, over the whole tree. 268 */ 269 auto byKey() 270 { 271 return BTreeRange!(RangeType.key)(this); 272 } 273 ///ditto 274 auto byKey() const 275 { 276 return const(BTreeRange!(RangeType.key))(this); 277 } 278 279 /** 280 Iterate over all keys and values simultaneously. 281 282 Returns: Forward range of a Voldemort struct that 283 exposes `.key` and `.value` of type `K` and 284 `V` respectively. 285 */ 286 auto byKeyValue() 287 { 288 return BTreeRange!(RangeType.keyValue)(this); 289 } 290 ///ditto 291 auto byKeyValue() const 292 { 293 return const(BTreeRange!(RangeType.keyValue))(this); 294 } 295 296 297 /** 298 Search the B-Tree by key. 299 300 Params: 301 key = Key to search for. 302 303 Returns: 304 A pointer to the corresponding `V` value. 305 `null` if not found. 306 307 Note: In case of duplicate keys, it returns one 308 of those in unspecified order. 309 */ 310 inout(V)* opBinaryRight(string op)(K key) inout 311 if (op == "in") 312 { 313 if (_root is null) 314 return null; 315 inout(KeyValue)* kv = findKeyValue(_root, key); 316 if (!kv) return null; 317 return &kv.value; 318 } 319 320 /** 321 Search the B-Tree by key. 322 323 Params: 324 key = Key to search for. 325 326 Returns: A reference to the value corresponding to 327 this key. 328 329 Note: In case of duplicate keys, it returns one 330 of those in unspecified order. 331 */ 332 ref inout(V) opIndex(K key) inout 333 { 334 inout(V)* p = key in this; 335 return *p; 336 } 337 338 /** 339 Search the B-Tree for a key. 340 341 Params: 342 key = Key to search for. 343 344 Returns: `true` if `key`` is contained in the tree. 345 */ 346 bool contains(K key) const 347 { 348 if (_root is null) 349 return false; 350 return findKeyValue(_root, key) !is null; 351 } 352 353 private: 354 355 // Called "t" or "minimum degree" in litterature, can 356 // never be < 2. 357 // Make it lower (eg: 2) to test tree algorithms. 358 // See <digression> below to see why this is not B-Tree 359 // "order". 360 enum minDegree = 16; 361 362 // Every node must have >= minKeysPerNode and <= 363 // maxKeysPerNode. 364 // The root node is allowed to have < minKeysPerNode 365 // (but not zero). 366 enum int minKeys = minDegree - 1; 367 enum int maxKeys = 2 * minDegree - 1; 368 enum int minChildren = minDegree; 369 enum int maxChildren = 2 * minDegree; 370 371 alias _less = binaryFun!less; 372 373 Node* _root; 374 size_t _count; 375 376 void checkInvariant() const 377 { 378 int count = 0; 379 if (_root) 380 { 381 assert(_count > 0); 382 checkNode(_root, null, count); 383 } 384 else 385 { 386 assert(_count == 0); // No items <=> null _root 387 } 388 assert(count == _count); 389 } 390 391 // null if not found 392 inout(KeyValue)* findKeyValue(inout(Node)* x, K key) 393 inout 394 { 395 int index; 396 inout(Node)* node = findNode(x, key, index); 397 if (node is null) 398 return null; 399 else 400 return &(node.kv[index]); 401 } 402 403 // Return containing node + index of value in store, or 404 // null if not found. 405 inout(Node)* findNode(inout(Node)* x, K key, 406 out int index) inout 407 { 408 int i = 0; 409 while (i < x.numKeys && _less(x.kv[i].key, key)) 410 i += 1; 411 412 // Like in Phobos' Red Black tree, this use: 413 // !less(a,b) && !less(b,a) instead of opEquals. 414 415 if (i < x.numKeys && !_less(key, x.kv[i].key)) 416 { 417 index = i; 418 return x; 419 } 420 else 421 { 422 if (x.isLeaf) 423 return null; 424 else 425 return findNode(x.children[i], key, index); 426 } 427 } 428 429 // Create root node if none. 430 void lazyInitialize() 431 { 432 if (_root) 433 return; 434 435 _root = allocateNode; 436 _root.isLeaf = true; 437 _root.numKeys = 0; 438 _root.parent = null; 439 } 440 441 void insertNonFull(Node* x, K key, V value) 442 { 443 int i = x.numKeys - 1; 444 if (x.isLeaf) 445 { 446 while (i >= 0 && _less(key, x.kv[i].key)) 447 { 448 x.kv[i+1] = x.kv[i]; 449 i -= 1; 450 } 451 x.kv[i+1] = KeyValue(key, value); 452 x.numKeys++; 453 } 454 else 455 { 456 while (i >= 0 && _less(key, x.kv[i].key)) 457 { 458 i -= 1; 459 } 460 i += 1; 461 Node* c = x.children[i]; 462 if (c.isFull) 463 { 464 splitChild(x, i, c); 465 if (_less(x.kv[i].key, key)) 466 { 467 i = i + 1; 468 c = x.children[i]; 469 } 470 } 471 insertNonFull(c, key, value); 472 } 473 } 474 475 // x = a parent with at least one slot available 476 // y = a full node to split 477 void splitChild(Node* x, int i, Node* y) 478 { 479 // create new child, that will take half the 480 // keyvalues and children of the full y node 481 Node* z = allocateNode(); 482 z.isLeaf = y.isLeaf; 483 z.numKeys = minDegree - 1; 484 z.parent = x; 485 486 // copy half of values (highest) in new child 487 for (int j = 0; j < minDegree - 1; ++j) 488 { 489 z.kv[j] = y.kv[minDegree + j]; 490 } 491 492 // same for child pointer if any 493 if (!y.isLeaf) 494 { 495 for (int j = 0; j < minDegree; ++j) 496 { 497 z.children[j] = y.children[minDegree + j]; 498 z.children[j].parent = z; 499 } 500 } 501 502 // Formerly full child now has room again 503 y.numKeys = minDegree - 1; 504 505 // And now for the parent node: 506 // * new child is inserted right of its older 507 // sibling 508 for (int j = x.numKeys; j > i; --j) 509 { 510 x.children[j+1] = x.children[j]; 511 } 512 x.children[i+1] = z; 513 for (int j = x.numKeys - 1; j >= i; --j) 514 { 515 x.kv[j+1] = x.kv[j]; 516 } 517 // * middle key is choosen as pivot 518 x.kv[i] = y.kv[minDegree-1]; 519 x.numKeys += 1; 520 } 521 522 // Take one node that is exactly below capacity, and 523 // reinstate the invariant by merging nodes and 524 // exchanging with neighbours. Is called on nodes that 525 // might be missing exactly one item. 526 void rebalanceAfterDeletion(Node* node) 527 { 528 if (node.parent is null) // is this the tree _root? 529 { 530 assert(_root is node); 531 532 if (_root.numKeys == 0) 533 { 534 if (_root.isLeaf) // no more items in tree 535 { 536 destroyFree(_root); 537 _root = null; 538 } 539 else // tree is reduced by one level 540 { 541 Node* oldRoot = _root; 542 _root = oldRoot.children[0]; 543 _root.parent = null; 544 oldRoot.numKeys = -1; 545 // so that it is not destroyed 546 oldRoot.children[0] = null; 547 destroyFree(oldRoot); // <- here 548 } 549 } 550 return; 551 } 552 553 if (node.numKeys >= minKeys) 554 return; // no balance issue, exit 555 556 // Else, the node is missing one key 557 assert(node.numKeys == minKeys - 1); 558 559 Node* parent = node.parent; 560 assert(parent !is null); 561 562 Node* left; 563 Node* right; 564 int childIndex = -1; 565 for (int n = 0; n < parent.numChildren; ++n) 566 { 567 if (parent.children[n] == node) 568 { 569 childIndex = n; 570 break; 571 } 572 } 573 574 // has left sibling? 575 if (childIndex != 0) 576 left = parent.children[childIndex-1]; 577 578 // has right sibling? 579 if (childIndex + 1 < parent.numChildren) 580 right = parent.children[childIndex+1]; 581 582 assert(left || right); // one of those exists 583 584 if (left && left.numKeys > minKeys) 585 { 586 // Take largest key from left sibling, it 587 // becomes the new pivot in parent. Old pivot 588 // erase our key (and if non-leaf, gets the left 589 // sibling right subtree + the node one child). 590 assert(left.isLeaf == node.isLeaf); 591 KeyValue largest = left.kv[left.numKeys - 1]; 592 Node* rightMost = left.children[left.numKeys]; 593 left.numKeys -= 1; 594 KeyValue pivot = node.parent.kv[childIndex-1]; 595 node.parent.kv[childIndex-1] = largest; 596 597 // Pivot enter at position 0. 598 // Need to shift a few kv. 599 for (int n = minKeys - 1; n > 0; --n) 600 { 601 node.kv[n] = node.kv[n-1]; 602 } 603 node.kv[0] = pivot; 604 if (!node.isLeaf) 605 { 606 for (int n = minKeys; n > 0; --n) 607 { 608 node.children[n] = node.children[n-1]; 609 } 610 node.children[0] = rightMost; 611 rightMost.parent = node; 612 } 613 node.numKeys = minKeys; 614 } 615 else if (right && right.numKeys > minKeys) 616 { 617 // Take smallest key from right sibling, it 618 // becomes the new pivot in parent. Old pivot 619 // erase our key. 620 assert(right.isLeaf == node.isLeaf); 621 KeyValue smallest = right.kv[0]; 622 Node* leftMostChild = right.children[0]; 623 624 // Delete first key (and first child, if any) of 625 // right sibling. 626 if (!node.isLeaf) 627 { 628 for (int n = 0; n < right.numKeys; ++n) 629 right.children[n] = right.children[n+1]; 630 } 631 deleteKeyValueAtIndexAndShift(right, 0); 632 633 KeyValue pivot = parent.kv[childIndex]; 634 parent.kv[childIndex] = smallest; 635 node.kv[minKeys - 1] = pivot; 636 if (!node.isLeaf) 637 { 638 leftMostChild.parent = node; 639 node.children[minKeys] = leftMostChild; 640 } 641 node.numKeys = minKeys; 642 } 643 else 644 { 645 // merge with either left or right 646 if (right) 647 { 648 mergeChild(parent, childIndex); 649 } 650 else if (left) 651 { 652 mergeChild(parent, childIndex - 1); 653 } 654 else 655 assert(0); 656 } 657 } 658 659 // Merge children nth and nth+1, which must both have 660 // min amount of keys. makes one node with max amount of 661 // keys 662 void mergeChild(Node* parent, int nth) 663 { 664 Node* left = parent.children[nth]; 665 Node* right = parent.children[nth+1]; 666 KeyValue pivot = parent.kv[nth]; 667 assert(left.isLeaf == right.isLeaf); 668 669 // One key is missing already 670 assert(left.numKeys + right.numKeys == 2*minKeys-1); 671 672 left.kv[left.numKeys] = pivot; 673 674 for (int n = 0; n < right.numKeys; ++n) 675 { 676 left.kv[left.numKeys + 1 + n] = right.kv[n]; 677 } 678 if (!left.isLeaf) 679 { 680 for (int n = 0; n < right.numKeys+1; ++n) 681 { 682 left.children[left.numKeys + 1 + n] = 683 right.children[n]; 684 assert(right.children[n].parent == right); 685 left.children[left.numKeys + 1 + n].parent = 686 left; 687 } 688 } 689 left.numKeys = 2 * minKeys; 690 691 // in parent, shift all by one 692 parent.numKeys -= 1; 693 for (int n = nth; n < parent.numKeys; ++n) 694 { 695 parent.kv[n] = parent.kv[n+1]; 696 parent.children[n+1] = parent.children[n+2]; 697 } 698 699 // in case the parent has too few items 700 rebalanceAfterDeletion(parent); 701 702 destroyFree(right); 703 } 704 705 // internal use, delete a kv and shift remaining kv 706 void deleteKeyValueAtIndexAndShift(Node* node, 707 int index) 708 { 709 node.numKeys -= 1; 710 for (int n = index; n < node.numKeys; ++n) 711 { 712 node.kv[n] = node.kv[n+1]; 713 } 714 } 715 716 // node invariant 717 void checkNode(const(Node)* node, 718 const(Node)* parent, 719 ref int count) const 720 { 721 // Each node of the tree except the root must 722 // contain at least 723 // `minDegree − 1` keys (and hence must have at 724 // least `minDegree` children if it is not a leaf). 725 if (parent !is null) 726 { 727 assert(node.numKeys >= minKeys); 728 assert(node.numKeys <= maxKeys); 729 730 // parent can't be a leaf 731 assert(!parent.isLeaf); 732 } 733 else 734 { 735 assert(node.numKeys >= 1); 736 } 737 738 assert(parent is node.parent); 739 740 count += node.numKeys; 741 742 if (!node.isLeaf) 743 { 744 // Check child invariants. 745 for (int n = 0; n < node.numChildren(); ++n) 746 { 747 checkNode(node.children[n], node, count); 748 } 749 750 // Check internal key ordering 751 for (int n = 0; n + 1 < node.numKeys; ++n) 752 { 753 const(K) k1 = node.kv[n].key; 754 const(K) k2 = node.kv[n+1].key; 755 static if (allowDuplicates) 756 { 757 assert(! _less(k2, k1)); 758 } 759 else 760 { 761 assert(_less(k1, k2)); 762 } 763 } 764 765 // Check key orderings with children. All keys 766 // of child must be inside parent range. 767 for (int n = 0; n < node.numKeys; ++n) 768 { 769 const(K) k = node.kv[n].key; 770 771 // All key of left children must be smaller, 772 // right must be larger. 773 const(Node)* left = node.children[n]; 774 const(Node)* right = node.children[n+1]; 775 776 for (int m = 0; m < left.numKeys; ++m) 777 { 778 static if (allowDuplicates) 779 assert(! _less(k, left.kv[m].key)); 780 else 781 assert(_less(left.kv[m].key, k)); 782 } 783 784 for (int m = 0; m < right.numKeys; ++m) 785 { 786 static if (allowDuplicates) 787 assert(!_less(right.kv[m].key, k)); 788 else 789 assert(_less(k, right.kv[m].key)); 790 } 791 } 792 } 793 } 794 795 static struct KeyValue 796 { 797 K key; 798 V value; 799 } 800 801 debug(btree) 802 public void display() 803 { 804 printf("Tree has %zu items\n", _count); 805 if (_root) 806 _root.display(); 807 else 808 { 809 printf(" * no root\n"); 810 } 811 } 812 813 Node* allocateNode() 814 { 815 Node* node = mallocNew!Node(); 816 node.treeRef = &this; 817 return node; 818 } 819 820 void deallocateNode(Node* node) 821 { 822 destroyFree!Node(node); 823 } 824 825 826 static struct Node 827 { 828 nothrow @nogc: 829 // Is this a leaf node? 830 bool isLeaf; 831 832 // This node stores: 833 // - numKeys keys, 834 // - and numKeys+1 children. 835 int numKeys; 836 837 // Keys and values together. 838 KeyValue[maxKeys] kv; 839 840 // (borrowed) Parent node. 841 // Is null for the root. 842 Node* parent; 843 844 // (owning) Pointer to child nodes. 845 Node*[maxChildren] children; 846 847 BTree* treeRef; 848 849 /// Number of children = numKeys + 1 850 int numChildren() const 851 { 852 assert(!isLeaf); // leaves have no child 853 return numKeys + 1; 854 } 855 856 bool isFull() const 857 { 858 return numKeys == maxKeys; 859 } 860 861 void reclaimMemory() 862 { 863 if (isLeaf) 864 return; 865 866 for (int c = 0; c < numChildren(); ++c) 867 { 868 children[c].reclaimMemory(); 869 } 870 871 treeRef.deallocateNode(&this); 872 } 873 874 debug(btree) 875 void display() 876 { 877 printf("\nNode %p\n", &this); 878 printf(" * parent = %p\n", parent); 879 printf(" * leaf = %s\n", isLeaf ? "yes".ptr: 880 "no".ptr); 881 printf(" * numKeys = %d\n", numKeys); 882 883 if (numKeys > 0) 884 { 885 for (int v = 0; v < numKeys; ++v) 886 { 887 static if (is(V == string)) 888 printf(" - key %d and value %s\n", 889 kv[v].key, kv[v].value.ptr); 890 else 891 printf(" - key %d\n", kv[v].key); 892 } 893 } 894 if (!isLeaf) 895 { 896 for (int v = 0; v < numKeys+1; ++v) 897 { 898 printf(" - => child %p\n", children[v]); 899 } 900 } 901 printf("\n"); 902 903 if (!isLeaf) 904 { 905 for (int v = 0; v < numKeys+1; ++v) 906 { 907 children[v].display; 908 } 909 } 910 } 911 } 912 913 914 public: 915 916 enum RangeType 917 { 918 key, 919 value, 920 keyValue 921 } 922 923 /// Btree Range 924 static struct BTreeRange(RangeType type) 925 { 926 nothrow @nogc: 927 this(ref BTree tree) 928 { 929 _current = tree._root; 930 if (_current is null) 931 return; 932 while(!_current.isLeaf) 933 _current = _current.children[0]; 934 } 935 936 this(ref const(BTree) tree) 937 { 938 // const_cast here 939 _current = cast(Node*)(tree._root); 940 if (_current is null) 941 return; 942 while(!_current.isLeaf) 943 _current = _current.children[0]; 944 } 945 946 bool empty() const 947 { 948 return _current is null; 949 } 950 951 auto front() 952 { 953 static if (type == RangeType.key) 954 return _current.kv[_curItem].key; 955 static if (type == RangeType.value) 956 return _current.kv[_curItem].value; 957 static if (type == RangeType.keyValue) 958 return _current.kv[_curItem]; 959 } 960 961 void popFront() 962 { 963 // Basically, _current and _curItem points 964 // at a key. 965 // 966 // 3 967 // / \ 968 // 1-2 4-5 969 // 970 // Case 1: increment _curItem 971 // Case 2: increment _curItem, see that 972 // it's out of bounds. 973 // Go up the parent chain, find 974 // position of child. 975 // Repeat if needed 976 // Then point at pivot if any, or exit. 977 // Case 3: See that you're not in a leaf. 978 // Go down to the leftmost leaf of the 979 // right sub-tree. 980 // Start at first item. This one always 981 // exist. 982 // Case 4: same as case 1. 983 // Case 5: same as case 2. 984 985 // If not a leaf, go inside the next children 986 if (!_current.isLeaf) 987 { 988 // Case 3 989 assert(_curItem + 1 < _current.numChildren); 990 _current = _current.children[_curItem+1]; 991 while (!_current.isLeaf) 992 _current = _current.children[0]; 993 _curItem = 0; 994 } 995 else 996 { 997 _curItem += 1; 998 if (_curItem >= _current.numKeys) 999 { 1000 while(true) 1001 { 1002 if (_current.parent is null) 1003 { 1004 // end of iteration 1005 _current = null; 1006 break; 1007 } 1008 else 1009 { 1010 // Find position of child in 1011 // parent 1012 int posParent = -2; 1013 Node* c = _current; 1014 Node* parent = _current.parent; 1015 1016 // Possibly there is a better 1017 // way to do it with a stack 1018 // somewhere. But that would 1019 // require to know the maximum 1020 // level. 1021 // That, or change everything to 1022 // be B+Tree. 1023 size_t N = parent.numChildren(); 1024 for (int n = 0; n < N; ++n) 1025 { 1026 if (parent.children[n] == c) 1027 { 1028 posParent = n; 1029 break; 1030 } 1031 } 1032 1033 // else tree invalid 1034 // not sure why I took -2 1035 assert(posParent != -2); 1036 1037 if (posParent < parent.numKeys) 1038 { 1039 // Point at pivot 1040 _current = parent; 1041 _curItem = posParent; 1042 break; 1043 } 1044 else 1045 { 1046 // continue search upwards 1047 // for a pivot 1048 _current = parent; 1049 } 1050 } 1051 } 1052 } 1053 else 1054 { 1055 // Case 1, nothing to do. 1056 } 1057 } 1058 } 1059 1060 private: 1061 // Next item returned by .front 1062 Node* _current; 1063 int _curItem; 1064 } 1065 } 1066 1067 // <digression> 1068 // 1069 // A short note about B-tree "Knuth order" vs t/minDegree. 1070 // 1071 // t or "minimum degree" ensures an even number of children 1072 // in full nodes. 1073 // 1074 // .----------------+---------------+-------------------. 1075 // | minDegree max | keys per node | children per node | 1076 // |----------------+---------------+-------------------| 1077 // | 2 | 1 to 3 | 2 to 4 | 1078 // | 3 | 2 to 5 | 3 to 6 | 1079 // | 4 | 3 to 7 | 4 to 8 | 1080 // +----------------+---------------+-------------------+ 1081 // 1082 // However Knuth defines that as an "order" m, which can be 1083 // an _odd_ number. 1084 // 1085 // In this case, the possible B-tree item bounds are: 1086 // 1087 // .----------------+---------------+-------------------. 1088 // | m | keys per node | children per node | 1089 // |----------------+---------------+-------------------| 1090 // | 4 | 1 to 3 | 2 to 4 | 1091 // | 5 | 2 to 4 | 3 to 5 | 1092 // | 6 | 2 to 5 | 3 to 6 | 1093 // | 7 | 3 to 6 | 4 to 7 | 1094 // | 8 | 3 to 7 | 4 to 8 | 1095 // +----------------+---------------+-------------------+ 1096 // 1097 // So, while things are similar for even m, they are 1098 // different for odd m and some Internet example use m = 5. 1099 // If we disallow non-even m, then it's impossible to 1100 // It makes a difference in deletion, because two minimally 1101 // filled nodes + a pivot can be merged together if m is 1102 // even. 1103 // 1104 // Our implementation does NOT allow for odd m. 1105 // 1106 // </digression> 1107 1108 unittest // .init testing, basics 1109 { 1110 // It should be possible to use most function of an 1111 // uninitialized BTree. 1112 // All except functions returning a range will work. 1113 BTree!(int, string) m; 1114 1115 assert(m.length == 0); 1116 assert(m.empty); 1117 assert(!m.contains(7)); 1118 m.insert(7, "lol"); 1119 assert(m.contains(7)); 1120 assert(m[7] == "lol"); 1121 m.remove(7); 1122 assert(!m.contains(7)); 1123 assert(m.length == 0); 1124 assert(m.empty); 1125 assert(m._root == null); 1126 } 1127 1128 unittest // add and delete in reverse order 1129 { 1130 enum int N = 10; 1131 for (int G = 0; G < 1; ++G) 1132 { 1133 BTree!(int, string) m; 1134 for (int k = 0; k < N; ++k) 1135 { 1136 m.insert(k, "l"); 1137 } 1138 1139 assert(m.length == N); 1140 for (int k = N-1; k >= 0; --k) 1141 { 1142 assert(1 == m.remove(k)); 1143 } 1144 assert(m.length == 0); 1145 assert(m.empty); 1146 } 1147 } 1148 1149 unittest // dupe keys 1150 { 1151 BTree!(int, int, "a < b", true) m; 1152 enum KEY = 4; 1153 1154 for (int n = 0; n < 32; ++n) 1155 { 1156 m.insert(KEY, 1 << n); 1157 } 1158 1159 foreach(k; m.byKey) 1160 assert(k == KEY); 1161 1162 int r = 0; 1163 for (int n = 0; n < 32; ++n) 1164 { 1165 int* p = KEY in m; 1166 assert(p); 1167 r |= *p; 1168 assert(m.remove(KEY) == 1); 1169 } 1170 assert(r == -1); // all were popped 1171 } 1172 1173 unittest // "in" without initialization 1174 { 1175 BTree!(int, int) m; 1176 void* p = 1 in m; 1177 assert(p is null); 1178 } 1179 1180 unittest 1181 { 1182 BTree!(int, int) m; 1183 m.insert(4, 5); 1184 m.insert(4, 9); 1185 }