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