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 }