1 /// Implementation of a B-Tree based upon "Introduction to Algorithms". 2 module s2.util.container.btree; 3 4 import std.algorithm : max; 5 import std.functional : binaryFun; 6 import std.traits : ReturnType; 7 import std.format : format; 8 9 10 /** 11 A B-Tree implementation based upon "Introduction to Algorithms" by Cormen, Leiserson, Rivest, 12 and Stein. 13 14 B-Trees are both smaller and faster than most implementations of set/map. The red-black tree 15 implementation of a set/map typically has an overhead of 3 pointers (left, right, and parent) 16 plus the node color information for each stored value. This B-Tree implementation stores 17 multiple values on fixed size nodes (usually 256 bytes) and does not store child pointers for 18 leaf nodes. 19 20 The packing of multiple values into each node of a B-Tree also improves cache locality which 21 translates into faster operations. 22 23 Because nodes contain both node pointers and values, a BTree will not have good performance 24 when used with large types that are passed by value, such as large structs. 25 26 As always, using the BTree with class objects, which are passed by reference, also avoids 27 storage in the BTree itself. 28 29 Params: 30 ValueT = The element type to be organized by the BTree. 31 NodeSizeV = The size in bytes of a node in the BinaryTree. Values are chosen to optimize 32 performance depending on usage. For example, a value of 4096 bytes may be useful when 33 retrieving items from disk, or the value 256 bytes to assure good use of CPU caches. 34 The default value is 256 bytes. 35 ValueLessF = A less-than comparison function for values, either a function or a string 36 representing a function as described by 37 $(LINK2 https://dlang.org/phobos/std_functional.html#binaryFun, std.functional.binaryFun). 38 */ 39 final class BTree(ValueT, size_t NodeSizeV = 256, alias ValueLessF = "a < b") 40 if (is(typeof(binaryFun!ValueLessF(ValueT.init, ValueT.init)) : bool)) { 41 42 private: 43 Node _root; 44 size_t _length; 45 46 alias _isValueLess = binaryFun!ValueLessF; 47 48 /// Wrapper for ValueLessF that will fail at compile time if its signature does not match. 49 static bool _isValueEqual(in ValueT v1, in ValueT v2) { 50 return !_isValueLess(v1, v2) && !_isValueLess(v2, v1); 51 } 52 53 /** 54 Gets the minimum degree of the B-Tree. 55 56 The B-Tree is defined by a minimum degree "t". 57 Each node other than the root must have at least t-1 values. 58 Each node may have up to 2t - 1 values, thus an internal node may have up to 2*t children. 59 60 Thus, the maximum size of a non-leaf node is: 61 - [values] (2t-1) * (size of a value) 62 - [numValues] + 1 * (size of size_t) 63 - [isLeaf] + 1 * (size of a bool) 64 - [children] + 2*t * (size of pointer to a node) 65 - = NodeSize 66 */ 67 static size_t getMinDegree() @nogc @safe pure nothrow { 68 size_t nodeSizeBase = bool.sizeof + Node.sizeof + size_t.sizeof + size_t.sizeof; 69 size_t nodeExtraSizePerChild = ValueT.sizeof + Node.sizeof; 70 71 if (NodeSizeV < nodeSizeBase) { 72 return 1; 73 } 74 size_t maxChildren = (NodeSizeV - nodeSizeBase) / nodeExtraSizePerChild; 75 if (maxChildren < 2) { 76 return 1; 77 } else { 78 return maxChildren / 2; 79 } 80 } 81 82 public: 83 84 enum MIN_DEGREE = getMinDegree(); 85 enum MAX_DEGREE = MIN_DEGREE * 2; 86 87 this() { 88 // TODO: ALLOCATE-NODE() which allocates a disk page in O(1). 89 _root = new Node(); 90 _length = 0; 91 // TODO: DISK-WRITE(_root) 92 } 93 94 /// The root Node of the B-Tree. 95 @property 96 inout(Node) root() inout { 97 return _root; 98 } 99 100 @property 101 size_t length() const { 102 return _length; 103 } 104 105 @property 106 bool empty() const { 107 return _length == 0; 108 } 109 110 //// 111 // Iterator Style Methods 112 //// 113 114 Iterator begin() { 115 return Iterator(_root.leftmost(), 0); 116 } 117 118 Iterator end() { 119 Node right = _root.rightmost(); 120 return Iterator(right, right.numValues()); 121 } 122 123 //// 124 // Range Style Methods 125 //// 126 127 /// A full slice for expressions of the form: myBTree[]. 128 Range opIndex() { 129 return Range(begin(), end()); 130 } 131 132 /// Defines the structure of a slice for slice expression of the form: myBTree[a..b]. 133 ValueT[2] opSlice(size_t dim)(ValueT a, ValueT b) { 134 return [a, b]; 135 } 136 137 Range opIndex(ValueT[2] slice) { 138 return Range(_root.findFirstGE(slice[0]), _root.findFirstGE(slice[1])); 139 } 140 141 /** 142 Find a range of elements whose value is equal to 'v'. 143 144 If no elements are found, the result will be an empty range. If there is more than one 145 element with the same value, the range will have more than 1 element. 146 */ 147 Range equalRange(ValueT v) { 148 return Range(_root.findFirstGE(v), _root.findFirstGT(v)); 149 } 150 151 /// Returns a range of all elements strictly less than 'v'. 152 Range lowerRange(ValueT v) { 153 return Range(begin(), _root.findFirstGE(v)); 154 } 155 156 /// Returns a range of all elements strictly greater than 'v'. 157 Range upperRange(ValueT v) { 158 return Range(_root.findFirstGT(v), end()); 159 } 160 161 //// 162 // Core BTree algorithms 163 //// 164 165 void clear() { 166 _root = new Node(); 167 _length = 0; 168 } 169 170 /// Inserts a new value into the BTree. 171 void insert(ValueT v) { 172 _length++; 173 Node curRoot = _root; 174 if (curRoot.isFull()) { 175 Node newRoot = new Node(); 176 newRoot._isLeaf = false; 177 newRoot._numValues = 0; 178 newRoot._parent = null; 179 newRoot.setChild(0, _root); 180 _root = newRoot; 181 _root.splitChild(0); 182 } 183 _root.insertNonFull(v); 184 } 185 186 /// Implements the "in" operator for membership. 187 bool opBinaryRight(string op)(ValueT value) 188 if (op == "in") { 189 return !equalRange(value).empty(); 190 } 191 192 /// Removes a value and value from the BTree if it exists. 193 void remove(ValueT v) { 194 bool isFound = _root.remove(v); 195 if (isFound) { 196 _length--; 197 } 198 if (!_root._isLeaf && _root._numValues == 0) { 199 _root = _root._children[0]; 200 _root._parent = null; 201 } 202 } 203 204 /** 205 Lower level iterators on the tree. 206 207 While not in the D-style, these methods may be used for easier compatibility when working 208 with code bases not in the D Language which are based on iterators. 209 */ 210 static struct Iterator { 211 private: 212 Node _node; 213 size_t _position; 214 public: 215 216 bool isFound() { 217 return _node !is null; 218 } 219 220 inout(ValueT) getValue() inout { 221 return _node.getValue(_position); 222 } 223 224 inout(ValueT) opUnary(string op)() inout 225 if (op == "*") { 226 return getValue(); 227 } 228 229 void increment() { 230 if (_node.isLeaf() && ++_position < _node.numValues()) { 231 return; 232 } 233 // We've gone past the last position. 234 if (_node.isLeaf()) { 235 Iterator save = this; 236 while (_position == _node.numValues() && !_node.isRoot()) { 237 _position = _node._position; 238 _node = _node._parent; 239 } 240 if (_position == _node.numValues()) { 241 this = save; 242 } 243 } else { 244 _node = _node._children[_position + 1]; 245 while (!_node.isLeaf()) { 246 _node = _node._children[0]; 247 } 248 _position = 0; 249 } 250 } 251 252 void opUnary(string op)() 253 if (op == "++") { 254 increment(); 255 } 256 257 void decrement() { 258 if (_node.isLeaf() && _position > 0) { 259 _position--; 260 return; 261 } 262 if (_node.isLeaf()) { 263 Iterator save = this; 264 while (_position == 0 && !_node.isRoot()) { 265 _position = _node._position; 266 _node = _node._parent; 267 } 268 _position--; 269 if (_position < 0) { 270 this = save; 271 } 272 } else { 273 _node = _node._children[_position]; 274 while (!_node.isLeaf()) { 275 _node = _node._children[_node._numValues]; 276 } 277 _position = _node._numValues - 1; 278 } 279 } 280 281 void opUnary(string op)() 282 if (op == "--") { 283 decrement(); 284 } 285 } 286 287 /// D-style Ranges. 288 static struct Range { 289 private: 290 Iterator _begin; 291 Iterator _end; 292 293 public: 294 bool empty() { 295 return _begin == _end; 296 } 297 298 inout(ValueT) front() inout { 299 return _begin.getValue(); 300 } 301 302 void popFront() 303 in { 304 assert(!empty()); 305 } do { 306 _begin.increment(); 307 } 308 309 Iterator toIterator() { 310 return _begin; 311 } 312 } 313 314 /** 315 A node in the BTree. 316 317 A notable feature is that the values that are inserted are stored inside the BTree itself 318 in the case of value data types, such as integers, floats, static arrays, or structs. In 319 other cases, such as dynamic arrays and classes, on the reference is stored. 320 */ 321 static class Node { 322 package: 323 /// Indicates if the node has reached the size limit specified in $(D_INLINECODE NodeSizeV). 324 bool isFull() { 325 return _numValues >= MAX_DEGREE - 1; 326 } 327 328 void setChild(size_t i, Node child) 329 in { 330 assert(!_isLeaf); 331 assert(i >= 0); 332 } do { 333 _children[i] = child; 334 _children[i]._position = i; 335 _children[i]._parent = this; 336 } 337 338 Node leftmost() { 339 Node seek = this; 340 while (!seek._isLeaf) { 341 seek = seek._children[0]; 342 } 343 return seek; 344 } 345 346 Node rightmost() { 347 Node seek = this; 348 while (!seek._isLeaf) { 349 seek = seek._children[seek._numValues]; 350 } 351 return seek; 352 } 353 354 package: 355 /// A flag indicating whether the node is a leaf node or not. 356 bool _isLeaf = true; 357 358 /// A reference to the node's parent. 359 Node _parent = null; 360 361 /// The position of the node in the node's parent. 362 size_t _position = 0; 363 364 /// A count of the number of values in the node. 365 size_t _numValues = 0; 366 ValueT[MAX_DEGREE - 1] _values; 367 368 /// Only non-leaf (internal) nodes have children. 369 Node[MAX_DEGREE] _children; 370 371 invariant { 372 // Assure that the number of values stays within the bounds of the MAX_DEGREE. 373 assert(_numValues <= MAX_DEGREE - 1); 374 375 // Assure that values remain in a consistent order. 376 foreach (i; 1 .. _numValues) { 377 assert(!_isValueLess(_values[i], _values[i - 1])); 378 } 379 380 // Assure that no child that should be present is null. 381 if (!_isLeaf) { 382 foreach (i; 0 .. _numValues + 1) { 383 assert(_children[i] !is null); 384 assert(_children[i]._parent is this); 385 assert( 386 _children[i]._position == i, 387 format("Found position %d, expected %d in child with values %s. _numValues=%d", 388 _children[i]._position, i, _children[i]._values, _numValues)); 389 } 390 } 391 } 392 393 public: 394 /** 395 Given that this node is non-full, but a child node that is, split the child node into two 396 separate nodes that are half full, and insert a value into this node between them. 397 */ 398 void splitChild(size_t i) 399 in { 400 assert(!isFull(), "this = " ~ toString()); 401 assert(_children[i].isFull(), format("_children[%d] = %s", i, _children[i].toString())); 402 } out { 403 assert(_children[i]._numValues == MIN_DEGREE - 1); 404 assert(_children[i+1]._numValues == MIN_DEGREE - 1); 405 } do { 406 Node toSplitNode = _children[i]; 407 408 // Prepare a new node containing the right half of the node at _children[i]. 409 // TODO: ALLOCATE-NODE(newNode) 410 Node newNode = new Node(); 411 newNode._parent = this; 412 newNode._isLeaf = toSplitNode._isLeaf; 413 newNode._numValues = MIN_DEGREE - 1; 414 foreach (j; 0 .. MIN_DEGREE - 1) { 415 newNode._values[j] = toSplitNode._values[j + MIN_DEGREE]; 416 } 417 if (!toSplitNode._isLeaf) { 418 foreach (j; 0 .. MIN_DEGREE) { 419 newNode.setChild(j, toSplitNode._children[j + MIN_DEGREE]); 420 } 421 } 422 423 // Make way for the new value added at position i. 424 for (auto j = _numValues; j > i; j--) { 425 _values[j] = _values[j - 1]; 426 } 427 _values[i] = toSplitNode._values[MIN_DEGREE - 1]; 428 _numValues++; 429 430 // Make way for a newNode to be added at position i + 1. 431 // There can be up to _numValues + 1 children. 432 for (auto j = _numValues; j > i + 1; j--) { 433 setChild(j, _children[j - 1]); 434 } 435 setChild(i + 1, newNode); 436 437 // Reduce the size of values in the node being split, cutting it in half. 438 toSplitNode._numValues = MIN_DEGREE - 1; 439 440 // TODO: DISK-WRITE(toSplitNode) 441 // TODO: DISK-WRITE(newNode) 442 // TODO: DISK-WRITE(this) 443 } 444 445 /// Inserts a new value into the BTree, provided that this node is not already full. 446 void insertNonFull(ValueT v) 447 in { 448 assert(!isFull()); 449 } do { 450 int i = cast(int) _numValues - 1; 451 if (_isLeaf) { 452 // Shift over the values to make room. 453 for (; i >= 0 && _isValueLess(v, _values[i]); i--) { 454 _values[i + 1] = _values[i]; 455 } 456 _values[i + 1] = v; 457 _numValues++; 458 // TODO: DISK-WRITE(this) 459 } else { 460 // Find the index of the first value less than the inserted value. 461 for (; i >= 0 && _isValueLess(v, _values[i]); i--) {} 462 // The child to insert into is one past the value's index, which may be -1. 463 // v[0] v[1] v[2] 464 // c[0] c[1] c[2] c[3] 465 i++; 466 // TODO: DISK-READ(_children[i]) 467 if (_children[i].isFull()) { 468 splitChild(i); 469 // After splitting, the median value of the child is not in this node at index i. 470 if (_isValueLess(_values[i], v)) { 471 i++; 472 } 473 } 474 _children[i].insertNonFull(v); 475 } 476 } 477 478 size_t findFirstGEIndex(ValueT v) const { 479 static if (MIN_DEGREE < 16) { 480 // If degree is small, use a simple linear search. 481 size_t i = 0; 482 while (i < _numValues && _isValueLess(_values[i], v)) { 483 i++; 484 } 485 return i; 486 } else { 487 // If degree is higher, use a binary search. 488 size_t i = 0; 489 size_t j = _numValues; 490 while (i < j) { 491 size_t mid = (i + j) / 2; 492 const(ValueT) midValue = _values[mid]; 493 if ((mid == 0 || _isValueLess(_values[mid - 1], v)) && !_isValueLess(midValue, v)) { 494 return mid; 495 } else if (!_isValueLess(midValue, v)) { 496 j = mid; 497 } else { 498 i = mid + 1; 499 } 500 } 501 return _numValues; 502 } 503 } 504 505 size_t findFirstGTIndex(ValueT v) const { 506 static if (MIN_DEGREE < 16) { 507 // If degree is small, use a simple linear search. 508 size_t i = 0; 509 while (i < _numValues && !_isValueLess(v, _values[i])) { 510 i++; 511 } 512 return i; 513 } else { 514 // If degree is higher, use a binary search. 515 size_t i = 0; 516 size_t j = _numValues; 517 while (i < j) { 518 size_t mid = (i + j) / 2; 519 const(ValueT) midValue = _values[mid]; 520 if ((mid == 0 || !_isValueLess(v, _values[mid - 1])) && _isValueLess(v, midValue)) { 521 return mid; 522 } else if (_isValueLess(v, midValue)) { 523 j = mid; 524 } else { 525 i = mid + 1; 526 } 527 } 528 return _numValues; 529 } 530 } 531 532 bool isRoot() { 533 return _parent is null; 534 } 535 536 bool isLeaf() { 537 return _isLeaf; 538 } 539 540 Node getChild(size_t i) 541 in { 542 assert(!_isLeaf); 543 assert(i >= 0); 544 assert(i <= _numValues); 545 } do { 546 return _children[i]; 547 } 548 549 size_t numChildren() const { 550 return _isLeaf ? 0 : _numValues + 1; 551 } 552 553 ValueT[] getValues() { 554 return _values[0 .. _numValues]; 555 } 556 557 bool remove(ValueT v) { 558 size_t i = findFirstGEIndex(v); 559 // 1. If the value v is in this node and this is a leaf, delete the value from this. 560 if (_isLeaf) { 561 if (i != _numValues && _isValueEqual(v, _values[i])) { 562 foreach (j; i .. _numValues - 1) { 563 _values[j] = _values[j+1]; 564 } 565 _numValues--; 566 return true; 567 } 568 // Otherwise the value was not in the BTree. 569 } 570 // 2. If the value v is in this node and this is an internal node: 571 else if (i != _numValues && _isValueEqual(v, _values[i])) { 572 // 2a. If child y that precedes v in this node has at least t values, then find the 573 // predecessor v' of v in the subtree rooted at y. Recursively delete v' and replace 574 // v by v' in this. 575 if (_children[i]._numValues >= MIN_DEGREE) { 576 Node predecessorNode = _children[i].rightmost(); 577 ValueT predecessor = predecessorNode._values[predecessorNode._numValues - 1]; 578 _values[i] = predecessor; 579 _children[i].remove(predecessor); 580 } 581 // 2b. If child z that follows v in this node has at least t values, then find the 582 // successor v' of v in the subtree rooted at z. Recursively delete v', and replace 583 // v by v' in this. 584 else if (_children[i+1]._numValues >= MIN_DEGREE) { 585 ValueT successor = _children[i+1].leftmost()._values[0]; 586 _values[i] = successor; 587 _children[i+1].remove(successor); 588 } 589 // 2c. Otherwise, if both y and z have only t-1 values, merge v and all of z into y, 590 // so that x loses both v and the pointer to z, and y now contains 2t-1 values. 591 // Then, free z and recursively delete v from y. 592 else { 593 Node y = _children[i]; 594 Node z = _children[i + 1]; 595 // Add v and z to y. 596 y._values[y._numValues++] = _values[i]; 597 foreach (j; 0 .. z._numValues) { 598 y._values[y._numValues + j] = z._values[j]; 599 } 600 // Note: All leaves have the same depth in a BTree. 601 assert(y._isLeaf == z._isLeaf); 602 if (!y._isLeaf) { 603 foreach (j; 0 .. z._numValues + 1) { 604 y.setChild(y._numValues + j, z._children[j]); 605 } 606 } 607 y._numValues += z._numValues; 608 609 // Remove v and z from this. 610 foreach (j; i .. _numValues - 1) { 611 _values[j] = _values[j + 1]; 612 } 613 foreach (j; i + 1 .. _numValues) { 614 setChild(j, _children[j + 1]); 615 } 616 _numValues--; 617 618 // Recursively remove v from y. 619 y.remove(v); 620 } 621 return true; 622 } 623 // 3. The internal node does not have the value, but maybe a child does. 624 else if (!_isLeaf) { 625 // Assure that the child to descend to has at least MIN_DEGREE values. 626 // If not, it needs to be adjusted. 627 if (_children[i]._numValues == MIN_DEGREE - 1) { 628 // 3a1. If _children[i] has a sibling with at least MIN_DEGREE values, move one value 629 // into this, and one one of this's values into _children. 630 // Handle if the right sibling has an extra value. 631 if (i < _numValues && _children[i + 1]._numValues >= MIN_DEGREE) { 632 Node y = _children[i]; 633 Node z = _children[i + 1]; 634 y._values[y._numValues] = _values[i]; 635 y._numValues++; 636 637 _values[i] = z._values[0]; 638 foreach (j; 0 .. z._numValues - 1) { 639 z._values[j] = z._values[j + 1]; 640 } 641 642 if (!y._isLeaf) { 643 y.setChild(y._numValues, z._children[0]); 644 foreach (j; 0 .. z._numValues) { 645 z.setChild(j, z._children[j + 1]); 646 } 647 } 648 z._numValues--; 649 } 650 // 3a2. Handle if the left sibling has an extra value. 651 else if (i > 0 && _children[i - 1]._numValues >= MIN_DEGREE) { 652 Node x = _children[i - 1]; 653 Node y = _children[i]; 654 655 // Make room for 1 more value at the start of y. 656 for (auto j = y._numValues; j > 0; j--) { 657 y._values[j] = y._values[j - 1]; 658 } 659 y._values[0] = _values[i - 1]; 660 _values[i - 1] = x._values[x._numValues - 1]; 661 662 if (!x._isLeaf) { 663 for (auto j = y._numValues + 1; j > 0; j--) { 664 y.setChild(j, y._children[j - 1]); 665 } 666 y.setChild(0, x._children[x._numValues]); 667 } 668 669 y._numValues++; 670 x._numValues--; 671 } 672 // 3b. If both siblings of the child that may have the value are of size 673 // MIN_DEGREE - 1, then merge the child with one of those siblings, merging 674 // a value from this which becomes the median node. 675 else if ((i == _numValues || _children[i + 1]._numValues == MIN_DEGREE - 1) 676 && (_children[i]._numValues == MIN_DEGREE - 1)) { 677 // 3b1. First try to merge with the right node. 678 if (i < _numValues) { 679 Node y = _children[i]; 680 Node z = _children[i + 1]; 681 // Add the value from this into y. 682 y._values[y._numValues] = _values[i]; 683 y._numValues++; 684 // Merge the values and values from z into y also. 685 foreach (j; 0 .. z._numValues) { 686 y._values[y._numValues + j] = z._values[j]; 687 } 688 if (!y._isLeaf) { 689 foreach (j; 0 .. z._numValues + 1) { 690 y.setChild(y._numValues + j, z._children[j]); 691 } 692 } 693 y._numValues += z._numValues; 694 695 // Now remove _values[i] and _children[i+1] from this. 696 foreach (j; i .. _numValues - 1) { 697 _values[j] = _values[j + 1]; 698 } 699 foreach (j; i + 1 .. _numValues) { 700 setChild(j, _children[j + 1]); 701 } 702 _numValues--; 703 } 704 // 3b2. Otherwise merge with the left node. 705 else { 706 Node x = _children[i - 1]; 707 Node y = _children[i]; 708 709 // Move the value to the left of y into x. 710 x._values[x._numValues] = _values[i - 1]; 711 x._numValues++; 712 // Now merge y into x. 713 foreach (j; 0 .. y._numValues) { 714 x._values[x._numValues + j] = y._values[j]; 715 } 716 if (!x._isLeaf) { 717 foreach (j; 0 .. y._numValues + 1) { 718 x.setChild(x._numValues + j, y._children[j]); 719 } 720 } 721 x._numValues += y._numValues; 722 723 // Erase the value from this that was merged into x. 724 foreach (j; i - 1 .. _numValues - 1) { 725 _values[j] = _values[j + 1]; 726 } 727 foreach (j; i .. _numValues) { 728 setChild(j, _children[j + 1]); 729 } 730 _numValues--; 731 i--; 732 } 733 } 734 } 735 return _children[i].remove(v); 736 } 737 return false; 738 } 739 740 override 741 string toString() { 742 return format("[isLeaf=%d, numValues=%d, position=%d]", _isLeaf, _numValues, _position); 743 } 744 745 /// Indicates how many values are in this node. 746 size_t numValues() const { 747 return _numValues; 748 } 749 750 /// Retrieves a values stored in this node. 751 inout(ValueT) getValue(size_t i) inout 752 in { 753 assert(i >= 0); 754 assert(i < _numValues, format("Value at %d is beyond numValues %d.", i, _numValues)); 755 } do { 756 return _values[i]; 757 } 758 759 /** 760 Recursively search for a given value and produce a result indicating whether a match 761 was found and what it's value is. 762 */ 763 Iterator findFirstGE(ValueT v) { 764 size_t i = findFirstGEIndex(v); 765 if (i < _numValues && _isValueEqual(_values[i], v)) { 766 return Iterator(this, i); 767 } 768 if (_isLeaf) { 769 // The index 'i' is one past the end. 770 auto iterator = Iterator(this, i-1); 771 iterator.increment(); 772 return iterator; 773 } else { 774 return _children[i].findFirstGE(v); 775 } 776 } 777 778 /** 779 Recursively search for a given value and produce a result indicating whether a match 780 was found and what it's value is. 781 */ 782 Iterator findFirstGT(ValueT v) { 783 size_t i = findFirstGTIndex(v); 784 785 // As a leaf, no further processing is possible. 786 if (_isLeaf) { 787 return Iterator(this, i); 788 } 789 790 // We have an index with the first greater-than value, but was there a lower value in 791 // one of the node's children? 792 Iterator childIterator = _children[i].findFirstGT(v); 793 if (i < _numValues && 794 childIterator._position == childIterator._node.numValues()) { 795 // The value found in this node was less than what could be found in a child node. 796 return Iterator(this, i); 797 } else { 798 return childIterator; 799 } 800 } 801 } 802 } 803 804 /// Simple use case with primitive types. 805 unittest { 806 auto btree = new BTree!int(); 807 btree.insert(2); 808 btree.insert(3); 809 btree.insert(4); 810 btree.insert(1); 811 btree.insert(4); 812 813 // equalRange() provides a range used to get the tree values equal to the given search value. 814 auto r = btree.equalRange(2); 815 assert(!r.empty()); 816 assert(r.front() == 2); 817 818 // There can be more than one match. 819 r = btree.equalRange(4); 820 assert(!r.empty()); 821 assert(r.front() == 4); 822 r.popFront(); 823 assert(!r.empty()); 824 assert(r.front() == 4); 825 826 // If there is not match, an empty range is returned. 827 r = btree.equalRange(-3); 828 assert(r.empty()); 829 } 830 831 /// Use case using comparison by a specific value. 832 unittest { 833 struct Structo { 834 int _id; 835 string _data; 836 } 837 838 // Organize the BTree using the _id field as the value used for comparison. 839 auto btree = new BTree!(Structo, 1024, "a._id < b._id"); 840 btree.insert(Structo(1, "Good Day")); 841 btree.insert(Structo(2, "Guten Tag")); 842 btree.insert(Structo(3, "G'Day Mate")); 843 assert(!btree.equalRange(Structo(2)).empty()); 844 assert(btree.equalRange(Structo(4)).empty()); 845 assert(btree.equalRange(Structo(3)).front()._data == "G'Day Mate"); 846 } 847 848 /// Use case using comparison by a specific value. 849 unittest { 850 struct Structo { 851 int _id; 852 string _data; 853 } 854 855 // This time use the string _data field, but only the first two characters. 856 auto btree2 = new BTree!(Structo, 1024, "a._data[0..2] > b._data[0..2]"); 857 858 // Lambdas may also be used. 859 auto btree3 = new BTree!( 860 Structo, // The type of thing being stored in the BTree. 861 1024, // The size of a node in bytes. 862 (a, b) => a._data[0..2] > b._data[0..2]); // Determine if one value is less than another. 863 864 btree3.insert(Structo(1, "RW-Fish")); 865 btree3.insert(Structo(2, "LG-Sheep")); 866 btree3.insert(Structo(3, "BK-Bunny")); 867 assert(!btree3.equalRange(Structo(0, "RW")).empty()); 868 assert(btree3.equalRange(Structo(0, "ZM")).empty()); 869 assert(btree3.equalRange(Structo(0, "BK")).front()._data == "BK-Bunny"); 870 } 871 872 /// Use case compatible with class comparing operator overrides: 873 unittest { 874 class Thingy { 875 int _a, _b; 876 877 this(int a, int b) { 878 _a = a; 879 _b = b; 880 } 881 882 int opCmp(in Thingy o) const { 883 return _a < o._a ? -1 884 : _a > o._a ? 1 885 : _b < o._b ? -1 886 : _b > o._b ? 1 887 : 0; 888 } 889 890 override 891 bool opEquals(in Object o) const { 892 Thingy other = cast(Thingy) o; 893 if (other is null) return false; 894 return _a == other._a && _b == other._b; 895 } 896 } 897 898 auto btree = new BTree!Thingy(); 899 btree.insert(new Thingy(1, 2)); 900 btree.insert(new Thingy(1, 3)); 901 btree.insert(new Thingy(2, 1)); 902 btree.insert(new Thingy(1, 2)); 903 904 assert(!btree.equalRange(new Thingy(2, 1)).empty()); 905 assert(btree.equalRange(new Thingy(2, 2)).empty()); 906 }