1 /** Tries and Prefix Trees.
2 
3     Implementation is layered:
4     $(UL
5     $(LI `RawRadixTree` stores its untyped keys as variable length ubyte-strings (`UKey`))
6     $(LI On top of that `RadixTree` implements typed-key access via its template parameter `Key`.)
7     )
8     Both layers currently support
9     $(UL
10     $(LI template parameterization on the `Value`-type in the map case (when `Value` is non-`void`))
11     $(LI `@nogc` and, when possible, `@safe pure nothrow`)
12     $(LI insertion with returned modifications status: `auto newKeyWasInserted = set.insert(Key key)`)
13     $(LI AA-style `in`-operator)
14       $(UL
15       $(LI `key in set` is `bool` for set-case)
16       $(LI `key in map` returns non-`null` `value` pointer when `key` is stored in `map`)
17       )
18     $(LI AA-style iteration of map keys: `map.byKey()`)
19     $(LI AA-style iteration of map values: `map.byValue()`)
20     $(LI `SortedRange`-style iteration over elements sorted by key: `assert(set[].isSorted)`)
21     $(LI containment checking: `bool contains(in Key key)`)
22     $(LI element indexing and element index assignment for map case via `opIndex` and `opIndexAssign`)
23     $(LI key-prefix Completion (returning a `Range` over all set/map-elements that match a key prefix): `prefix(Key keyPrefix)`)
24     )
25     Typed layer supports
26     $(UL
27     $(LI ordered access of aggregate types)
28     )
29 
30     See_Also: $(HTTP en.wikipedia.org/wiki/Trie)
31     See_Also: $(HTTP en.wikipedia.org/wiki/Radix_tree)
32     See_Also: $(HTTP github.com/nordlow/phobos-next/blob/master/src/test_trie_prefix.d) for a descriptive usage of prefixed access.
33 
34     TODO split up file into raw_trie.d, trie.d
35 
36     TODO use fast bit-scanning functions in core.bitop for DenseBranch and
37     DenseLeaf iterators
38 
39     TODO shift addresses of class and pointers by its alignment before adding
40     them to make top-branch as dense possible
41 
42     TODO Allow slicing from non-mutable tries and add test-case at line 5300
43 
44     TODO 2. Make as many members as possible free functionss free-functions to
45     reduce compilation times. Alternative make them templates (`template-lazy` )
46 
47     TODO Use scope on `Range`, `RawRange` and members that return key and value
48     reference when DIP-1000 has been implemented
49 
50     TODO Encode `string` with zero-terminating 0 byte when it's not the last
51     member in a key aggregate
52 
53     TODO
54 
55     Replace
56     case undefined: return typeof(return).init; // terminate recursion
57     with
58     case undefined: return curr;
59 
60     TODO:
61     TODO Make `Key` and Ix[]-array of `immutable Ix` like `string`
62 
63     TODO Allow `Node`-constructors to take const and immutable prefixes and then
64     make `toRawKey` and `toTypedKey` accept return const-slices
65 
66     TODO Remove @trusted from VLA (variable length array)-members of SparseBranch/SparseLeaf and make their callers @trusted instead.
67 
68     TODO Assure that ~this() is run for argument `nt` in `freeNode`. Can we use `postblit()` for this?
69 
70     TODO Search for "functionize this loop or reuse memmove" and use move()
71 
72     TODO Add Branch-hint allocation flag and re-benchmark construction of `radixTreeSet` with 10000000 uints
73 
74     TODO Add sortedness to `IxsN` and make `IxsN.contains()` use `binarySearch()`. Make use of `sortn`.
75 
76     TODO Check for case when expanding to bit-branch instead of `SparseBranch` in all `expand()` overloads
77 
78     TODO Make array indexing/slicing as @trusted and use .ptr[] instead of [] when things are stable.
79 
80     TODO Add various extra packings in MixLeaf1to4: number of
81     - Ix  (0,1,2,3,4,5,6): 3-bits
82     - Ix2 (0,1,2,3): 2-bits
83     - Ix3 (0,1,2): 2-bits
84     - Ix4 (0,1): 1-bit
85     Total bits 8-bits
86     Possible packings with 6 bytes
87     - 4_
88     - 4_2
89     - 4_2
90     - 4_2_1
91     - 4_1
92     - 4_1_1
93     - 3_2
94     - 3_2_1
95     - 3_1
96     - 3_1_1
97     - 3_1_1_1
98     - 2_2_2
99     - 2_2
100     - 2_2_1
101     - 2_2_1_1
102     - 2
103     - 2_1
104     - 2_1_1
105     - 2_1_1_1
106     - 2_1_1_1_1
107     - 1
108     - 1_1
109     - 1_1_1
110     - 1_1_1_1
111     - 1_1_1_1_1
112     - 1_1_1_1_1_1
113 
114     TODO Sorted Range Primitives over Keys
115 
116     - Returns a range of elements which are equivalent (though not necessarily equal) to value.
117       auto equalRange(this This)(inout T value)
118 
119     - Returns a range of elements which are greater than low and smaller than highValue.
120       auto bound(this This)(inout T lowValue, inout T highValue)
121 
122     - Returns a range of elements which are less than value.
123       auto lowerBound(this This)(inout T value)
124 
125     - Returns a range of elements which are greater than value.
126       auto upperBound(this This)(inout T value)
127 
128     TODO opBinaryRight shall return `_rawTree.ElementRef` instead of `bool`
129 
130     TODO Fix vla-allocations in constructVariableSizeNode according
131     C11-recommendations. For reference set commit
132     d2f1971dd570439da4198fa76603b53b072060f8 at
133     https://github.com/emacs-mirror/emacs.git
134 */
135 module nxt.trie;
136 
137 import std.algorithm.mutation : move;
138 import std.algorithm.comparison : min, max;
139 import std.traits : isSomeString, isArray, isPointer, Unqual;
140 import std.range.primitives : isInputRange, ElementType, hasLength;
141 
142 import nxt.bijections : isIntegralBijectableType, bijectToUnsigned, bijectFromUnsigned;
143 import nxt.variant_ex : WordVariant;
144 import nxt.typecons_ex : IndexedBy;
145 import nxt.container_traits : mustAddGCRange;
146 import nxt.dynamic_array : Array = DynamicArray;
147 
148 // version = enterSingleInfiniteMemoryLeakTest;
149 // version = benchmark;
150 // version = show;
151 // version = useMemoryErrorHandler;
152 // version = showBranchSizes;
153 // version = showAssertTags;
154 
155 alias isFixedTrieableKeyType = isIntegralBijectableType;
156 
157 /** Returns: `true` if `T` is a scalar trie key-type, `false` otherwise. */
158 enum isScalarTrieableKeyType(T) = (isFixedTrieableKeyType!T ||
159                                    (isInputRange!T &&
160                                     isFixedTrieableKeyType!(ElementType!T)));
161 
162 /** Returns: `true` if `T` is a type that can be stored as a key in a trie, ` false` otherwise. */
163 template isTrieableKeyType(T)
164 {
165     static if (is(T == struct))
166     {
167         import std.meta : allSatisfy;
168         enum isTrieableKeyType = allSatisfy!(isScalarTrieableKeyType, // recurse
169                                              typeof(T.tupleof));
170     }
171     else static if (is(T == class))
172     {
173         static assert("Class types cannot be stored in a radix tree because classes in D has reference semantics");
174     }
175     else
176     {
177         enum isTrieableKeyType = isScalarTrieableKeyType!T;
178     }
179 }
180 
181 version(useMemoryErrorHandler) unittest
182 {
183     import etc.linux.memoryerror : registerMemoryErrorHandler;
184     registerMemoryErrorHandler();
185     dbg("registerMemoryErrorHandler done");
186 }
187 
188 @safe pure nothrow @nogc unittest
189 { version(showAssertTags) dbg();
190     static assert(isTrieableKeyType!(const(char)[]));
191 
192     struct S { int x, y, z; double w; bool b; }
193     static assert(isTrieableKeyType!(S));
194 }
195 
196 /** Binary power of radix, typically either 1, 2, 4 or 8. */
197 private enum span = 8;
198 /** Branch-multiplicity. Also called order. */
199 private enum radix = 2^^span;
200 
201 static assert(span == 8, "Radix is currently limited to 8");
202 static assert(size_t.sizeof == 8, "Currently requires a 64-bit CPU (size_t.sizeof == 8)");
203 
204 version(unittest)
205 {
206     version = useModulo;
207 }
208 version = useModulo;            // TODO remove and activate only in version(unittest)
209 
210 /** Radix Modulo Index
211     Restricted index type avoids range checking in array indexing below.
212 */
213 version(useModulo)
214 {
215     import nxt.modulo : Mod, mod;   // TODO remove these if radix is `256`
216     alias Ix = Mod!(radix, ubyte);
217     alias UIx = Mod!(radix, size_t); // `size_t` is faster than `uint` on Intel Haswell
218 
219     /** Mutable RawTree Key. */
220     alias Key(size_t span) = Mod!(2^^span)[]; // TODO use static_bitarray to more naturally support span != 8.
221     /** Immutable RawTree Key. */
222     alias IKey(size_t span) = immutable(Mod!(2^^span))[]; // TODO use static_bitarray to more naturally support span != 8.
223     /** Fixed-Length RawTree Key. */
224     alias KeyN(size_t span, size_t N) = Mod!(2^^span)[N];
225 
226     enum useModuloFlag = true;
227 }
228 else
229 {
230     alias Ix = ubyte;
231     alias UIx = size_t;
232 
233     /** Mutable RawTree Key. */
234     alias Key(size_t span) = ubyte[]; // TODO use static_bitarray to more naturally support span != 8.
235     /** Immutable RawTree Key. */
236     alias IKey(size_t span) = immutable(ubyte)[]; // TODO use static_bitarray to more naturally support span != 8.
237     /** Fixed-Length RawTree Key. */
238     alias KeyN(size_t span, size_t N) = ubyte[N];
239 
240     enum useModuloFlag = false;
241 }
242 
243 import nxt.static_modarray : StaticModArray;
244 alias IxsN(uint capacity,
245            uint elementLength) = StaticModArray!(capacity,
246                                                  elementLength,
247                                                  8,
248                                                  useModuloFlag);
249 
250 alias UKey = Key!span;
251 bool empty(UKey ukey) @safe pure nothrow @nogc
252 {
253     return ukey.length == 0;
254 }
255 
256 private template IxElt(Value)
257 {
258     static if (is(Value == void)) // set case
259     {
260         alias IxElt = UIx;
261     }
262     else                        // map case
263     {
264         struct IxElt { UIx ix; Value value; }
265     }
266 }
267 
268 private static auto eltIx(Value)(inout IxElt!Value elt)
269 {
270     static if (is(Value == void)) // set case
271     {
272         return elt;
273     }
274     else                        // map case
275     {
276         return elt.ix;
277     }
278 }
279 
280 private template Elt(Value)
281 {
282     static if (is(Value == void)) // set case
283     {
284         alias Elt = UKey;
285     }
286     else                        // map case
287     {
288         struct Elt { UKey key; Value value; }
289     }
290 }
291 
292 private auto eltKey(Value)(inout Elt!Value elt)
293 {
294     static if (is(Value == void)) // set case
295     {
296         return elt;
297     }
298     else                        // map case
299     {
300         return elt.key;
301     }
302 }
303 
304 private auto eltKeyDropExactly(Value)(Elt!Value elt, size_t n)
305 {
306     static if (is(Value == void)) // set case
307     {
308         return elt[n .. $];
309     }
310     else                        // map case
311     {
312         return Elt!Value(elt.key[n .. $], elt.value);
313     }
314 }
315 
316 /** Results of attempt at modification sub. */
317 enum ModStatus:ubyte
318 {
319     maxCapacityReached,         // no operation, max capacity reached
320     unchanged,                  // no operation, element already stored
321     added, inserted = added,    // new element was added (inserted)
322     updated, modified = updated, // existing element was update/modified
323 }
324 
325 /** Size of a CPU cache line in bytes.
326  *
327  * Container layouts should be adapted to make use of at least this many bytes
328  * in its nodes.
329 */
330 private enum cacheLineSize = 64;
331 
332 shared static this()
333 {
334     import core.cpuid : dataCaches;
335     assert(cacheLineSize == dataCaches()[0].lineSize, "Cache line is not 64 bytes");
336 }
337 
338 enum keySeparator = ',';
339 
340 /** Single/1-Key Leaf with maximum key-length 7. */
341 struct OneLeafMax7
342 {
343     @safe pure:
344     enum capacity = 7;
345 
346     this(Ix[] key) nothrow @nogc
347     {
348         pragma(inline, true);
349         assert(key.length != 0);
350         this.key = key;
351     }
352 
353     bool contains(UKey key) const nothrow @nogc
354     {
355         pragma(inline, true);
356         return this.key == key;
357     }
358 
359     @property string toString() const
360     {
361         import std.string : format;
362         string s;
363         foreach (immutable i, immutable ix; key)
364         {
365             immutable first = i == 0; // first iteration
366             if (!first) { s ~= '_'; }
367             s ~= format("%.2X", ix); // in hexadecimal
368         }
369         return s;
370     }
371 
372     IxsN!(capacity, 1) key;
373 }
374 
375 /** Binary/2-Key Leaf with key-length 3. */
376 struct TwoLeaf3
377 {
378     enum keyLength = 3; // fixed length key
379     enum capacity = 2; // maximum number of keys stored
380 
381     @safe pure nothrow @nogc:
382 
383     this(Keys...)(Keys keys)
384     if (Keys.length != 0 &&
385         Keys.length <= capacity)
386     {
387         pragma(inline, true);
388         this.keys = keys;
389     }
390 
391     inout(Ix)[] prefix() inout
392     {
393         // version(LDC) pragma(inline, true);
394         assert(!keys.empty);
395         final switch (keys.length)
396         {
397         case 1:
398             return keys.at!0[];
399         case 2:
400             import std.algorithm.searching : commonPrefix;
401             return commonPrefix(keys.at!0[], keys.at!1[]);
402         }
403     }
404 
405     bool contains(UKey key) const
406     {
407         version(LDC) pragma(inline, true);
408         assert(!keys.empty);
409         final switch (keys.length)
410         {
411         case 1: return keys[0] == key;
412         case 2: return keys[0] == key || keys[1] == key;
413         }
414         // return keys.contains(key);
415     }
416 
417     IxsN!(capacity, keyLength) keys; // should never be empty
418 }
419 
420 /** Ternary/3-Key Leaf with key-length 2. */
421 struct TriLeaf2
422 {
423     enum keyLength = 2; // fixed length key
424     enum capacity = 3; // maximum number of keys stored
425 
426     @safe pure nothrow @nogc:
427 
428     this(Keys...)(Keys keys)
429     if (Keys.length != 0 &&
430         Keys.length <= capacity)
431     {
432         pragma(inline, true);
433         this.keys = keys;
434     }
435 
436     inout(Ix)[] prefix() inout
437     {
438         // version(LDC) pragma(inline, true);
439         assert(!keys.empty);
440         import std.algorithm.searching : commonPrefix;
441         final switch (keys.length)
442         {
443         case 1:
444             return keys.at!0[];
445         case 2:
446             return commonPrefix(keys.at!0[], keys.at!1[]);
447         case 3:
448             return commonPrefix(keys.at!0[],
449                                 commonPrefix(keys.at!1[],
450                                              keys.at!2[])); // TODO make and reuse variadic commonPrefix
451         }
452     }
453 
454     bool contains(UKey key) const
455     {
456         version(LDC) pragma(inline, true);
457         assert(!keys.empty);
458         final switch (keys.length)
459         {
460         case 1: return keys[0] == key;
461         case 2: return keys[0] == key || keys[1] == key;
462         case 3: return keys[0] == key || keys[1] == key || keys[2] == key;
463         }
464         // return keys.contains(key);
465     }
466 
467     IxsN!(capacity, keyLength) keys; // should never be empty
468 }
469 
470 /** Hepa/7-Key Leaf with key-length 1. */
471 struct HeptLeaf1
472 {
473     enum keyLength = 1;
474     enum capacity = 7; // maximum number of elements
475 
476     @safe pure nothrow @nogc:
477 
478     this(Keys...)(Keys keys)
479     if (Keys.length != 0 &&
480         Keys.length <= capacity)
481     {
482         pragma(inline, true);
483         // static foreach (const ix, const key; keys)
484         // {
485         //     this.keys[ix] = cast(Ix)key;
486         // }
487         this.keys = keys;
488     }
489 
490     bool contains(UIx key) const
491     {
492         pragma(inline, true);
493         assert(!keys.empty);
494         // NOTE this is not noticably faster
495         // final switch (keys.length)
496         // {
497         // case 1: return keys[0] == key;
498         // case 2: return keys[0] == key || keys[1] == key;
499         // case 3: return keys[0] == key || keys[1] == key || keys[2] == key;
500         // case 4: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key;
501         // case 5: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key || keys[4] == key;
502         // case 6: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key || keys[4] == key || keys[5] == key;
503         // case 7: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key || keys[4] == key || keys[5] == key || keys[6] == key;
504         // }
505         return keys.contains(key);
506     }
507     bool contains(UKey key) const
508     {
509         pragma(inline, true);
510         return (key.length == 1 &&
511                 keys.contains(UIx(key[0])));
512     }
513 
514     IxsN!(capacity, 1) keys;    // should never be empty
515 }
516 
517 /** Check if type `NodeType` is a variable-length aggregate (`struct`) type. */
518 private template hasVariableSize(NodeType)
519 {
520     import std.traits: hasMember;
521     enum hasVariableSize = hasMember!(NodeType, "allocationSizeOfCapacity");
522 }
523 
524 /** Allocate (if pointer) and Construct a `Node`-type of value type `NodeType`
525  * using constructor arguments `args` of `Args`.
526  */
527 auto constructFixedSizeNode(NodeType, Args...)(Args args) @trusted pure nothrow @nogc
528 if (!hasVariableSize!NodeType)
529 {
530     static if (isPointer!NodeType)
531     {
532         // debug ++_heapAllocBalance;
533         import core.lifetime : emplace;
534         return emplace(cast(NodeType)malloc((*NodeType.init).sizeof), args);
535         // TODO ensure alignment of node at least that of NodeType.alignof
536     }
537     else
538     {
539         return NodeType(args);
540     }
541 }
542 
543 /** Allocate (via malloc) and emplace an instance of a variable-length aggregate (`struct`) type `NodeType`.
544  *
545  * Construction is done using `malloc` plus `emplace`.
546  */
547 private NodeType* constructVariableSizeNode(NodeType, Args...)(size_t requiredCapacity, Args args) @trusted
548 if (is(NodeType == struct) &&
549     hasVariableSize!NodeType)
550 {
551     import std.math : nextPow2;
552     import std.algorithm.comparison : clamp;
553     const paddedRequestedCapacity = (requiredCapacity == 1 ?
554                                      1 :
555                                      (nextPow2(requiredCapacity - 1).clamp(NodeType.minCapacity,
556                                                                            NodeType.maxCapacity)));
557 
558     // import nxt.dbgio;
559     // dbg(NodeType.stringof, " paddedRequestedCapacity:", paddedRequestedCapacity, " requiredCapacity:", requiredCapacity);
560 
561     // assert(paddedRequestedCapacity >= requiredCapacity); // TODO this fails for dmd but not for ldc
562 
563     import nxt.qcmeman : malloc;
564     import core.lifetime : emplace;
565     return emplace(cast(typeof(return))malloc(NodeType.allocationSizeOfCapacity(paddedRequestedCapacity)),
566                    paddedRequestedCapacity, args);
567 }
568 
569 void freeNode(NodeType)(NodeType nt) @trusted pure nothrow @nogc
570 {
571     static if (isPointer!NodeType)
572     {
573         free(cast(void*)nt);  // TODO Allocator.free
574         // debug --_heapAllocBalance;
575     }
576 }
577 
578 /** Sparsely coded leaves with values of type `Value`. */
579 static private struct SparseLeaf1(Value)
580 {
581     import nxt.searching_ex : containsStoreIndex;
582     import std.range : assumeSorted;
583     import std.algorithm.sorting : isSorted;
584 
585     enum hasValue = !is(Value == void);
586 
587     enum minCapacity = 0;     // preferred minimum number of preallocated values
588 
589     // preferred maximum number of preallocated values, if larger use a DenseLeaf1 instead
590     static if (hasValue)
591     {
592         enum maxCapacity = 128;
593     }
594     else
595     {
596         enum maxCapacity = 48;
597     }
598 
599     version(useModulo)
600     {
601         alias Capacity = Mod!(maxCapacity + 1);
602     }
603     else
604     {
605         alias Capacity = ubyte;
606     }
607     alias Length = Capacity;
608 
609     pure nothrow @nogc:
610 
611     this(size_t capacity)
612     {
613         _capacity = cast(Capacity)capacity;
614         _length = 0;
615     }
616 
617     /** Returns a an allocated duplicate of this.
618      *
619      * Shallowly duplicates the values in the map case.
620      */
621     typeof(this)* dup()
622     {
623         static if (hasValue)
624         {
625             return constructVariableSizeNode!(typeof(this))(this._capacity, ixs, values);
626         }
627         else
628         {
629             return constructVariableSizeNode!(typeof(this))(this._capacity, ixs);
630         }
631     }
632 
633     static if (hasValue)
634     {
635         static if (mustAddGCRange!Value)
636         {
637             import core.memory : GC;
638         }
639 
640         /** Construct with capacity `capacity`. */
641         this(size_t capacity, Ix[] ixs, Value[] values)
642         in
643         {
644             assert(ixs.length == values.length);
645             assert(capacity >= ixs.length);
646             assert(values.length <= maxCapacity);
647             assert(ixs.isSorted);
648         }
649         do
650         {
651             _capacity = capacity;
652             _length = ixs.length;
653             foreach (immutable i, immutable ix; ixs) { ixsSlots[i] = ix; }
654             foreach (immutable i, immutable value; values) { valuesSlots[i] = value; }
655             static if (mustAddGCRange!Value)
656             {
657                 GC.addRange(valuesSlots.ptr, _capacity * Value.sizeof);
658             }
659         }
660     }
661     else
662     {
663         this(size_t capacity, Ix[] ixs)
664         in
665         {
666             assert(capacity >= ixs.length);
667             assert(ixs.length <= maxCapacity);
668             assert(ixs.isSorted);
669         }
670         do
671         {
672             _capacity = cast(Capacity)capacity;
673             _length = cast(Length)ixs.length;
674             foreach (immutable i, immutable ix; ixs) { ixsSlots[i] = ix; }
675         }
676     }
677 
678     ~this() @nogc
679     {
680         pragma(inline, true);
681         deinit();
682     }
683 
684     private void deinit() @trusted
685     {
686         pragma(inline, true);
687         static if (hasValue &&
688                    mustAddGCRange!Value)
689         {
690             GC.removeRange(valuesSlots.ptr, _capacity * Value.sizeof);
691         }
692     }
693 
694     auto makeRoom()
695     {
696         auto next = &this;
697         if (full)
698         {
699             if (length < maxCapacity) // if we can expand more
700             {
701                 // make room
702                 static if (hasValue)
703                 {
704                     next = constructVariableSizeNode!(typeof(this))(length + 1, ixsSlots, valuesSlots);
705                 }
706                 else
707                 {
708                     next = constructVariableSizeNode!(typeof(this))(length + 1, ixsSlots);
709                 }
710                 this.deinit(); free(&this); // clear `this`. TODO reuse existing helper function in Phobos?
711             }
712             else
713             {
714                 return null;    // indicate that max capacity has been reached
715             }
716         }
717         return next;
718     }
719 
720     /** Insert `key`, possibly self-reallocating `this` (into return). */
721     typeof(this)* reconstructingInsert(IxElt!Value elt,
722                                        out ModStatus modStatus,
723                                        out size_t index) @trusted
724     {
725         // get index
726         static if (hasValue)
727         {
728             immutable ix = elt.ix;
729         }
730         else
731         {
732             immutable ix = elt;
733         }
734 
735         // handle existing element
736         if (ixs.assumeSorted.containsStoreIndex(ix, index))
737         {
738             static if (hasValue)
739             {
740                 modStatus = valuesSlots[index] != elt.value ? ModStatus.updated : ModStatus.unchanged;
741                 valuesSlots[index] = elt.value;
742             }
743             else
744             {
745                 modStatus = ModStatus.unchanged;
746             }
747             return &this;
748         }
749 
750         // try making room for new element
751         auto next = makeRoom();
752         if (next is null)
753         {
754             modStatus = ModStatus.maxCapacityReached; // TODO expand to `DenseLeaf1`
755             return &this;
756         }
757 
758         // insert new element
759         next.insertAt(index, elt);
760         modStatus = ModStatus.added;
761 
762         return next;
763     }
764 
765     private void insertAt(size_t index, IxElt!Value elt)
766     {
767         assert(index <= _length);
768 
769         foreach (immutable i; 0 .. _length - index) // TODO functionize this loop or reuse memmove:
770         {
771             immutable iD = _length - i;
772             immutable iS = iD - 1;
773             ixsSlots[iD] = ixsSlots[iS];
774         }
775 
776         static if (hasValue)
777         {
778             ixsSlots[index] = elt.ix;
779             valuesSlots[index] = elt.value;
780         }
781         else
782         {
783             ixsSlots[index] = cast(Ix)elt;
784         }
785 
786         ++_length;
787     }
788 
789     pragma(inline, true) Length length() const @safe @nogc { return _length; }
790     pragma(inline, true) Capacity capacity() const @safe @nogc { return _capacity; }
791 
792     pragma(inline, true) bool empty() const @safe @nogc { return _length == 0; }
793     pragma(inline, true) bool full() const @safe @nogc { return _length == _capacity; }
794 
795     /** Get all initialized keys. */
796     pragma(inline, true) auto ixs() inout @trusted @nogc { return ixsSlots[0 .. _length]; }
797 
798     static if (hasValue)
799     {
800         /** Get all intialized values. */
801         auto values() inout @trusted @nogc
802         {
803             pragma(inline, true)
804             return valuesSlots[0 .. _length];
805         }
806 
807         void setValue(UIx ix, in Value value) @trusted
808         {
809             pragma(inline, true);
810             size_t index;
811             immutable hit = ixs.assumeSorted.containsStoreIndex(ix, index);
812             assert(hit);        // assert hit for now
813             assert(index < length);
814             values[index] = value;
815         }
816 
817         inout(Value*) contains(UIx key) inout @nogc
818         {
819             pragma(inline, true);
820             size_t index;
821             if (ixs.assumeSorted.containsStoreIndex(key, index))
822             {
823                 return &(values[index]);
824             }
825             else
826             {
827                 return null;
828             }
829         }
830     }
831     else
832     {
833         bool contains(UIx key) const @nogc
834         {
835             pragma(inline, true);
836             return ixs.assumeSorted.contains(key);
837         }
838     }
839 
840     /** Get all reserved keys. */
841     private auto ixsSlots() inout @trusted @nogc
842     {
843         pragma(inline, true);
844         static if (hasValue)
845         {
846             return (cast(Ix*)(_values.ptr + _capacity))[0 .. _capacity];
847         }
848         else
849         {
850             return _ixs.ptr[0 .. _capacity];
851         }
852     }
853     static if (hasValue)
854     {
855         /** Get all reserved values. */
856         private auto valuesSlots() inout @trusted @nogc
857         {
858             pragma(inline, true);
859             return _values.ptr[0 .. _capacity];
860         }
861     }
862 
863     /** Get allocation size (in bytes) needed to hold `length` number of
864      * elements (keys and optionally values).
865      */
866     static size_t allocationSizeOfCapacity(size_t capacity) @safe pure nothrow @nogc
867     {
868         static if (hasValue)
869         {
870             return (this.sizeof + // base plus
871                     Value.sizeof*capacity + // actual size of `_values`
872                     Ix.sizeof*capacity);   // actual size of `_ixs`
873         }
874         else
875         {
876             return (this.sizeof + // base plus
877                     Ix.sizeof*capacity);   // actual size of `_ixs`
878         }
879     }
880 
881     /** Get allocated size (in bytes) of `this` including the variable-length part.
882      */
883     size_t allocatedSize() const @safe pure nothrow @nogc
884     {
885         return allocationSizeOfCapacity(_capacity);
886     }
887 
888 private:
889     Length _length;
890     const Capacity _capacity;
891     static if (hasValue)
892     {
893         Value[0] _values;
894     }
895     Ix[0] _ixs;
896 }
897 
898 /** Densely coded leaves with values of type `Value`. */
899 static private struct DenseLeaf1(Value)
900 {
901     import nxt.static_bitarray : StaticBitArray;
902 
903     enum hasValue = !is(Value == void);
904 
905     static if (hasValue)
906     {
907         enum hasGCScannedValues = !is(Value == bool) && mustAddGCRange!Value;
908     }
909     else
910     {
911         enum hasGCScannedValues = false;
912     }
913 
914     static if (hasGCScannedValues)
915     {
916         import core.memory : GC;
917     }
918 
919     enum capacity = radix;
920 
921     @safe pure nothrow @nogc:
922 
923     static if (hasValue)
924     {
925         this(Ix[] ixs, Value[] values)
926         {
927             assert(ixs.length <= capacity);
928             assert(ixs.length == values.length);
929             foreach (immutable i, immutable ix; ixs)
930             {
931                 _ixBits[ix] = true;
932                 _values[ix] = values[i];
933             }
934             static if (hasGCScannedValues)
935             {
936                 GC.addRange(_values.ptr, capacity * Value.size);
937             }
938         }
939 
940         this(ref StaticBitArray!capacity ixBits, Value[] values)
941         {
942             assert(ixBits.length == values.length);
943             _ixBits = ixBits;
944             _values[] = values[]; // TODO commenting out does not affect unittests
945             static if (hasGCScannedValues)
946             {
947                 GC.addRange(_values.ptr, capacity * Value.size);
948             }
949         }
950     }
951     else
952     {
953         this(Ix[] ixs)
954         {
955             assert(ixs.length <= capacity);
956             foreach (immutable ix; ixs)
957             {
958                 _ixBits[ix] = true;
959             }
960         }
961 
962         this(const ref StaticBitArray!capacity ixBits)
963         {
964             _ixBits = ixBits;
965         }
966     }
967 
968 
969     /** Returns a an allocated duplicate of this.
970         Shallowly duplicates the values in the map case.
971     */
972     typeof(this)* dup()
973     {
974         pragma(inline, true);
975         static if (hasValue)
976         {
977             return constructFixedSizeNode!(typeof(this)*)(_ixBits, _values);
978         }
979         else
980         {
981             return constructFixedSizeNode!(typeof(this)*)(_ixBits);
982         }
983     }
984 
985     ~this() @nogc
986     {
987         pragma(inline, true);
988         static if (hasGCScannedValues)
989         {
990             GC.removeRange(_values.ptr, capacity * Value.size);
991         }
992     }
993 
994     pragma(inline, true) bool empty() const { return _ixBits.allZero; }
995     pragma(inline, true) bool full() const { return _ixBits.allOne; }
996     pragma(inline, true) size_t count() const { return _ixBits.countOnes; }
997 
998     static if (hasValue)
999     {
1000         pragma(inline, true) inout(Value*) contains(UIx ix) inout
1001         {
1002             return _ixBits[ix] ? &(_values[ix]) : null;
1003         }
1004     }
1005     else
1006     {
1007         pragma(inline, true) bool contains(UIx ix) const { return _ixBits[ix]; }
1008     }
1009 
1010     ModStatus insert(IxElt!Value elt)
1011     {
1012         pragma(inline, true);
1013 
1014         ModStatus modStatus;
1015 
1016         static if (hasValue)
1017         {
1018             immutable ix = elt.ix;
1019         }
1020         else
1021         {
1022             immutable ix = elt;
1023         }
1024 
1025         if (contains(ix))
1026         {
1027             static if (hasValue)
1028             {
1029                 modStatus = _values[ix] != elt.value ? ModStatus.updated : ModStatus.unchanged;
1030             }
1031             else
1032             {
1033                 modStatus = ModStatus.unchanged;
1034             }
1035         }
1036         else
1037         {
1038             _ixBits[ix] = true;
1039             modStatus = ModStatus.added;
1040         }
1041 
1042         // set element
1043         static if (hasValue)
1044         {
1045             _values[ix] = elt.value;
1046         }
1047 
1048         return modStatus;
1049     }
1050 
1051     /** Try to find index to first set bit in `_ixBits` starting at bit index `ix` and put the result in `nextIx`.
1052         Returns: `true` upon find, `false` otherwise.
1053         TODO move to StaticBitArray
1054      */
1055     bool tryFindSetBitIx(UIx ix, out UIx nextIx) const
1056     {
1057         pragma(inline, true);
1058         assert(!_ixBits.allZero);
1059         return _ixBits.canFindIndexOf(true, ix, nextIx);
1060     }
1061 
1062     bool tryFindNextSetBitIx(UIx ix, out UIx nextIx) const
1063     {
1064         pragma(inline, true);
1065         immutable ix1 = cast(uint)(ix + 1);
1066         return ix1 != radix && tryFindSetBitIx(UIx(ix1), nextIx);
1067     }
1068 
1069     static if (hasValue)
1070     {
1071         /** Set value at index `ix` to `value`. */
1072         pragma(inline, true) void setValue(UIx ix, in Value value) { _values[ix] = value; }
1073 
1074         pragma(inline, true) auto values() inout { return _values; }
1075     }
1076 
1077 private:
1078     StaticBitArray!capacity _ixBits;  // 32 bytes
1079     static if (hasValue)
1080     {
1081         // static if (is(Value == bool))
1082         // {
1083         //     StaticBitArray!capacity _values; // packed values
1084         // }
1085         // else
1086         // {
1087         //     Value[capacity] _values;
1088         // }
1089         Value[capacity] _values;
1090     }
1091 }
1092 
1093 /** Fixed-Length leaf Key-only Node. */
1094 alias FixedKeyLeafN = WordVariant!(OneLeafMax7,
1095                                    TwoLeaf3,
1096                                    TriLeaf2);
1097 
1098 /** Mutable leaf node of 1-Ix leaves.
1099  *
1100  * Used by branch-leaf.
1101  */
1102 alias Leaf1(Value) = WordVariant!(HeptLeaf1, // TODO remove from case when Value is void
1103                                   SparseLeaf1!Value*,
1104                                   DenseLeaf1!Value*);
1105 
1106 /** Returns: `true` if `key` is stored under `curr`, `false` otherwise. */
1107 UIx firstIx(Value)(Leaf1!Value curr)
1108 {
1109     final switch (curr.typeIx) with (Leaf1!Value.Ix)
1110     {
1111     case undefined: assert(false);
1112     case ix_HeptLeaf1:
1113     case ix_SparseLeaf1Ptr:
1114         return 0;           // always first
1115     case ix_DenseLeaf1Ptr:
1116         auto curr_ = curr.as!(DenseLeaf1!Value*);
1117         UIx nextIx;
1118         immutable bool hit = curr_.tryFindSetBitIx(0, nextIx);
1119         assert(hit);
1120         return nextIx;
1121     }
1122 }
1123 
1124 /** Try to iterate leaf index `ix` to index, either sparse or dense and put result in `nextIx`.
1125  *
1126  * Returns: `true` if `nextIx` was set, `false` if no more indexes was available.
1127  */
1128 pragma(inline) bool tryNextIx(Value)(Leaf1!Value curr, const UIx ix, out Ix nextIx)
1129 {
1130     final switch (curr.typeIx) with (Leaf1!Value.Ix)
1131     {
1132     case undefined: assert(false);
1133     case ix_HeptLeaf1:
1134         immutable curr_ = curr.as!(HeptLeaf1);
1135         if (ix + 1 == curr_.keys.length)
1136         {
1137             return false;
1138         }
1139         else
1140         {
1141             nextIx = Ix(ix + 1);
1142             return true;
1143         }
1144     case ix_SparseLeaf1Ptr:
1145         immutable curr_ = curr.as!(SparseLeaf1!Value*);
1146         if (ix + 1 == curr_.length)
1147         {
1148             return false;
1149         }
1150         else
1151         {
1152             nextIx = Ix(ix + 1);
1153             return true;
1154         }
1155     case ix_DenseLeaf1Ptr:
1156         return curr.as!(DenseLeaf1!Value*).tryFindNextSetBitIx(ix, nextIx);
1157     }
1158 }
1159 
1160 static assert((DenseLeaf1!void).sizeof == 32);
1161 
1162 /** Adaptive radix tree (ART) container storing untyped (raw) keys.
1163 
1164     In set-case (`Value` is `void`) this container contains specific memory
1165     optimizations for representing a set pointers or integral types (of fixed
1166     length).
1167 
1168     Radix-trees are suitable for storing ordered sets/maps with variable-length
1169     keys and provide completion of all its keys matching a given
1170     key-prefix. This enables, for instance, efficient storage and retrieval of
1171     large sets of long URLs with high probability of sharing a common prefix,
1172     typically a domain and path.
1173 
1174     Branch packing of leaves is more efficient when `Key.sizeof` is fixed, that
1175     is, the member `hasFixedKeyLength` returns `true`.
1176 
1177     For optimal performance, the individual bit-chunks should be arranged
1178     starting with most sparse chunks first. For integers this means most
1179     significant chunk (byte) first. This includes IEEE-compliant floating point
1180     numbers.
1181 
1182     For a good introduction to adaptive radix trees (ART) see $(HTTP infosys.cs.uni-saarland.de/publications/ARCD15.pdf)
1183 
1184     See_Also: $(HTTP www.ietf.org/rfc/rfc2616.txt, RFC2616)
1185 
1186     See_Also: $(HTTP en.wikipedia.org/wiki/Trie)
1187     See_Also: $(HTTP en.wikipedia.org/wiki/Radix_tree)
1188     See_Also: $(HTTP github.com/npgall/concurrent-trees)
1189     See_Also: $(HTTP code.dogmap.org/kart/)
1190     See_Also: $(HTTP cr.yp.to/critbit.html)
1191     See_Also: $(HTTP gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/trie_based_containers.html)
1192     See_Also: $(HTTP github.com/npgall/concurrent-trees)
1193 */
1194 template RawRadixTree(Value = void)
1195 {
1196     import std.bitmanip : bitfields;
1197     import std.algorithm.iteration : filter;
1198     import std.meta : AliasSeq, staticMap;
1199 
1200     import nxt.static_bitarray : StaticBitArray;
1201 
1202     enum isValue = !is(Value == void);
1203 
1204     version(useModulo)
1205     {
1206         alias SubCount = Mod!(radix + 1);
1207     }
1208     else
1209     {
1210         alias SubCount = uint; // needed because for inclusive range 0 .. 256
1211     }
1212 
1213     struct IxSub { UIx ix; Node node; }
1214 
1215     /** Sparse/Packed dynamically sized branch implemented as variable-length
1216         struct.
1217     */
1218     static private struct SparseBranch
1219     {
1220         enum minCapacity = 0; // minimum number of preallocated sub-indexes and sub-nodes
1221         enum maxCapacity = 48; // maximum number of preallocated sub-indexes and sub-nodes
1222         enum prefixCapacity = 5; // 5, 13, 21, ...
1223 
1224         version(useModulo)
1225         {
1226             alias Count = Mod!(maxCapacity + 1);
1227         }
1228         else
1229         {
1230             alias Count = ubyte;
1231         }
1232 
1233         @safe pure nothrow:
1234 
1235         this(size_t subCapacity)
1236         {
1237             version(LDC) pragma(inline, true);
1238             initialize(subCapacity);
1239         }
1240 
1241         this(size_t subCapacity, const Ix[] prefix, Leaf1!Value leaf1)
1242         {
1243             version(LDC) pragma(inline, true);
1244             initialize(subCapacity);
1245             this.prefix = prefix;
1246             this.leaf1 = leaf1;
1247         }
1248 
1249         this(size_t subCapacity, const Ix[] prefix)
1250         {
1251             version(LDC) pragma(inline, true);
1252             initialize(subCapacity);
1253             this.prefix = prefix;
1254         }
1255 
1256         this(size_t subCapacity, Leaf1!Value leaf1)
1257         {
1258             version(LDC) pragma(inline, true);
1259             initialize(subCapacity);
1260             this.leaf1 = leaf1;
1261         }
1262 
1263         this(size_t subCapacity, const Ix[] prefix, IxSub sub)
1264         {
1265             version(LDC) pragma(inline, true);
1266 
1267             assert(subCapacity);
1268 
1269             initialize(subCapacity);
1270 
1271             this.prefix = prefix;
1272             this.subCount = 1;
1273             this.subIxSlots[0] = sub.ix;
1274             this.subNodeSlots[0] = sub.node;
1275         }
1276 
1277         this(size_t subCapacity, typeof(this)* rhs)
1278         in
1279         {
1280             assert(subCapacity > rhs.subCapacity);
1281             assert(rhs);
1282         }
1283         do
1284         {
1285             version(LDC) pragma(inline, true);
1286             // these two must be in this order:
1287             // 1.
1288             move(rhs.leaf1, this.leaf1);
1289             debug rhs.leaf1 = typeof(rhs.leaf1).init;
1290             this.subCount = rhs.subCount;
1291             move(rhs.prefix, this.prefix);
1292 
1293             // 2.
1294             initialize(subCapacity);
1295 
1296             // copy variable length part. TODO optimize:
1297             this.subIxSlots[0 .. rhs.subCount] = rhs.subIxSlots[0 .. rhs.subCount];
1298             this.subNodeSlots[0 .. rhs.subCount] = rhs.subNodeSlots[0 .. rhs.subCount];
1299 
1300             assert(this.subCapacity > rhs.subCapacity);
1301         }
1302 
1303         private pragma(inline, true) void initialize(size_t subCapacity)
1304         {
1305             // assert(subCapacity != 4);
1306             this.subCapacity = cast(Count)subCapacity;
1307             debug           // only zero-initialize in debug mode
1308             {
1309                 // zero-initialize variable-length part
1310                 subIxSlots[] = Ix.init;
1311                 subNodeSlots[] = Node.init;
1312             }
1313         }
1314 
1315         typeof(this)* dup() @nogc
1316         {
1317             auto copy = constructVariableSizeNode!(typeof(this))(this.subCapacity);
1318             copy.leaf1 = dupAt(this.leaf1);
1319             copy.prefix = this.prefix;
1320             copy.subCount = this.subCount;
1321 
1322             copy.subIxSlots[0 .. subCount] = this.subIxSlots[0 .. subCount]; // copy initialized
1323             debug copy.subIxSlots[subCount .. $] = Ix.init;                  // zero rest in debug
1324 
1325             foreach (immutable i; 0 .. subCount)
1326             {
1327                 copy.subNodeSlots[i] = dupAt(subNodeSlots[i]);
1328             }
1329             return copy;
1330         }
1331 
1332         pragma(inline, true) ~this() @nogc { deinit(); }
1333 
1334         private pragma(inline, true) void deinit() @nogc { /* nothing for now */ }
1335 
1336         /** Insert `sub`, possibly self-reallocating `this` (into return).
1337          */
1338         typeof(this)* reconstructingInsert(IxSub sub,
1339                                            out ModStatus modStatus,
1340                                            out size_t index) @trusted
1341         {
1342             auto next = &this;
1343 
1344             import nxt.searching_ex : containsStoreIndex;
1345             if (subIxs.containsStoreIndex(sub.ix, index))
1346             {
1347                 assert(subIxSlots[index] == sub.ix); // subIxSlots[index] = sub[0];
1348                 subNodeSlots[index] = sub.node;
1349                 modStatus = ModStatus.updated;
1350                 return next;
1351             }
1352 
1353             if (full)
1354             {
1355                 if (subCount < maxCapacity) // if we can expand more
1356                 {
1357                     next = constructVariableSizeNode!(typeof(this))(subCount + 1, &this);
1358 
1359                     this.deinit(); free(&this); // clear `this`. TODO reuse existing helper function in Phobos?
1360                 }
1361                 else
1362                 {
1363                     modStatus = ModStatus.maxCapacityReached; // TODO expand to `DenseBranch`
1364                     return next;
1365                 }
1366             }
1367 
1368             next.insertAt(index, sub);
1369             modStatus = ModStatus.added;
1370             return next;
1371         }
1372 
1373         private void insertAt(size_t index, IxSub sub)
1374         {
1375             assert(index <= subCount);
1376             foreach (immutable i; 0 .. subCount - index) // TODO functionize this loop or reuse memmove:
1377             {
1378                 immutable iD = subCount - i;
1379                 immutable iS = iD - 1;
1380                 subIxSlots[iD] = subIxSlots[iS];
1381                 subNodeSlots[iD] = subNodeSlots[iS];
1382             }
1383             subIxSlots[index] = cast(Ix)sub.ix; // set new element
1384             subNodeSlots[index] = sub.node; // set new element
1385             ++subCount;
1386         }
1387 
1388         inout(Node) subNodeAt(UIx ix) inout
1389         {
1390             import nxt.searching_ex : binarySearch; // need this instead of `SortedRange.contains` because we need the index
1391             immutable hitIndex = subIxSlots[0 .. subCount].binarySearch(ix); // find index where insertion should be made
1392             return (hitIndex != typeof(hitIndex).max) ? subNodeSlots[hitIndex] : Node.init;
1393         }
1394 
1395         pragma(inline, true) bool empty() const @nogc { return subCount == 0; }
1396         pragma(inline, true) bool full()  const @nogc { return subCount == subCapacity; }
1397 
1398         pragma(inline, true) auto subIxs() inout @nogc
1399         {
1400             import std.range : assumeSorted;
1401             return subIxSlots[0 .. subCount].assumeSorted;
1402         }
1403 
1404         pragma(inline, true) auto subNodes() inout @nogc { return subNodeSlots[0 .. subCount]; }
1405 
1406         pragma(inline, true) Node firstSubNode() @nogc
1407         {
1408             return subCount ? subNodeSlots[0] : typeof(return).init;
1409         }
1410 
1411         /** Get all sub-`Ix` slots, both initialized and uninitialized. */
1412         private auto subIxSlots() inout @trusted pure nothrow
1413         {
1414             return (cast(Ix*)(_subNodeSlots0.ptr + subCapacity))[0 .. subCapacity];
1415         }
1416         /** Get all sub-`Node` slots, both initialized and uninitialized. */
1417         private auto subNodeSlots() inout @trusted pure nothrow
1418         {
1419             return _subNodeSlots0.ptr[0 .. subCapacity];
1420         }
1421 
1422         /** Append statistics of tree under `this` into `stats`. */
1423         void calculate(ref Stats stats) const
1424         {
1425             size_t count = 0; // number of non-zero sub-nodes
1426             foreach (immutable sub; subNodes)
1427             {
1428                 ++count;
1429                 sub.calculate!(Value)(stats);
1430             }
1431             assert(count <= radix);
1432             ++stats.popHist_SparseBranch[count]; // TODO type-safe indexing
1433 
1434             stats.sparseBranchAllocatedSizeSum += allocatedSize;
1435 
1436             if (leaf1)
1437             {
1438                 leaf1.calculate!(Value)(stats);
1439             }
1440         }
1441 
1442         /** Get allocation size (in bytes) needed to hold `length` number of
1443             sub-indexes and sub-nodes. */
1444         static size_t allocationSizeOfCapacity(size_t capacity) @safe pure nothrow @nogc
1445         {
1446             return (this.sizeof + // base plus
1447                     Node.sizeof*capacity + // actual size of `_subNodeSlots0`
1448                     Ix.sizeof*capacity);   // actual size of `_subIxSlots0`
1449         }
1450 
1451         /** Get allocated size (in bytes) of `this` including the variable-length part. */
1452         size_t allocatedSize() const @safe pure nothrow @nogc
1453         {
1454             return allocationSizeOfCapacity(subCapacity);
1455         }
1456 
1457     private:
1458 
1459         // members in order of decreasing `alignof`:
1460         Leaf1!Value leaf1;
1461 
1462         IxsN!(prefixCapacity, 1) prefix; // prefix common to all `subNodes` (also called edge-label)
1463         Count subCount;
1464         Count subCapacity;
1465         static assert(prefix.sizeof + subCount.sizeof + subCapacity.sizeof == 8); // assert alignment
1466 
1467         // variable-length part
1468         Node[0] _subNodeSlots0;
1469         Ix[0] _subIxSlots0;     // needs to special alignment
1470     }
1471 
1472     static assert(hasVariableSize!SparseBranch);
1473     static if (!isValue)
1474     {
1475         static assert(SparseBranch.sizeof == 16);
1476     }
1477 
1478     /** Dense/Unpacked `radix`-branch with `radix` number of sub-nodes. */
1479     static private struct DenseBranch
1480     {
1481         enum maxCapacity = 256;
1482         enum prefixCapacity = 15; // 7, 15, 23, ..., we can afford larger prefix here because DenseBranch is so large
1483 
1484         @safe pure nothrow:
1485 
1486         this(const Ix[] prefix)
1487         {
1488             this.prefix = prefix;
1489         }
1490 
1491         this(const Ix[] prefix, IxSub sub)
1492         {
1493             this(prefix);
1494             this.subNodes[sub.ix] = sub.node;
1495         }
1496 
1497         this(const Ix[] prefix, IxSub subA, IxSub subB)
1498         {
1499             assert(subA.ix != subB.ix); // disjunct indexes
1500             assert(subA.node != subB.node); // disjunct nodes
1501 
1502             this.subNodes[subA.ix] = subA.node;
1503             this.subNodes[subB.ix] = subB.node;
1504         }
1505 
1506         this(SparseBranch* rhs)
1507         {
1508             this.prefix = rhs.prefix;
1509 
1510             // move leaf
1511             move(rhs.leaf1, this.leaf1);
1512             debug rhs.leaf1 = Leaf1!Value.init; // make reference unique, to be on the safe side
1513 
1514             foreach (immutable i; 0 .. rhs.subCount) // each sub node. TODO use iota!(Mod!N)
1515             {
1516                 immutable iN = (cast(ubyte)i).mod!(SparseBranch.maxCapacity);
1517                 immutable subIx = UIx(rhs.subIxSlots[iN]);
1518 
1519                 this.subNodes[subIx] = rhs.subNodeSlots[iN];
1520                 debug rhs.subNodeSlots[iN] = null; // make reference unique, to be on the safe side
1521             }
1522         }
1523 
1524         typeof(this)* dup() @trusted // TODO remove @trusted qualifier when .ptr problem has been fixed
1525         {
1526             auto copy = constructFixedSizeNode!(typeof(this)*);
1527             copy.leaf1 = dupAt(leaf1);
1528             copy.prefix = prefix;
1529             foreach (immutable i, subNode; subNodes)
1530             {
1531                 copy.subNodes.ptr[i] = dupAt(subNode); // TODO remove .ptr access when I inout problem is solved
1532             }
1533             return copy;
1534         }
1535 
1536         /** Number of non-null sub-Nodes. */
1537         SubCount subCount() const
1538         {
1539             typeof(return) count = 0; // number of non-zero sub-nodes
1540             foreach (immutable subNode; subNodes) // TODO why can't we use std.algorithm.count here?
1541             {
1542                 if (subNode) { ++count; }
1543             }
1544             assert(count <= radix);
1545             return count;
1546         }
1547 
1548         bool findSubNodeAtIx(size_t currIx, out UIx nextIx) inout @nogc
1549         {
1550             import std.algorithm.searching : countUntil;
1551             immutable cnt = subNodes[currIx .. $].countUntil!(subNode => cast(bool)subNode);
1552             immutable bool hit = cnt >= 0;
1553             if (hit)
1554             {
1555                 const nextIx_ = currIx + cnt;
1556                 assert(nextIx_ <= Ix.max);
1557                 nextIx = cast(Ix)nextIx_;
1558             }
1559             return hit;
1560         }
1561 
1562         /** Append statistics of tree under `this` into `stats`. */
1563         void calculate(ref Stats stats) const
1564         {
1565             size_t count = 0; // number of non-zero sub-nodes
1566             foreach (immutable subNode; subNodes)
1567             {
1568                 if (subNode)
1569                 {
1570                     ++count;
1571                     subNode.calculate!(Value)(stats);
1572                 }
1573             }
1574             assert(count <= radix);
1575             ++stats.popHist_DenseBranch[count]; // TODO type-safe indexing
1576             if (leaf1) { leaf1.calculate!(Value)(stats); }
1577         }
1578 
1579     private:
1580         // members in order of decreasing `alignof`:
1581         Leaf1!Value leaf1;
1582         IxsN!(prefixCapacity, 1) prefix; // prefix (edge-label) common to all `subNodes`
1583         IndexedBy!(Node[radix], UIx) subNodes;
1584     }
1585 
1586     version(showBranchSizes)
1587     {
1588         pragma(msg, "SparseBranch.sizeof:", SparseBranch.sizeof, " SparseBranch.alignof:", SparseBranch.alignof);
1589         pragma(msg, "DenseBranch.sizeof:", DenseBranch.sizeof, " DenseBranch.alignof:", DenseBranch.alignof);
1590 
1591         pragma(msg, "SparseBranch.prefix.sizeof:", SparseBranch.prefix.sizeof, " SparseBranch.prefix.alignof:", SparseBranch.prefix.alignof);
1592         pragma(msg, "DenseBranch.prefix.sizeof:", DenseBranch.prefix.sizeof, " DenseBranch.prefix.alignof:", DenseBranch.prefix.alignof);
1593 
1594         // pragma(msg, "SparseBranch.subNodes.sizeof:", SparseBranch.subNodes.sizeof, " SparseBranch.subNodes.alignof:", SparseBranch.subNodes.alignof);
1595         // pragma(msg, "SparseBranch.subIxs.sizeof:", SparseBranch.subIxs.sizeof, " SparseBranch.subIxs.alignof:", SparseBranch.subIxs.alignof);
1596     }
1597 
1598     // TODO make these run-time arguments at different key depths and map to statistics of typed-key
1599     alias DefaultBranch = SparseBranch; // either `SparseBranch`, `DenseBranch`
1600 
1601     /** Mutable node. */
1602     alias Node = WordVariant!(OneLeafMax7,
1603                               TwoLeaf3,
1604                               TriLeaf2,
1605 
1606                               HeptLeaf1,
1607                               SparseLeaf1!Value*,
1608                               DenseLeaf1!Value*,
1609 
1610                               SparseBranch*,
1611                               DenseBranch*);
1612 
1613     /** Mutable branch node. */
1614     alias Branch = WordVariant!(SparseBranch*,
1615                                 DenseBranch*);
1616 
1617     static assert(Node.typeBits <= IxsN!(7, 1).typeBits);
1618     static assert(Leaf1!Value.typeBits <= IxsN!(7, 1).typeBits);
1619     static assert(Branch.typeBits <= IxsN!(7, 1).typeBits);
1620 
1621     /** Constant node. */
1622     // TODO make work with indexNaming
1623     // import std.typecons : ConstOf;
1624     // alias ConstNodePtr = WordVariant!(staticMap!(ConstOf, Node));
1625 
1626     static assert(span <= 8*Ix.sizeof, "Need more precision in Ix");
1627 
1628     /** Element reference. */
1629     private static struct ElementRef
1630     {
1631         Node node;
1632         UIx ix; // `Node`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1633         ModStatus modStatus;
1634 
1635         pragma(inline, true)
1636         @safe pure nothrow:
1637 
1638         bool opCast(T : bool)() const @nogc
1639         {
1640             return cast(bool)node;
1641         }
1642     }
1643 
1644     /** Branch Range (Iterator). */
1645     private static struct BranchRange
1646     {
1647         @safe pure nothrow:
1648 
1649         this(SparseBranch* branch)
1650         {
1651             this.branch = Branch(branch);
1652 
1653             this._subsEmpty = branch.subCount == 0;
1654             this._subCounter = 0; // always zero
1655 
1656             if (branch.leaf1)
1657             {
1658                 this.leaf1Range = Leaf1Range(branch.leaf1);
1659             }
1660 
1661             cacheFront();
1662         }
1663 
1664         this(DenseBranch* branch)
1665         {
1666             this.branch = Branch(branch);
1667 
1668             this._subCounter = 0; // TODO needed?
1669             _subsEmpty = !branch.findSubNodeAtIx(0, this._subCounter);
1670 
1671             if (branch.leaf1)
1672             {
1673                 this.leaf1Range = Leaf1Range(branch.leaf1);
1674             }
1675 
1676             cacheFront();
1677         }
1678 
1679         pragma(inline, true) UIx frontIx() const @nogc
1680         {
1681             assert(!empty);
1682             return _cachedFrontIx;
1683         }
1684 
1685         pragma(inline, true) bool atLeaf1() const @nogc
1686         {
1687             assert(!empty);
1688             return _frontAtLeaf1;
1689         }
1690 
1691         private UIx subFrontIx() const
1692         {
1693             final switch (branch.typeIx) with (Branch.Ix)
1694             {
1695             case undefined: assert(false);
1696             case ix_SparseBranchPtr:
1697                 return UIx(branch.as!(SparseBranch*).subIxs[_subCounter]);
1698             case ix_DenseBranchPtr:
1699                 return _subCounter;
1700             }
1701         }
1702 
1703         private Node subFrontNode() const
1704         {
1705             final switch (branch.typeIx) with (Branch.Ix)
1706             {
1707             case undefined: assert(false);
1708             case ix_SparseBranchPtr: return branch.as!(SparseBranch*).subNodeSlots[_subCounter];
1709             case ix_DenseBranchPtr: return branch.as!(DenseBranch*).subNodes[_subCounter];
1710             }
1711         }
1712 
1713         void appendFrontIxsToKey(ref Array!Ix key) const @nogc
1714         {
1715             final switch (branch.typeIx) with (Branch.Ix)
1716             {
1717             case undefined: assert(false);
1718             case ix_SparseBranchPtr:
1719                 key ~= branch.as!(SparseBranch*).prefix[];
1720                 break;
1721             case ix_DenseBranchPtr:
1722                 key ~= branch.as!(DenseBranch*).prefix[];
1723                 break;
1724             }
1725             key ~= cast(Ix)frontIx; // uses cached data so ok to not depend on branch type
1726         }
1727 
1728         size_t prefixLength() const @nogc
1729         {
1730             final switch (branch.typeIx) with (Branch.Ix)
1731             {
1732             case undefined: assert(false);
1733             case ix_SparseBranchPtr: return branch.as!(SparseBranch*).prefix.length;
1734             case ix_DenseBranchPtr: return  branch.as!(DenseBranch*).prefix.length;
1735             }
1736         }
1737 
1738         pragma(inline, true) bool empty() const @nogc { return leaf1Range.empty && _subsEmpty; }
1739         pragma(inline, true) bool subsEmpty() const @nogc { return _subsEmpty; }
1740 
1741         /** Try to iterated forward.
1742             Returns: `true` upon successful forward iteration, `false` otherwise (upon completion of iteration),
1743         */
1744         void popFront()
1745         {
1746             assert(!empty);
1747 
1748             // pop front element
1749             if (_subsEmpty)
1750             {
1751                 leaf1Range.popFront();
1752             }
1753             else if (leaf1Range.empty)
1754             {
1755                 popBranchFront();
1756             }
1757             else                // both non-empty
1758             {
1759                 if (leaf1Range.front <= subFrontIx) // `a` before `ab`
1760                 {
1761                     leaf1Range.popFront();
1762                 }
1763                 else
1764                 {
1765                     popBranchFront();
1766                 }
1767             }
1768 
1769             if (!empty) { cacheFront(); }
1770         }
1771 
1772         /** Fill cached value and remember if we we next element is direct
1773             (_frontAtLeaf1 is true) or under a sub-branch (_frontAtLeaf1 is
1774             false).
1775         */
1776         void cacheFront()
1777         {
1778             assert(!empty);
1779             if (_subsEmpty)     // no more sub-nodes
1780             {
1781                 _cachedFrontIx = leaf1Range.front;
1782                 _frontAtLeaf1 = true;
1783             }
1784             else if (leaf1Range.empty) // no more in direct leaf node
1785             {
1786                 _cachedFrontIx = subFrontIx;
1787                 _frontAtLeaf1 = false;
1788             }
1789             else                // both non-empty
1790             {
1791                 immutable leaf1Front = leaf1Range.front;
1792                 if (leaf1Front <= subFrontIx) // `a` before `ab`
1793                 {
1794                     _cachedFrontIx = leaf1Front;
1795                     _frontAtLeaf1 = true;
1796                 }
1797                 else
1798                 {
1799                     _cachedFrontIx = subFrontIx;
1800                     _frontAtLeaf1 = false;
1801                 }
1802             }
1803         }
1804 
1805         private void popBranchFront()
1806         {
1807             // TODO move all calls to Branch-specific members popFront()
1808             final switch (branch.typeIx) with (Branch.Ix)
1809             {
1810             case undefined: assert(false);
1811             case ix_SparseBranchPtr:
1812                 auto branch_ = branch.as!(SparseBranch*);
1813                 if (_subCounter + 1 == branch_.subCount) { _subsEmpty = true; } else { ++_subCounter; }
1814                 break;
1815             case ix_DenseBranchPtr:
1816                 auto branch_ = branch.as!(DenseBranch*);
1817                 _subsEmpty = !branch_.findSubNodeAtIx(_subCounter + 1, this._subCounter);
1818                 break;
1819             }
1820         }
1821 
1822     private:
1823         Branch branch;          // branch part of range
1824         Leaf1Range leaf1Range;  // range of direct leaves
1825 
1826         UIx _subCounter; // `Branch`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1827         UIx _cachedFrontIx;
1828 
1829         bool _frontAtLeaf1;   // `true` iff front is currently at a leaf1 element
1830         bool _subsEmpty;      // `true` iff no sub-nodes exists
1831     }
1832 
1833     /** Leaf1 Range (Iterator). */
1834     private static struct Leaf1Range
1835     {
1836         this(Leaf1!Value leaf1)
1837         {
1838             this.leaf1 = leaf1;
1839             bool empty;
1840             this._ix = firstIx(empty);
1841             if (empty)
1842             {
1843                 this.leaf1 = null;
1844             }
1845         }
1846 
1847         @safe pure nothrow:
1848 
1849         /** Get first index in current subkey. */
1850         UIx front() const
1851         {
1852             assert(!empty);
1853             final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1854             {
1855             case undefined: assert(false);
1856             case ix_HeptLeaf1:
1857                 static if (isValue)
1858                 {
1859                     assert(false, "HeptLeaf1 cannot store a value");
1860                 }
1861                 else
1862                 {
1863                     return UIx(leaf1.as!(HeptLeaf1).keys[_ix]);
1864                 }
1865             case ix_SparseLeaf1Ptr:
1866                 return UIx(leaf1.as!(SparseLeaf1!Value*).ixs[_ix]);
1867             case ix_DenseLeaf1Ptr:
1868                 return _ix;
1869             }
1870         }
1871 
1872         static if (isValue)
1873         {
1874             Value frontValue() const
1875             {
1876                 assert(!empty);
1877                 final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1878                 {
1879                 case undefined: assert(false);
1880                 case ix_HeptLeaf1: assert(false, "HeptLeaf1 cannot store a value");
1881                 case ix_SparseLeaf1Ptr:
1882                     return leaf1.as!(SparseLeaf1!Value*).values[_ix];
1883                 case ix_DenseLeaf1Ptr:
1884                     return leaf1.as!(DenseLeaf1!Value*).values[_ix];
1885                 }
1886             }
1887         }
1888 
1889         bool empty() const @nogc { return leaf1.isNull; }
1890 
1891         /** Pop front element.
1892          */
1893         void popFront()
1894         {
1895             assert(!empty);
1896 
1897             // TODO move all calls to leaf1-specific members popFront()
1898             final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1899             {
1900             case undefined: assert(false);
1901             case ix_HeptLeaf1:
1902                 static if (isValue)
1903                 {
1904                     assert(false, "HeptLeaf1 cannot store a value");
1905                 }
1906                 else
1907                 {
1908                     immutable leaf_ = leaf1.as!(HeptLeaf1);
1909                     if (_ix + 1 == leaf_.keys.length) { leaf1 = null; } else { ++_ix; }
1910                     break;
1911                 }
1912             case ix_SparseLeaf1Ptr:
1913                 const leaf_ = leaf1.as!(SparseLeaf1!Value*);
1914                 if (_ix + 1 == leaf_.length) { leaf1 = null; } else { ++_ix; }
1915                 break;
1916             case ix_DenseLeaf1Ptr:
1917                 const leaf_ = leaf1.as!(DenseLeaf1!Value*);
1918                 if (!leaf_.tryFindNextSetBitIx(_ix, _ix))
1919                 {
1920                     leaf1 = null;
1921                 }
1922                 break;
1923             }
1924         }
1925 
1926         static if (isValue)
1927         {
1928             auto ref value() inout
1929             {
1930                 final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1931                 {
1932                 case undefined: assert(false);
1933                 case ix_HeptLeaf1: assert(false, "HeptLeaf1 cannot store a value");
1934                 case ix_SparseLeaf1Ptr: return leaf1.as!(SparseLeaf1!Value*).values[_ix];
1935                 case ix_DenseLeaf1Ptr: return leaf1.as!(DenseLeaf1!Value*).values[_ix];
1936                 }
1937             }
1938         }
1939 
1940         private UIx firstIx(out bool empty) const
1941         {
1942             final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1943             {
1944             case undefined: assert(false);
1945             case ix_HeptLeaf1:
1946                 static if (isValue)
1947                 {
1948                     assert(false, "HeptLeaf1 cannot store a value");
1949                 }
1950                 else
1951                 {
1952                     return UIx(0);           // always first
1953                 }
1954             case ix_SparseLeaf1Ptr:
1955                 auto leaf_ = leaf1.as!(SparseLeaf1!Value*);
1956                 empty = leaf_.empty;
1957                 return UIx(0);           // always first
1958             case ix_DenseLeaf1Ptr:
1959                 auto leaf_ = leaf1.as!(DenseLeaf1!Value*);
1960                 UIx nextIx;
1961                 immutable bool hit = leaf_.tryFindSetBitIx(UIx(0), nextIx);
1962                 assert(hit);
1963                 return nextIx;
1964             }
1965         }
1966 
1967     private:
1968         Leaf1!Value leaf1; // TODO Use Leaf1!Value-WordVariant when it includes non-Value leaf1 types
1969         UIx _ix; // `Node`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1970     }
1971 
1972     /** Leaf Value Range (Iterator). */
1973     private static struct LeafNRange
1974     {
1975         this(Node leaf)
1976         {
1977             this.leaf = leaf;
1978             bool emptied;
1979             this.ix = firstIx(emptied);
1980             if (emptied) { this.leaf = null; }
1981         }
1982 
1983         @safe pure nothrow:
1984 
1985         private UIx firstIx(out bool emptied) const
1986         {
1987             switch (leaf.typeIx) with (Node.Ix)
1988             {
1989             case undefined: assert(false);
1990             case ix_OneLeafMax7:
1991             case ix_TwoLeaf3:
1992             case ix_TriLeaf2:
1993             case ix_HeptLeaf1:
1994                 return UIx(0);           // always first
1995             case ix_SparseLeaf1Ptr:
1996                 const leaf_ = leaf.as!(SparseLeaf1!Value*);
1997                 emptied = leaf_.empty;
1998                 return UIx(0);           // always first
1999             case ix_DenseLeaf1Ptr:
2000                 const leaf_ = leaf.as!(DenseLeaf1!Value*);
2001                 UIx nextIx;
2002                 immutable bool hit = leaf_.tryFindSetBitIx(UIx(0), nextIx);
2003                 assert(hit);
2004                 return nextIx;
2005             default: assert(false, "Unsupported Node type");
2006             }
2007         }
2008 
2009         /** Get current subkey. */
2010         Ix[] frontIxs()
2011         {
2012             assert(!empty);
2013             switch (leaf.typeIx) with (Node.Ix)
2014             {
2015             case undefined: assert(false);
2016             case ix_OneLeafMax7:
2017                 assert(ix == 0);
2018                 return leaf.as!(OneLeafMax7).key;
2019             case ix_TwoLeaf3:
2020                 return leaf.as!(TwoLeaf3).keys[ix][];
2021             case ix_TriLeaf2:
2022                 return leaf.as!(TriLeaf2).keys[ix][];
2023             case ix_HeptLeaf1:
2024                 return [leaf.as!(HeptLeaf1).keys[ix]];
2025             case ix_SparseLeaf1Ptr:
2026                 return [leaf.as!(SparseLeaf1!Value*).ixs[ix]];
2027             case ix_DenseLeaf1Ptr:
2028                 return [Ix(ix)];
2029             default: assert(false, "Unsupported Node type");
2030             }
2031         }
2032 
2033         /** Get first index in current subkey. */
2034         UIx frontIx()
2035         {
2036             assert(!empty);
2037             switch (leaf.typeIx) with (Node.Ix)
2038             {
2039             case undefined: assert(false);
2040             case ix_OneLeafMax7:
2041                 assert(ix == 0);
2042                 return UIx(leaf.as!(OneLeafMax7).key[0]);
2043             case ix_TwoLeaf3:
2044                 return UIx(leaf.as!(TwoLeaf3).keys[ix][0]);
2045             case ix_TriLeaf2:
2046                 return UIx(leaf.as!(TriLeaf2).keys[ix][0]);
2047             case ix_HeptLeaf1:
2048                 return UIx(leaf.as!(HeptLeaf1).keys[ix]);
2049             case ix_SparseLeaf1Ptr:
2050                 return UIx(leaf.as!(SparseLeaf1!Value*).ixs[ix]);
2051             case ix_DenseLeaf1Ptr:
2052                 return ix;
2053             default: assert(false, "Unsupported Node type");
2054             }
2055         }
2056 
2057         void appendFrontIxsToKey(ref Array!Ix key) const @nogc
2058         {
2059             assert(!empty);
2060             switch (leaf.typeIx) with (Node.Ix)
2061             {
2062             case undefined: assert(false);
2063             case ix_OneLeafMax7:
2064                 assert(ix == 0);
2065                 key ~= leaf.as!(OneLeafMax7).key[];
2066                 break;
2067             case ix_TwoLeaf3:
2068                 key ~= leaf.as!(TwoLeaf3).keys[ix][];
2069                 break;
2070             case ix_TriLeaf2:
2071                 key ~= leaf.as!(TriLeaf2).keys[ix][];
2072                 break;
2073             case ix_HeptLeaf1:
2074                 key ~= Ix(leaf.as!(HeptLeaf1).keys[ix]);
2075                 break;
2076             case ix_SparseLeaf1Ptr:
2077                 key ~= Ix(leaf.as!(SparseLeaf1!Value*).ixs[ix]);
2078                 break;
2079             case ix_DenseLeaf1Ptr:
2080                 key ~= cast(Ix)ix;
2081                 break;
2082             default: assert(false, "Unsupported Node type");
2083             }
2084         }
2085 
2086         pragma(inline, true) bool empty() const @nogc { return !leaf; }
2087 
2088         pragma(inline, true) void makeEmpty() @nogc { leaf = null; }
2089 
2090         /** Pop front element. */
2091         void popFront()
2092         {
2093             assert(!empty);
2094             // TODO move all calls to leaf-specific members popFront()
2095             switch (leaf.typeIx) with (Node.Ix)
2096             {
2097             case undefined: assert(false);
2098             case ix_OneLeafMax7:
2099                 makeEmpty; // only one element so forward automatically completes
2100                 break;
2101             case ix_TwoLeaf3:
2102                 auto leaf_ = leaf.as!(TwoLeaf3);
2103                 if (ix + 1 == leaf_.keys.length) { makeEmpty; } else { ++ix; }
2104                 break;
2105             case ix_TriLeaf2:
2106                 auto leaf_ = leaf.as!(TriLeaf2);
2107                 if (ix + 1 == leaf_.keys.length) { makeEmpty; } else { ++ix; }
2108                 break;
2109             case ix_HeptLeaf1:
2110                 auto leaf_ = leaf.as!(HeptLeaf1);
2111                 if (ix + 1 == leaf_.keys.length) { makeEmpty; } else { ++ix; }
2112                 break;
2113             case ix_SparseLeaf1Ptr:
2114                 auto leaf_ = leaf.as!(SparseLeaf1!Value*);
2115                 if (ix + 1 == leaf_.length) { makeEmpty; } else { ++ix; }
2116                 break;
2117             case ix_DenseLeaf1Ptr:
2118                 auto leaf_ = leaf.as!(DenseLeaf1!Value*);
2119                 immutable bool emptied = !leaf_.tryFindNextSetBitIx(ix, ix);
2120                 if (emptied) { makeEmpty; }
2121                 break;
2122             default: assert(false, "Unsupported Node type");
2123             }
2124         }
2125 
2126         static if (isValue)
2127         {
2128             auto ref value() inout
2129             {
2130                 switch (leaf.typeIx) with (Node.Ix)
2131                 {
2132                 case ix_SparseLeaf1Ptr: return leaf.as!(SparseLeaf1!Value*).values[ix];
2133                 case ix_DenseLeaf1Ptr: return leaf.as!(DenseLeaf1!Value*).values[ix];
2134                 default: assert(false, "Incorrect Leaf-type");
2135                 }
2136             }
2137         }
2138 
2139     private:
2140         Node leaf;              // TODO Use Leaf-WordVariant when it includes non-Value leaf types
2141         UIx ix; // `Node`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
2142     }
2143 
2144     /** Ordered Set of BranchRanges. */
2145     private static struct BranchRanges
2146     {
2147         static if (isValue)
2148         {
2149             bool appendFrontIxsToKey(ref Array!Ix key, ref Value value) const @trusted
2150             {
2151                 foreach (const ref branchRange; _bRanges)
2152                 {
2153                     branchRange.appendFrontIxsToKey(key);
2154                     if (branchRange.atLeaf1)
2155                     {
2156                         value = branchRange.leaf1Range.frontValue;
2157                         return true; // key and value are both complete
2158                     }
2159                 }
2160                 return false;   // only key is partially complete
2161             }
2162         }
2163         else
2164         {
2165             bool appendFrontIxsToKey(ref Array!Ix key) const @trusted
2166             {
2167                 foreach (const ref branchRange; _bRanges)
2168                 {
2169                     branchRange.appendFrontIxsToKey(key);
2170                     if (branchRange.atLeaf1)
2171                     {
2172                         return true; // key is complete
2173                     }
2174                 }
2175                 return false;   // key is not complete
2176             }
2177         }
2178         size_t get1DepthAt(size_t depth) const @trusted
2179         {
2180             foreach (immutable i, ref branchRange; _bRanges[depth .. $])
2181             {
2182                 if (branchRange.atLeaf1) { return depth + i; }
2183             }
2184             return typeof(_branch1Depth).max;
2185         }
2186 
2187         private void updateLeaf1AtDepth(size_t depth) @trusted
2188         {
2189             if (_bRanges[depth].atLeaf1)
2190             {
2191                 if (_branch1Depth == typeof(_branch1Depth).max) // if not yet defined
2192                 {
2193                     _branch1Depth = min(depth, _branch1Depth);
2194                 }
2195             }
2196             assert(_branch1Depth == get1DepthAt(0));
2197         }
2198 
2199         size_t getNext1DepthAt() const
2200         {
2201             pragma(inline, true);
2202             return get1DepthAt(_branch1Depth + 1);
2203         }
2204 
2205         bool hasBranch1Front() const
2206         {
2207             pragma(inline, true);
2208             return _branch1Depth != typeof(_branch1Depth).max;
2209         }
2210 
2211         void popBranch1Front()
2212         {
2213             pragma(inline, true);
2214             // _branchesKeyPrefix.popBackN(_bRanges.back);
2215             _bRanges[_branch1Depth].popFront();
2216         }
2217 
2218         bool emptyBranch1() const
2219         {
2220             pragma(inline, true);
2221             return _bRanges[_branch1Depth].empty;
2222         }
2223 
2224         bool atLeaf1() const
2225         {
2226             pragma(inline, true);
2227             return _bRanges[_branch1Depth].atLeaf1;
2228         }
2229 
2230         void shrinkTo(size_t newLength)
2231         {
2232             pragma(inline, true);
2233             // turn emptyness exception into an assert like ranges do
2234             // size_t suffixLength = 0;
2235             // foreach (const ref branchRange; _bRanges[$ - newLength .. $]) // TODO reverse isearch
2236             // {
2237             //     suffixLength += branchRange.prefixLength + 1;
2238             // }
2239             // _branchesKeyPrefix.popBackN(suffixLength);
2240             _bRanges.length = newLength;
2241         }
2242 
2243         void push(ref BranchRange branchRange)
2244         {
2245             pragma(inline, true);
2246             // branchRange.appendFrontIxsToKey(_branchesKeyPrefix);
2247             _bRanges ~= branchRange;
2248         }
2249 
2250         size_t branchCount() const @safe pure nothrow @nogc
2251         {
2252             pragma(inline, true);
2253             return _bRanges.length;
2254         }
2255 
2256         void pop()
2257         {
2258             pragma(inline, true);
2259             // _branchesKeyPrefix.popBackN(_bRanges.back.prefixLength + 1);
2260             _bRanges.popBack();
2261         }
2262 
2263         ref BranchRange bottom()
2264         {
2265             pragma(inline, true);
2266             return _bRanges.back;
2267         }
2268 
2269         private void verifyBranch1Depth()
2270         {
2271             pragma(inline, true);
2272             assert(_branch1Depth == get1DepthAt(0));
2273         }
2274 
2275         void resetBranch1Depth()
2276         {
2277             pragma(inline, true);
2278             _branch1Depth = typeof(_branch1Depth).max;
2279         }
2280 
2281         @property typeof(this) save()
2282         {
2283             typeof(this) copy;
2284             copy._bRanges = this._bRanges.dup; // ...this is inferred as @safe
2285             copy._branch1Depth = this._branch1Depth;
2286             return copy;
2287         }
2288 
2289     private:
2290         Array!BranchRange _bRanges;
2291         // Array!Ix _branchesKeyPrefix;
2292 
2293         // index to first branchrange in `_bRanges` that is currently on a leaf1
2294         // or `typeof.max` if undefined
2295         size_t _branch1Depth = size_t.max;
2296     }
2297 
2298     /** Forward (Left) Single-Directional Range over Tree. */
2299     private static struct FrontRange
2300     {
2301         @safe pure nothrow @nogc:
2302 
2303         this(Node root)
2304         {
2305             if (root)
2306             {
2307                 diveAndVisitTreeUnder(root, 0);
2308                 cacheFront();
2309             }
2310         }
2311 
2312         void popFront()
2313         {
2314             debug branchRanges.verifyBranch1Depth();
2315 
2316             if (branchRanges.hasBranch1Front) // if we're currently at leaf1 of branch
2317             {
2318                 popFrontInBranchLeaf1();
2319             }
2320             else                // if bottommost leaf should be popped
2321             {
2322                 leafNRange.popFront();
2323                 if (leafNRange.empty)
2324                 {
2325                     postPopTreeUpdate();
2326                 }
2327             }
2328 
2329             if (!empty) { cacheFront(); }
2330         }
2331 
2332         private void popFrontInBranchLeaf1() // TODO move to member of BranchRanges
2333         {
2334             branchRanges.popBranch1Front();
2335             if (branchRanges.emptyBranch1)
2336             {
2337                 branchRanges.shrinkTo(branchRanges._branch1Depth); // remove `branchRange` and all others below
2338                 branchRanges.resetBranch1Depth();
2339                 postPopTreeUpdate();
2340             }
2341             else if (!branchRanges.atLeaf1) // if not at leaf
2342             {
2343                 branchRanges._branch1Depth = branchRanges.getNext1DepthAt;
2344             }
2345             else                // still at leaf
2346             {
2347                 // nothing needed
2348             }
2349         }
2350 
2351         private void cacheFront()
2352         {
2353             _cachedFrontKey.length = 0; // not clear() because we want to keep existing allocation
2354 
2355             // branches
2356             static if (isValue)
2357             {
2358                 if (branchRanges.appendFrontIxsToKey(_cachedFrontKey,
2359                                                      _cachedFrontValue)) { return; }
2360             }
2361             else
2362             {
2363                 if (branchRanges.appendFrontIxsToKey(_cachedFrontKey)) { return; }
2364             }
2365 
2366             // leaf
2367             if (!leafNRange.empty)
2368             {
2369                 leafNRange.appendFrontIxsToKey(_cachedFrontKey);
2370                 static if (isValue)
2371                 {
2372                     _cachedFrontValue = leafNRange.value; // last should be leaf containing value
2373                 }
2374             }
2375         }
2376 
2377         // Go upwards and iterate forward in parents.
2378         private void postPopTreeUpdate()
2379         {
2380             while (branchRanges.branchCount)
2381             {
2382                 branchRanges.bottom.popFront();
2383                 if (branchRanges.bottom.empty)
2384                 {
2385                     branchRanges.pop();
2386                 }
2387                 else            // if not empty
2388                 {
2389                     if (branchRanges.bottom.atLeaf1)
2390                     {
2391                         branchRanges._branch1Depth = min(branchRanges.branchCount - 1,
2392                                                          branchRanges._branch1Depth);
2393                     }
2394                     break;      // branchRanges.bottom is not empty so break
2395                 }
2396             }
2397             if (branchRanges.branchCount &&
2398                 !branchRanges.bottom.subsEmpty) // if any sub nodes
2399             {
2400                 diveAndVisitTreeUnder(branchRanges.bottom.subFrontNode,
2401                                       branchRanges.branchCount); // visit them
2402             }
2403         }
2404 
2405         /** Find ranges of branches and leaf for all nodes under tree `root`. */
2406         private void diveAndVisitTreeUnder(Node root, size_t depth)
2407         {
2408             Node curr = root;
2409             Node next;
2410             do
2411             {
2412                 final switch (curr.typeIx) with (Node.Ix)
2413                 {
2414                 case undefined: assert(false);
2415                 case ix_OneLeafMax7:
2416                 case ix_TwoLeaf3:
2417                 case ix_TriLeaf2:
2418                 case ix_HeptLeaf1:
2419                 case ix_SparseLeaf1Ptr:
2420                 case ix_DenseLeaf1Ptr:
2421                     assert(leafNRange.empty);
2422                     leafNRange = LeafNRange(curr);
2423                     next = null; // we're done diving
2424                     break;
2425                 case ix_SparseBranchPtr:
2426                     auto curr_ = curr.as!(SparseBranch*);
2427                     auto currRange = BranchRange(curr_);
2428                     branchRanges.push(currRange);
2429                     branchRanges.updateLeaf1AtDepth(depth);
2430                     next = (curr_.subCount) ? curr_.firstSubNode : Node.init;
2431                     break;
2432                 case ix_DenseBranchPtr:
2433                     auto curr_ = curr.as!(DenseBranch*);
2434                     auto currRange = BranchRange(curr_);
2435                     branchRanges.push(currRange);
2436                     branchRanges.updateLeaf1AtDepth(depth);
2437                     next = branchRanges.bottom.subsEmpty ? Node.init : branchRanges.bottom.subFrontNode;
2438                     break;
2439                 }
2440                 curr = next;
2441                 ++depth;
2442             }
2443             while (next);
2444         }
2445 
2446         @property typeof(this) save() @trusted // TODO remove @trusted
2447         {
2448             typeof(this) copy;
2449             copy.leafNRange = this.leafNRange;
2450             copy.branchRanges = this.branchRanges.save;
2451             copy._cachedFrontKey = this._cachedFrontKey.dup;
2452             static if (isValue)
2453             {
2454                 copy._cachedFrontValue = this._cachedFrontValue;
2455             }
2456             return copy;
2457         }
2458 
2459         /** Check if empty. */
2460         bool empty() const
2461         {
2462             pragma(inline, true);
2463             return (leafNRange.empty &&
2464                     branchRanges.branchCount == 0);
2465         }
2466 
2467         /** Get front key. */
2468         auto frontKey() const @trusted
2469         {
2470             pragma(inline, true);
2471             return _cachedFrontKey[]; // TODO replace @trusted with DIP-1000 scope
2472         }
2473 
2474         static if (isValue)
2475         {
2476             /** Get front value. */
2477             auto frontValue() const
2478             {
2479                 pragma(inline, true);
2480                 return _cachedFrontValue;
2481             }
2482         }
2483 
2484     private:
2485         LeafNRange leafNRange;
2486         BranchRanges branchRanges;
2487 
2488         // cache
2489         Array!Ix _cachedFrontKey; // copy of front key
2490         static if (isValue)
2491         {
2492             Value _cachedFrontValue; // copy of front value
2493         }
2494     }
2495 
2496     /** Bi-Directional Range over Tree.
2497         Fulfills `isBidirectionalRange`.
2498     */
2499     private static struct Range
2500     {
2501         import nxt.array_algorithm : startsWith;
2502 
2503         pure nothrow @nogc:
2504 
2505         this(Node root, UKey keyPrefix)
2506         {
2507             this._rawKeyPrefix = keyPrefix;
2508 
2509             this._front = FrontRange(root);
2510             // TODO this._back = FrontRange(root);
2511 
2512             if (!empty &&
2513                 !_front.frontKey.startsWith(_rawKeyPrefix))
2514             {
2515                 popFront();
2516             }
2517         }
2518 
2519         @property typeof(this) save()
2520         {
2521             typeof(this) copy;
2522             copy._front = this._front.save;
2523             copy._back = this._back.save;
2524             copy._rawKeyPrefix = this._rawKeyPrefix;
2525             return copy;
2526         }
2527 
2528         bool empty() const
2529         {
2530             pragma(inline, true);
2531             return _front.empty; // TODO _front == _back;
2532         }
2533 
2534         auto lowKey() const
2535         {
2536             pragma(inline, true);
2537             return _front.frontKey[_rawKeyPrefix.length .. $];
2538         }
2539         auto highKey() const
2540         {
2541             pragma(inline, true);
2542             return _back.frontKey[_rawKeyPrefix.length .. $];
2543         }
2544 
2545         void popFront()
2546         {
2547             assert(!empty);
2548             do { _front.popFront(); }
2549             while (!empty &&
2550                    !_front.frontKey.startsWith(_rawKeyPrefix));
2551         }
2552 
2553         void popBack()
2554         {
2555             assert(!empty);
2556             do { _back.popFront(); }
2557             while (!empty &&
2558                    !_back.frontKey.startsWith(_rawKeyPrefix));
2559         }
2560 
2561     private:
2562         FrontRange _front;
2563         FrontRange _back;
2564         UKey _rawKeyPrefix;
2565     }
2566 
2567     /** Sparse-Branch population histogram. */
2568     alias SparseLeaf1_PopHist = size_t[radix];
2569 
2570     /** 256-Branch population histogram. */
2571     alias DenseLeaf1_PopHist = size_t[radix];
2572 
2573     /** 4-Branch population histogram.
2574         Index maps to population with value range (0 .. 4).
2575     */
2576     alias SparseBranch_PopHist = size_t[SparseBranch.maxCapacity + 1];
2577 
2578     /** Radix-Branch population histogram.
2579         Index maps to population with value range (0 .. `radix`).
2580     */
2581     alias DenseBranch_PopHist = size_t[radix + 1];
2582 
2583     /** Tree Population and Memory-Usage Statistics. */
2584     struct Stats
2585     {
2586         SparseLeaf1_PopHist popHist_SparseLeaf1;
2587         DenseLeaf1_PopHist popHist_DenseLeaf1;
2588         SparseBranch_PopHist popHist_SparseBranch; // packed branch population histogram
2589         DenseBranch_PopHist popHist_DenseBranch; // full branch population histogram
2590 
2591         import nxt.typecons_ex : IndexedArray;
2592 
2593         /** Maps `Node` type/index `Ix` to population.
2594 
2595             Used to calculate complete tree memory usage, excluding allocator
2596             overhead typically via `malloc` and `calloc`.
2597         */
2598         IndexedArray!(size_t, Node.Ix) popByNodeType;
2599         static assert(is(typeof(popByNodeType).Index == Node.Ix));
2600 
2601         IndexedArray!(size_t, Leaf1!Value.Ix) popByLeaf1Type;
2602         static assert(is(typeof(popByLeaf1Type).Index == Leaf1!Value.Ix));
2603 
2604         /** Number of heap-allocated `Node`s. Should always equal `heapAllocationBalance`. */
2605         size_t heapNodeCount;
2606 
2607         /** Number of heap-allocated `Leaf`s. Should always equal `heapLeafAllocationBalance`. */
2608         size_t heapLeafCount;
2609 
2610         size_t sparseBranchAllocatedSizeSum;
2611 
2612         size_t sparseLeaf1AllocatedSizeSum;
2613     }
2614 
2615     /** Destructively expand `curr` to a branch node able to store
2616         `capacityIncrement` more sub-nodes.
2617     */
2618     Branch expand(SparseBranch* curr, size_t capacityIncrement = 1) @safe pure nothrow @nogc
2619     {
2620         typeof(return) next;
2621         assert(curr.subCount < radix); // we shouldn't expand beyond radix
2622         if (curr.empty)     // if curr also empty length capacity must be zero
2623         {
2624             next = constructVariableSizeNode!(typeof(*curr))(1, curr); // so allocate one
2625         }
2626         else if (curr.subCount + capacityIncrement <= curr.maxCapacity) // if we can expand to curr
2627         {
2628             immutable requiredCapacity = curr.subCapacity + capacityIncrement;
2629             auto next_ = constructVariableSizeNode!(typeof(*curr))(requiredCapacity, curr);
2630             assert(next_.subCapacity >= requiredCapacity);
2631             next = next_;
2632         }
2633         else
2634         {
2635             next = constructFixedSizeNode!(DenseBranch*)(curr);
2636         }
2637         freeNode(curr);
2638         return next;
2639     }
2640 
2641     /** Destructively expand `curr` to a leaf node able to store
2642         `capacityIncrement` more sub-nodes.
2643     */
2644     Leaf1!Value expand(SparseLeaf1!Value* curr, size_t capacityIncrement = 1) @safe pure nothrow @nogc
2645     {
2646         typeof(return) next;
2647         assert(curr.length < radix); // we shouldn't expand beyond radix
2648         if (curr.empty)     // if curr also empty length capacity must be zero
2649         {
2650             next = constructVariableSizeNode!(typeof(*curr))(capacityIncrement); // make room for at least one
2651         }
2652         else if (curr.length + capacityIncrement <= curr.maxCapacity) // if we can expand to curr
2653         {
2654             immutable requiredCapacity = curr.capacity + capacityIncrement;
2655             static if (isValue)
2656             {
2657                 auto next_ = constructVariableSizeNode!(typeof(*curr))(requiredCapacity, curr.ixs, curr.values);
2658             }
2659             else
2660             {
2661                 auto next_ = constructVariableSizeNode!(typeof(*curr))(requiredCapacity, curr.ixs);
2662             }
2663             assert(next_.capacity >= requiredCapacity);
2664             next = next_;
2665         }
2666         else
2667         {
2668             static if (isValue)
2669             {
2670                 next = constructFixedSizeNode!(DenseLeaf1!Value*)(curr.ixs, curr.values); // TODO make use of sortedness of `curr.keys`?
2671             }
2672             else
2673             {
2674                 next = constructFixedSizeNode!(DenseLeaf1!Value*)(curr.ixs); // TODO make use of sortedness of `curr.keys`?
2675             }
2676         }
2677         freeNode(curr);
2678         return next;
2679     }
2680 
2681     /// ditto
2682 
2683     Branch setSub(SparseBranch* curr, UIx subIx, Node subNode) @safe pure nothrow @nogc
2684     {
2685         // debug if (willFail) { dbg("WILL FAIL: subIx:", subIx); }
2686         size_t insertionIndex;
2687         ModStatus modStatus;
2688         curr = curr.reconstructingInsert(IxSub(subIx, subNode), modStatus, insertionIndex);
2689         if (modStatus == ModStatus.maxCapacityReached) // try insert and if it fails
2690         {
2691             // we need to expand because `curr` is full
2692             auto next = expand(curr);
2693             assert(getSub(next, subIx) == Node.init); // key slot should be unoccupied
2694             return setSub(next, subIx, subNode);
2695         }
2696         return Branch(curr);
2697     }
2698 
2699     /// ditto
2700     Branch setSub(DenseBranch* curr, UIx subIx, Node subNode) @safe pure nothrow @nogc
2701     {
2702         pragma(inline, true);
2703         debug assert(!curr.subNodes[subIx], "sub-Node already set ");
2704         // "sub-Node at index " ~ subIx.to!string ~
2705         // " already set to " ~ subNode.to!string);
2706         curr.subNodes[subIx] = subNode;
2707         return Branch(curr);
2708     }
2709 
2710     /** Set sub-`Node` of branch `Node curr` at index `ix` to `subNode`. */
2711     Branch setSub(Branch curr, UIx subIx, Node subNode) @safe pure nothrow @nogc
2712     {
2713         version(LDC) pragma(inline, true);
2714         final switch (curr.typeIx) with (Branch.Ix)
2715         {
2716         case ix_SparseBranchPtr: return setSub(curr.as!(SparseBranch*), subIx, subNode);
2717         case ix_DenseBranchPtr: return setSub(curr.as!(DenseBranch*), subIx, subNode);
2718         case undefined: assert(false);
2719         }
2720     }
2721 
2722     /** Get sub-`Node` of branch `Node curr` at index `subIx`. */
2723     Node getSub(Branch curr, UIx subIx) @safe pure nothrow @nogc
2724     {
2725         version(LDC) pragma(inline, true);
2726         final switch (curr.typeIx) with (Branch.Ix)
2727         {
2728         case ix_SparseBranchPtr: return getSub(curr.as!(SparseBranch*), subIx);
2729         case ix_DenseBranchPtr: return getSub(curr.as!(DenseBranch*), subIx);
2730         case undefined: assert(false);
2731         }
2732     }
2733     /// ditto
2734     Node getSub(SparseBranch* curr, UIx subIx) @safe pure nothrow @nogc
2735     {
2736         version(LDC) pragma(inline, true);
2737         if (auto subNode = curr.subNodeAt(subIx)) { return subNode; }
2738         return Node.init;
2739     }
2740     /// ditto
2741     Node getSub(DenseBranch* curr, UIx subIx) @safe pure nothrow @nogc
2742     {
2743         pragma(inline, true);
2744         auto sub = curr.subNodes[subIx];
2745         debug curr.subNodes[subIx] = Node.init; // zero it to prevent multiple references
2746         return sub;
2747     }
2748 
2749     /** Get leaves of node `curr`. */
2750     inout(Leaf1!Value) getLeaf1(inout Branch curr) @safe pure nothrow @nogc
2751     {
2752         version(LDC) pragma(inline, true);
2753         final switch (curr.typeIx) with (Branch.Ix)
2754         {
2755         case ix_SparseBranchPtr: return curr.as!(SparseBranch*).leaf1;
2756         case ix_DenseBranchPtr: return curr.as!(DenseBranch*).leaf1;
2757         case undefined: assert(false);
2758         }
2759     }
2760 
2761     /** Set direct leaves node of node `curr` to `leaf1`. */
2762     void setLeaf1(Branch curr, Leaf1!Value leaf1) @safe pure nothrow @nogc
2763     {
2764         version(LDC) pragma(inline, true);
2765         final switch (curr.typeIx) with (Branch.Ix)
2766         {
2767         case ix_SparseBranchPtr: curr.as!(SparseBranch*).leaf1 = leaf1; break;
2768         case ix_DenseBranchPtr: curr.as!(DenseBranch*).leaf1 = leaf1; break;
2769         case undefined: assert(false);
2770         }
2771     }
2772 
2773     /** Get prefix of node `curr`. */
2774     auto getPrefix(inout Branch curr) @safe pure nothrow @nogc
2775     {
2776         version(LDC) pragma(inline, true);
2777         final switch (curr.typeIx) with (Branch.Ix)
2778         {
2779         case ix_SparseBranchPtr: return curr.as!(SparseBranch*).prefix[];
2780         case ix_DenseBranchPtr: return curr.as!(DenseBranch*).prefix[];
2781         case undefined: assert(false);
2782         }
2783     }
2784 
2785     /** Set prefix of branch node `curr` to `prefix`. */
2786     void setPrefix(Branch curr, const Ix[] prefix) @safe pure nothrow @nogc
2787     {
2788         pragma(inline, true);
2789         final switch (curr.typeIx) with (Branch.Ix)
2790         {
2791         case ix_SparseBranchPtr: curr.as!(SparseBranch*).prefix = typeof(curr.as!(SparseBranch*).prefix)(prefix); break;
2792         case ix_DenseBranchPtr: curr.as!(DenseBranch*).prefix = typeof(curr.as!(DenseBranch*).prefix)(prefix); break;
2793         case undefined: assert(false);
2794         }
2795     }
2796 
2797     /** Pop `n` from prefix. */
2798     void popFrontNPrefix(Branch curr, size_t n) @safe pure nothrow @nogc
2799     {
2800         version(LDC) pragma(inline, true);
2801         final switch (curr.typeIx) with (Branch.Ix)
2802         {
2803         case ix_SparseBranchPtr: curr.as!(SparseBranch*).prefix.popFrontN(n); break;
2804         case ix_DenseBranchPtr: curr.as!(DenseBranch*).prefix.popFrontN(n); break;
2805         case undefined: assert(false);
2806         }
2807     }
2808 
2809     /** Get number of sub-nodes of node `curr`. */
2810     SubCount getSubCount(inout Branch curr) @safe pure nothrow @nogc
2811     {
2812         version(LDC) pragma(inline, true);
2813         final switch (curr.typeIx) with (Branch.Ix)
2814         {
2815         case ix_SparseBranchPtr: return cast(typeof(return))curr.as!(SparseBranch*).subCount;
2816         case ix_DenseBranchPtr: return cast(typeof(return))curr.as!(DenseBranch*).subCount;
2817         case undefined: assert(false);
2818         }
2819     }
2820 
2821     static if (isValue)
2822     {
2823         @safe pure nothrow @nogc:
2824 
2825         /** Returns: `true` if `key` is stored under `curr`, `false` otherwise. */
2826         inout(Value*) containsAt(inout Leaf1!Value curr, UKey key)
2827         {
2828             version(LDC) pragma(inline, true);
2829             // debug if (willFail) { dbg("curr:", curr); }
2830             // debug if (willFail) { dbg("key:", key); }
2831             switch (curr.typeIx) with (Leaf1!Value.Ix)
2832             {
2833             case undefined: return null;
2834             case ix_SparseLeaf1Ptr: return key.length == 1 ? curr.as!(SparseLeaf1!Value*).contains(UIx(key[0])) : null;
2835             case ix_DenseLeaf1Ptr:  return key.length == 1 ? curr.as!(DenseLeaf1!Value*).contains(UIx(key[0])) : null;
2836             default: assert(false);
2837             }
2838         }
2839 
2840         /// ditto
2841         inout(Value*) containsAt(inout Node curr, UKey key) /* TODO make @safe */ @trusted
2842         {
2843             assert(key.length);
2844             // debug if (willFail) { dbg("key:", key); }
2845             import nxt.array_algorithm : skipOver;
2846             switch (curr.typeIx) with (Node.Ix)
2847             {
2848             case undefined: return null;
2849             case ix_SparseLeaf1Ptr: return key.length == 1 ? curr.as!(SparseLeaf1!Value*).contains(UIx(key[0])) : null;
2850             case ix_DenseLeaf1Ptr:  return key.length == 1 ? curr.as!(DenseLeaf1!Value*).contains(UIx(key[0])) : null;
2851             case ix_SparseBranchPtr:
2852                 auto curr_ = curr.as!(SparseBranch*);
2853                 if (key.skipOver(curr_.prefix[]))
2854                 {
2855                     return (key.length == 1 ?
2856                             containsAt(curr_.leaf1, key) : // in leaf
2857                             containsAt(curr_.subNodeAt(UIx(key[0])), key[1 .. $])); // recurse into branch tree
2858                 }
2859                 return null;
2860             case ix_DenseBranchPtr:
2861                 auto curr_ = curr.as!(DenseBranch*);
2862                 if (key.skipOver(curr_.prefix[]))
2863                 {
2864                     return (key.length == 1 ?
2865                             containsAt(curr_.leaf1, key) : // in leaf
2866                             containsAt(curr_.subNodes[UIx(key[0])], key[1 .. $])); // recurse into branch tree
2867                 }
2868                 return null;
2869             default: assert(false);
2870             }
2871         }
2872     }
2873     else
2874     {
2875         @safe pure nothrow @nogc:
2876         const:
2877 
2878         /** Returns: `true` if `key` is stored under `curr`, `false` otherwise. */
2879         bool containsAt(Leaf1!Value curr, UKey key)
2880         {
2881             // debug if (willFail) { dbg("key:", key); }
2882             final switch (curr.typeIx) with (Leaf1!Value.Ix)
2883             {
2884             case undefined: return false;
2885             case ix_HeptLeaf1: return curr.as!(HeptLeaf1).contains(key);
2886             case ix_SparseLeaf1Ptr: return key.length == 1 && curr.as!(SparseLeaf1!Value*).contains(UIx(key[0]));
2887             case ix_DenseLeaf1Ptr:  return key.length == 1 && curr.as!(DenseLeaf1!Value*).contains(UIx(key[0]));
2888             }
2889         }
2890         /// ditto
2891         bool containsAt(Node curr, UKey key) /* TODO make @safe */ @trusted
2892         {
2893             assert(key.length);
2894             // debug if (willFail) { dbg("key:", key); }
2895             import nxt.array_algorithm : skipOver;
2896             final switch (curr.typeIx) with (Node.Ix)
2897             {
2898             case undefined: return false;
2899             case ix_OneLeafMax7: return curr.as!(OneLeafMax7).contains(key);
2900             case ix_TwoLeaf3: return curr.as!(TwoLeaf3).contains(key);
2901             case ix_TriLeaf2: return curr.as!(TriLeaf2).contains(key);
2902             case ix_HeptLeaf1: return curr.as!(HeptLeaf1).contains(key);
2903             case ix_SparseLeaf1Ptr:
2904                 return key.length == 1 && curr.as!(SparseLeaf1!Value*).contains(UIx(key[0]));
2905             case ix_DenseLeaf1Ptr:
2906                 return key.length == 1 && curr.as!(DenseLeaf1!Value*).contains(UIx(key[0]));
2907             case ix_SparseBranchPtr:
2908                 auto curr_ = curr.as!(SparseBranch*);
2909                 return (key.skipOver(curr_.prefix) &&        // matching prefix
2910                         (key.length == 1 ?
2911                          containsAt(curr_.leaf1, key) : // in leaf
2912                          containsAt(curr_.subNodeAt(UIx(key[0])), key[1 .. $]))); // recurse into branch tree
2913             case ix_DenseBranchPtr:
2914                 auto curr_ = curr.as!(DenseBranch*);
2915                 return (key.skipOver(curr_.prefix) &&        // matching prefix
2916                         (key.length == 1 ?
2917                          containsAt(curr_.leaf1, key) : // in leaf
2918                          containsAt(curr_.subNodes[UIx(key[0])], key[1 .. $]))); // recurse into branch tree
2919             }
2920         }
2921     }
2922 
2923     inout(Node) prefixAt(inout Node curr, UKey keyPrefix, out UKey keyPrefixRest) /* TODO make @safe */ @trusted pure nothrow @nogc
2924     {
2925         import nxt.array_algorithm : startsWith;
2926         final switch (curr.typeIx) with (Node.Ix)
2927         {
2928         case undefined: return typeof(return).init; // terminate recursion
2929         case ix_OneLeafMax7:
2930             if (curr.as!(OneLeafMax7).key[].startsWith(keyPrefix)) { goto processHit; }
2931             break;
2932         case ix_TwoLeaf3:
2933             if (curr.as!(TwoLeaf3).keyLength >= keyPrefix.length) { goto processHit; }
2934             break;
2935         case ix_TriLeaf2:
2936             if (curr.as!(TriLeaf2).keyLength >= keyPrefix.length) { goto processHit; }
2937             break;
2938         case ix_HeptLeaf1:
2939             if (curr.as!(HeptLeaf1).keyLength >= keyPrefix.length) { goto processHit; }
2940             break;
2941         case ix_SparseLeaf1Ptr:
2942         case ix_DenseLeaf1Ptr:
2943             if (keyPrefix.length <= 1) { goto processHit; }
2944             break;
2945         case ix_SparseBranchPtr:
2946             auto curr_ = curr.as!(SparseBranch*);
2947             if (keyPrefix.startsWith(curr_.prefix[]))
2948             {
2949                 immutable currPrefixLength = curr_.prefix.length;
2950                 if (keyPrefix.length == currPrefixLength || // if no more prefix
2951                     (curr_.leaf1 && // both leaf1
2952                      curr_.subCount)) // and sub-nodes
2953                 {
2954                     goto processHit;
2955                 }
2956                 else if (curr_.subCount == 0) // only leaf1
2957                 {
2958                     return prefixAt(Node(curr_.leaf1),
2959                                     keyPrefix[currPrefixLength .. $],
2960                                     keyPrefixRest);
2961                 }
2962                 else        // only sub-node(s)
2963                 {
2964                     return prefixAt(curr_.subNodeAt(UIx(keyPrefix[currPrefixLength])),
2965                                     keyPrefix[currPrefixLength + 1 .. $],
2966                                     keyPrefixRest);
2967                 }
2968             }
2969             break;
2970         case ix_DenseBranchPtr:
2971             auto curr_ = curr.as!(DenseBranch*);
2972             if (keyPrefix.startsWith(curr_.prefix[]))
2973             {
2974                 immutable currPrefixLength = curr_.prefix.length;
2975                 if (keyPrefix.length == currPrefixLength || // if no more prefix
2976                     (curr_.leaf1 && // both leaf1
2977                      curr_.subCount)) // and sub-nodes
2978                 {
2979                     goto processHit;
2980                 }
2981                 else if (curr_.subCount == 0) // only leaf1
2982                 {
2983                     return prefixAt(Node(curr_.leaf1),
2984                                     keyPrefix[currPrefixLength .. $],
2985                                     keyPrefixRest);
2986                 }
2987                 else        // only sub-node(s)
2988                 {
2989                     return prefixAt(curr_.subNodes[UIx(keyPrefix[currPrefixLength])],
2990                                     keyPrefix[currPrefixLength + 1 .. $],
2991                                     keyPrefixRest);
2992                 }
2993             }
2994             break;
2995         }
2996         return typeof(return).init;
2997     processHit:
2998         keyPrefixRest = keyPrefix;
2999         return curr;
3000     }
3001 
3002     inout(Node) matchCommonPrefixAt(inout Node curr, UKey key, out UKey keyRest) /* TODO make @safe */ @trusted pure nothrow @nogc
3003     {
3004         // dbg(curr.typeIx);
3005         import nxt.array_algorithm : startsWith;
3006         final switch (curr.typeIx) with (Node.Ix)
3007         {
3008         case undefined: return typeof(return).init; // terminate recursion
3009         case ix_OneLeafMax7:
3010         case ix_TwoLeaf3:
3011         case ix_TriLeaf2:
3012         case ix_HeptLeaf1:
3013         case ix_SparseLeaf1Ptr:
3014         case ix_DenseLeaf1Ptr:
3015             goto processHit;
3016         case ix_SparseBranchPtr:
3017             auto curr_ = curr.as!(SparseBranch*);
3018             // dbg(key);
3019             // dbg(curr_.prefix[]);
3020             if (key.startsWith(curr_.prefix[]))
3021             {
3022                 immutable currPrefixLength = curr_.prefix.length;
3023                 if (key.length == currPrefixLength || // if no more prefix
3024                     (curr_.leaf1 && // both leaf1
3025                      curr_.subCount)) // and sub-nodes
3026                 {
3027                     goto processHit;
3028                 }
3029                 else if (curr_.subCount == 0) // only leaf1
3030                 {
3031                     return matchCommonPrefixAt(Node(curr_.leaf1),
3032                                                key[currPrefixLength .. $],
3033                                                keyRest);
3034                 }
3035                 else if (curr_.subCount == 1) // only one sub node
3036                 {
3037                     return matchCommonPrefixAt(curr_.subNodeAt(UIx(key[currPrefixLength])),
3038                                                key[currPrefixLength + 1 .. $],
3039                                                keyRest);
3040                 }
3041                 else
3042                 {
3043                     goto processHit;
3044                 }
3045             }
3046             break;
3047         case ix_DenseBranchPtr:
3048             auto curr_ = curr.as!(DenseBranch*);
3049             if (key.startsWith(curr_.prefix[]))
3050             {
3051                 immutable currPrefixLength = curr_.prefix.length;
3052                 if (key.length == currPrefixLength || // if no more prefix
3053                     (curr_.leaf1 && // both leaf1
3054                      curr_.subCount)) // and sub-nodes
3055                 {
3056                     goto processHit;
3057                 }
3058                 else if (curr_.subCount == 0) // only leaf1
3059                 {
3060                     return matchCommonPrefixAt(Node(curr_.leaf1),
3061                                                key[currPrefixLength .. $],
3062                                                keyRest);
3063                 }
3064                 else if (curr_.subCount == 1) // only one sub node
3065                 {
3066                     return matchCommonPrefixAt(curr_.subNodes[UIx(key[currPrefixLength])],
3067                                                key[currPrefixLength + 1 .. $],
3068                                                keyRest);
3069                 }
3070                 else
3071                 {
3072                     goto processHit;
3073                 }
3074             }
3075             break;
3076         }
3077         return typeof(return).init;
3078     processHit:
3079         keyRest = key;
3080         return curr;
3081     }
3082 
3083     size_t countHeapNodesAt(Node curr) @safe pure nothrow @nogc
3084     {
3085         size_t count = 0;
3086         final switch (curr.typeIx) with (Node.Ix)
3087         {
3088         case undefined: break;  // propagate undefined
3089         case ix_OneLeafMax7: break;
3090         case ix_TwoLeaf3: break;
3091         case ix_TriLeaf2: break;
3092         case ix_HeptLeaf1: break;
3093         case ix_SparseLeaf1Ptr:
3094         case ix_DenseLeaf1Ptr:
3095             ++count;
3096             break;
3097         case ix_SparseBranchPtr:
3098             auto curr_ = curr.as!(SparseBranch*);
3099             ++count;
3100             foreach (subNode; curr_.subNodeSlots[0 .. curr_.subCount])
3101             {
3102                 if (subNode) { count += countHeapNodesAt(subNode); }
3103             }
3104             break;
3105         case ix_DenseBranchPtr:
3106             ++count;
3107             auto curr_ = curr.as!(DenseBranch*);
3108             foreach (subNode; curr_.subNodes)
3109             {
3110                 if (subNode) { count += countHeapNodesAt(subNode); }
3111             }
3112             break;
3113         }
3114         return count;
3115     }
3116 
3117     /** Returns a duplicate of this tree with root at `curr`.
3118         Shallowly duplicates the values in the map case.
3119     */
3120     Leaf1!Value dupAt(Leaf1!Value curr) @safe pure nothrow @nogc
3121     {
3122         final switch (curr.typeIx) with (Leaf1!Value.Ix)
3123         {
3124         case undefined:         // propagate undefined
3125         case ix_HeptLeaf1: return curr; // value semantics so just copy
3126         case ix_SparseLeaf1Ptr: return typeof(return)(curr.as!(SparseLeaf1!Value*).dup);
3127         case ix_DenseLeaf1Ptr: return typeof(return)(curr.as!(DenseLeaf1!Value*).dup);
3128         }
3129     }
3130 
3131     /** Returns a duplicate of this tree with root at `curr`.
3132         Shallowly duplicates the values in the map case.
3133     */
3134     Node dupAt(Node curr) @safe pure nothrow @nogc
3135     {
3136         final switch (curr.typeIx) with (Node.Ix)
3137         {
3138         case undefined:         // propagate undefined
3139         case ix_OneLeafMax7:
3140         case ix_TwoLeaf3:
3141         case ix_TriLeaf2:
3142         case ix_HeptLeaf1: return curr; // value semantics so just copy
3143         case ix_SparseLeaf1Ptr: return typeof(return)(curr.as!(SparseLeaf1!Value*).dup);
3144         case ix_DenseLeaf1Ptr: return typeof(return)(curr.as!(DenseLeaf1!Value*).dup);
3145         case ix_SparseBranchPtr: return typeof(return)(curr.as!(SparseBranch*).dup);
3146         case ix_DenseBranchPtr: return typeof(return)(curr.as!(DenseBranch*).dup);
3147         }
3148     }
3149 
3150     static if (!isValue)
3151     {
3152         Node insertNew(UKey key, out ElementRef elementRef) @safe pure nothrow @nogc
3153         {
3154             Node next;
3155             // debug if (willFail) { dbg("WILL FAIL: key:", key); }
3156             switch (key.length)
3157             {
3158             case 0: assert(false, "key must not be empty"); // return elementRef = Node(constructFixedSizeNode!(OneLeafMax7)());
3159             case 1: next = Node(constructFixedSizeNode!(HeptLeaf1)(key[0])); break;
3160             case 2: next = Node(constructFixedSizeNode!(TriLeaf2)(key)); break;
3161             case 3: next = Node(constructFixedSizeNode!(TwoLeaf3)(key)); break;
3162             default:
3163                 if (key.length <= OneLeafMax7.capacity)
3164                 {
3165                     next = Node(constructFixedSizeNode!(OneLeafMax7)(key));
3166                     break;
3167                 }
3168                 else                // key doesn't fit in a `OneLeafMax7`
3169                 {
3170                     return Node(insertNewBranch(key, elementRef));
3171                 }
3172             }
3173             elementRef = ElementRef(next,
3174                                     UIx(0), // always first index
3175                                     ModStatus.added);
3176             return next;
3177         }
3178     }
3179 
3180     Branch insertNewBranch(Elt!Value elt, out ElementRef elementRef) @safe pure nothrow @nogc
3181     {
3182         // debug if (willFail) { dbg("WILL FAIL: elt:", elt); }
3183         auto key = eltKey!Value(elt);
3184         assert(key.length);
3185         immutable prefixLength = min(key.length - 1, // all but last Ix of key
3186                                  DefaultBranch.prefixCapacity); // as much as possible of key in branch prefix
3187         auto prefix = key[0 .. prefixLength];
3188         typeof(return) next = insertAtBelowPrefix(Branch(constructVariableSizeNode!(DefaultBranch)(1, prefix)),
3189                                                   eltKeyDropExactly!Value(elt, prefixLength), elementRef);
3190         assert(elementRef);
3191         return next;
3192     }
3193 
3194     /** Insert `key` into sub-tree under root `curr`. */
3195     Node insertAt(Node curr, Elt!Value elt, out ElementRef elementRef) @safe pure nothrow @nogc
3196     {
3197         auto key = eltKey!Value(elt);
3198         // debug if (willFail) { dbg("WILL FAIL: key:", key, " curr:", curr); }
3199         assert(key.length);
3200 
3201         if (!curr)          // if no existing `Node` to insert at
3202         {
3203             static if (isValue)
3204             {
3205                 auto next = Node(insertNewBranch(elt, elementRef));
3206             }
3207             else
3208             {
3209                 auto next = insertNew(key, elementRef);
3210             }
3211             assert(elementRef); // must be added to new Node
3212             return next;
3213         }
3214         else
3215         {
3216             final switch (curr.typeIx) with (Node.Ix)
3217             {
3218             case undefined: return typeof(return).init;
3219             case ix_OneLeafMax7:
3220                 static if (isValue)
3221                 {
3222                     assert(false);
3223                 }
3224                 else
3225                 {
3226                     return insertAt(curr.as!(OneLeafMax7), key, elementRef);
3227                 }
3228             case ix_TwoLeaf3:
3229                 static if (isValue)
3230                 {
3231                     assert(false);
3232                 }
3233                 else
3234                 {
3235                     return insertAt(curr.as!(TwoLeaf3), key, elementRef);
3236                 }
3237             case ix_TriLeaf2:
3238                 static if (isValue)
3239                 {
3240                     assert(false);
3241                 }
3242                 else
3243                 {
3244                     return insertAt(curr.as!(TriLeaf2), key, elementRef);
3245                 }
3246             case ix_HeptLeaf1:
3247                 static if (isValue)
3248                 {
3249                     assert(false);
3250                 }
3251                 else
3252                 {
3253                     return insertAt(curr.as!(HeptLeaf1), key, elementRef);
3254                 }
3255             case ix_SparseLeaf1Ptr:
3256                 return insertAtLeaf(Leaf1!Value(curr.as!(SparseLeaf1!Value*)), elt, elementRef); // TODO use toLeaf(curr)
3257             case ix_DenseLeaf1Ptr:
3258                 return insertAtLeaf(Leaf1!Value(curr.as!(DenseLeaf1!Value*)), elt, elementRef); // TODO use toLeaf(curr)
3259             case ix_SparseBranchPtr:
3260                 // debug if (willFail) { dbg("WILL FAIL: currPrefix:", curr.as!(SparseBranch*).prefix); }
3261                 return Node(insertAtAbovePrefix(Branch(curr.as!(SparseBranch*)), elt, elementRef));
3262             case ix_DenseBranchPtr:
3263                 return Node(insertAtAbovePrefix(Branch(curr.as!(DenseBranch*)), elt, elementRef));
3264             }
3265         }
3266     }
3267 
3268     /** Insert `key` into sub-tree under branch `curr` above prefix, that is
3269         the prefix of `curr` is stripped from `key` prior to insertion. */
3270     Branch insertAtAbovePrefix(Branch curr, Elt!Value elt, out ElementRef elementRef) @safe pure nothrow @nogc
3271     {
3272         auto key = eltKey!Value(elt);
3273         assert(key.length);
3274 
3275         import std.algorithm.searching : commonPrefix;
3276         auto currPrefix = getPrefix(curr);
3277         auto matchedKeyPrefix = commonPrefix(key, currPrefix);
3278 
3279         // debug if (willFail) { dbg("WILL FAIL: key:", key,
3280         //                     " curr:", curr,
3281         //                     " currPrefix:", getPrefix(curr),
3282         //                     " matchedKeyPrefix:", matchedKeyPrefix); }
3283 
3284         if (matchedKeyPrefix.length == 0) // no prefix key match
3285         {
3286             if (currPrefix.length == 0) // no current prefix
3287             {
3288                 // debug if (willFail) { dbg("WILL FAIL"); }
3289                 // NOTE: prefix:"", key:"cd"
3290                 return insertAtBelowPrefix(curr, elt, elementRef);
3291             }
3292             else  // if (currPrefix.length != 0) // non-empty current prefix
3293             {
3294                 // NOTE: prefix:"ab", key:"cd"
3295                 immutable currSubIx = UIx(currPrefix[0]); // subIx = 'a'
3296                 if (currPrefix.length == 1 && getSubCount(curr) == 0) // if `curr`-prefix become empty and only leaf pointer
3297                 {
3298                     // debug if (willFail) { dbg("WILL FAIL"); }
3299                     popFrontNPrefix(curr, 1);
3300                     curr = setSub(curr, currSubIx, Node(getLeaf1(curr))); // move it to sub
3301                     setLeaf1(curr, Leaf1!Value.init);
3302 
3303                     return insertAtBelowPrefix(curr, elt, elementRef); // directly call below because `curr`-prefix is now empty
3304                 }
3305                 else
3306                 {
3307                     // debug if (willFail) { dbg("WILL FAIL"); }
3308                     popFrontNPrefix(curr, 1);
3309                     auto next = constructVariableSizeNode!(DefaultBranch)(2, null, IxSub(currSubIx, Node(curr)));
3310                     return insertAtAbovePrefix(Branch(next), elt, elementRef);
3311                 }
3312             }
3313         }
3314         else if (matchedKeyPrefix.length < key.length)
3315         {
3316             if (matchedKeyPrefix.length == currPrefix.length)
3317             {
3318                 // debug if (willFail) { dbg("WILL FAIL"); }
3319                 // NOTE: key is an extension of prefix: prefix:"ab", key:"abcd"
3320                 return insertAtBelowPrefix(curr, eltKeyDropExactly!Value(elt, currPrefix.length), elementRef);
3321             }
3322             else
3323             {
3324                 // debug if (willFail) { dbg("WILL FAIL"); }
3325                 // NOTE: prefix and key share beginning: prefix:"ab11", key:"ab22"
3326                 immutable currSubIx = UIx(currPrefix[matchedKeyPrefix.length]); // need index first before we modify curr.prefix
3327                 popFrontNPrefix(curr, matchedKeyPrefix.length + 1);
3328                 auto next = constructVariableSizeNode!(DefaultBranch)(2, matchedKeyPrefix, IxSub(currSubIx, Node(curr)));
3329                 return insertAtBelowPrefix(Branch(next), eltKeyDropExactly!Value(elt, matchedKeyPrefix.length), elementRef);
3330             }
3331         }
3332         else // if (matchedKeyPrefix.length == key.length)
3333         {
3334             // debug if (willFail) { dbg("WILL FAIL"); }
3335             assert(matchedKeyPrefix.length == key.length);
3336             if (matchedKeyPrefix.length < currPrefix.length)
3337             {
3338                 // NOTE: prefix is an extension of key: prefix:"abcd", key:"ab"
3339                 assert(matchedKeyPrefix.length);
3340                 immutable nextPrefixLength = matchedKeyPrefix.length - 1;
3341                 immutable currSubIx = UIx(currPrefix[nextPrefixLength]); // need index first
3342                 popFrontNPrefix(curr, matchedKeyPrefix.length); // drop matchedKeyPrefix plus index to next super branch
3343                 auto next = constructVariableSizeNode!(DefaultBranch)(2, matchedKeyPrefix[0 .. $ - 1],
3344                                                                     IxSub(currSubIx, Node(curr)));
3345                 return insertAtBelowPrefix(Branch(next), eltKeyDropExactly!Value(elt, nextPrefixLength), elementRef);
3346             }
3347             else /* if (matchedKeyPrefix.length == currPrefix.length) and in turn
3348                     if (key.length == currPrefix.length */
3349             {
3350                 // NOTE: prefix equals key: prefix:"abcd", key:"abcd"
3351                 assert(matchedKeyPrefix.length);
3352                 immutable currSubIx = UIx(currPrefix[matchedKeyPrefix.length - 1]); // need index first
3353                 popFrontNPrefix(curr, matchedKeyPrefix.length); // drop matchedKeyPrefix plus index to next super branch
3354                 auto next = constructVariableSizeNode!(DefaultBranch)(2, matchedKeyPrefix[0 .. $ - 1],
3355                                                                     IxSub(currSubIx, Node(curr)));
3356                 static if (isValue)
3357                 {
3358                     return insertAtLeaf1(Branch(next), UIx(key[$ - 1]), elt.value, elementRef);
3359                 }
3360                 else
3361                 {
3362                     return insertAtLeaf1(Branch(next), UIx(key[$ - 1]), elementRef);
3363                 }
3364             }
3365         }
3366     }
3367 
3368     /** Like `insertAtAbovePrefix` but also asserts that `key` is
3369         currently not stored under `curr`. */
3370     pragma(inline) Branch insertNewAtAbovePrefix(Branch curr, Elt!Value elt) @safe pure nothrow @nogc
3371     {
3372         pragma(inline, true);
3373         ElementRef elementRef;
3374         auto next = insertAtAbovePrefix(curr, elt, elementRef);
3375         assert(elementRef);
3376         return next;
3377     }
3378 
3379     static if (isValue)
3380     {
3381         pragma(inline) Branch insertAtSubNode(Branch curr, UKey key, Value value, out ElementRef elementRef) @safe pure nothrow @nogc
3382         {
3383             // debug if (willFail) { dbg("WILL FAIL"); }
3384             immutable subIx = UIx(key[0]);
3385             return setSub(curr, subIx,
3386                           insertAt(getSub(curr, subIx), // recurse
3387                                    Elt!Value(key[1 .. $], value),
3388                                    elementRef));
3389         }
3390     }
3391     else
3392     {
3393         pragma(inline) Branch insertAtSubNode(Branch curr, UKey key, out ElementRef elementRef) @safe pure nothrow @nogc
3394         {
3395             // debug if (willFail) { dbg("WILL FAIL"); }
3396             immutable subIx = UIx(key[0]);
3397             return setSub(curr, subIx,
3398                           insertAt(getSub(curr, subIx), // recurse
3399                                    key[1 .. $],
3400                                    elementRef));
3401         }
3402     }
3403 
3404     /** Insert `key` into sub-tree under branch `curr` below prefix, that is
3405         the prefix of `curr` is not stripped from `key` prior to
3406         insertion. */
3407     Branch insertAtBelowPrefix(Branch curr, Elt!Value elt, out ElementRef elementRef) @safe pure nothrow @nogc
3408     {
3409         auto key = eltKey!Value(elt);
3410         assert(key.length);
3411         // debug if (willFail) { dbg("WILL FAIL: key:", key,
3412         //                           " curr:", curr,
3413         //                           " currPrefix:", getPrefix(curr),
3414         //                           " elementRef:", elementRef); }
3415         if (key.length == 1)
3416         {
3417             static if (isValue)
3418             {
3419                 return insertAtLeaf1(curr, UIx(key[0]), elt.value, elementRef);
3420             }
3421             else
3422             {
3423                 return insertAtLeaf1(curr, UIx(key[0]), elementRef);
3424             }
3425         }
3426         else                // key.length >= 2
3427         {
3428             static if (isValue)
3429             {
3430                 return insertAtSubNode(curr, key, elt.value, elementRef);
3431             }
3432             else
3433             {
3434                 return insertAtSubNode(curr, key, elementRef);
3435             }
3436         }
3437     }
3438 
3439     Branch insertNewAtBelowPrefix(Branch curr, Elt!Value elt) @safe pure nothrow @nogc
3440     {
3441         pragma(inline, true);
3442         ElementRef elementRef;
3443         auto next = insertAtBelowPrefix(curr, elt, elementRef);
3444         assert(elementRef);
3445         return next;
3446     }
3447 
3448     Leaf1!Value insertIxAtLeaftoLeaf(Leaf1!Value curr, IxElt!Value elt, out ElementRef elementRef) @safe pure nothrow @nogc
3449     {
3450         auto key = eltIx!Value(elt);
3451         // debug if (willFail) { dbg("WILL FAIL: elt:", elt,
3452         //                           " curr:", curr,
3453         //                           " elementRef:", elementRef); }
3454         switch (curr.typeIx) with (Leaf1!Value.Ix)
3455         {
3456         case undefined:
3457             return typeof(return).init;
3458         case ix_HeptLeaf1:
3459             static if (isValue)
3460             {
3461                 assert(false);
3462             }
3463             else
3464             {
3465                 return insertAt(curr.as!(HeptLeaf1), key, elementRef); // possibly expanded to other Leaf1!Value
3466             }
3467         case ix_SparseLeaf1Ptr:
3468             SparseLeaf1!Value* curr_ = curr.as!(SparseLeaf1!Value*);
3469             size_t index;
3470             ModStatus modStatus;
3471             curr_ = curr_.reconstructingInsert(elt, modStatus, index);
3472             curr = Leaf1!Value(curr_);
3473             final switch (modStatus)
3474             {
3475             case ModStatus.unchanged: // already stored at `index`
3476                 elementRef = ElementRef(Node(curr_), UIx(index), modStatus);
3477                 return curr;
3478             case ModStatus.added:
3479                 elementRef = ElementRef(Node(curr_), UIx(index), modStatus);
3480                 return curr;
3481             case ModStatus.maxCapacityReached:
3482                 auto next = insertIxAtLeaftoLeaf(expand(curr_, 1), // make room for one more
3483                                                  elt, elementRef);
3484                 assert(next.peek!(DenseLeaf1!Value*));
3485                 return next;
3486             case ModStatus.updated:
3487                 elementRef = ElementRef(Node(curr_), UIx(index), modStatus);
3488                 return curr;
3489             }
3490         case ix_DenseLeaf1Ptr:
3491             immutable modStatus = curr.as!(DenseLeaf1!Value*).insert(elt);
3492             static if (isValue)
3493             {
3494                 immutable ix = elt.ix;
3495             }
3496             else
3497             {
3498                 immutable ix = elt;
3499             }
3500             elementRef = ElementRef(Node(curr), ix, modStatus);
3501             break;
3502         default:
3503             assert(false, "Unsupported Leaf1!Value type " // ~ curr.typeIx.to!string
3504                 );
3505         }
3506         return curr;
3507     }
3508 
3509     static if (isValue)
3510     {
3511         Branch insertAtLeaf1(Branch curr, UIx key, Value value, out ElementRef elementRef) @safe pure nothrow @nogc
3512         {
3513             // debug if (willFail) { dbg("WILL FAIL: key:", key,
3514             //                           " value:", value,
3515             //                           " curr:", curr,
3516             //                           " currPrefix:", getPrefix(curr),
3517             //                           " elementRef:", elementRef); }
3518             if (auto leaf = getLeaf1(curr))
3519             {
3520                 setLeaf1(curr, insertIxAtLeaftoLeaf(leaf, IxElt!Value(key, value), elementRef));
3521             }
3522             else
3523             {
3524                 Ix[1] ixs = [Ix(key)]; // TODO scope
3525                 Value[1] values = [value]; // TODO scope
3526                 auto leaf_ = constructVariableSizeNode!(SparseLeaf1!Value)(1, ixs, values); // needed for values
3527                 elementRef = ElementRef(Node(leaf_), UIx(0), ModStatus.added);
3528                 setLeaf1(curr, Leaf1!Value(leaf_));
3529             }
3530             return curr;
3531         }
3532     }
3533     else
3534     {
3535         Branch insertAtLeaf1(Branch curr, UIx key, out ElementRef elementRef) @safe pure nothrow @nogc
3536         {
3537             // debug if (willFail) { dbg("WILL FAIL: key:", key,
3538             //                           " curr:", curr,
3539             //                           " currPrefix:", getPrefix(curr),
3540             //                           " elementRef:", elementRef); }
3541             if (auto leaf = getLeaf1(curr))
3542             {
3543                 setLeaf1(curr, insertIxAtLeaftoLeaf(leaf, key, elementRef));
3544             }
3545             else
3546             {
3547                 auto leaf_ = constructFixedSizeNode!(HeptLeaf1)(key); // can pack more efficiently when no value
3548                 elementRef = ElementRef(Node(leaf_), UIx(0), ModStatus.added);
3549                 setLeaf1(curr, Leaf1!Value(leaf_));
3550             }
3551             return curr;
3552         }
3553     }
3554 
3555     Node insertAtLeaf(Leaf1!Value curr, Elt!Value elt, out ElementRef elementRef) @safe pure nothrow @nogc
3556     {
3557         // debug if (willFail) { dbg("WILL FAIL: elt:", elt); }
3558         auto key = eltKey!Value(elt);
3559         assert(key.length);
3560         if (key.length == 1)
3561         {
3562             static if (isValue)
3563             {
3564                 return Node(insertIxAtLeaftoLeaf(curr, IxElt!Value(UIx(key[0]), elt.value), elementRef));
3565             }
3566             else
3567             {
3568                 return Node(insertIxAtLeaftoLeaf(curr, UIx(key[0]), elementRef));
3569             }
3570         }
3571         else
3572         {
3573             assert(key.length >= 2);
3574             immutable prefixLength = key.length - 2; // >= 0
3575             const nextPrefix = key[0 .. prefixLength];
3576             auto next = constructVariableSizeNode!(DefaultBranch)(1, nextPrefix, curr); // one sub-node and one leaf
3577             return Node(insertAtBelowPrefix(Branch(next), eltKeyDropExactly!Value(elt, prefixLength), elementRef));
3578         }
3579     }
3580 
3581     static if (!isValue)
3582     {
3583         Node insertAt(OneLeafMax7 curr, UKey key, out ElementRef elementRef) @safe pure nothrow @nogc
3584         {
3585             assert(curr.key.length);
3586             // debug if (willFail) { dbg("WILL FAIL: key:", key, " curr.key:", curr.key); }
3587 
3588             import std.algorithm.searching : commonPrefix;
3589             auto matchedKeyPrefix = commonPrefix(key, curr.key);
3590             if (curr.key.length == key.length)
3591             {
3592                 if (matchedKeyPrefix.length == key.length) // curr.key, key and matchedKeyPrefix all equal
3593                 {
3594                     return Node(curr); // already stored in `curr`
3595                 }
3596                 else if (matchedKeyPrefix.length + 1 == key.length) // key and curr.key are both matchedKeyPrefix plus one extra
3597                 {
3598                     // TODO functionize:
3599                     Node next;
3600                     switch (matchedKeyPrefix.length)
3601                     {
3602                     case 0:
3603                         next = constructFixedSizeNode!(HeptLeaf1)(curr.key[0], key[0]);
3604                         elementRef = ElementRef(next, UIx(1), ModStatus.added);
3605                         break;
3606                     case 1:
3607                         next = constructFixedSizeNode!(TriLeaf2)(curr.key, key);
3608                         elementRef = ElementRef(next, UIx(1), ModStatus.added);
3609                         break;
3610                     case 2:
3611                         next = constructFixedSizeNode!(TwoLeaf3)(curr.key, key);
3612                         elementRef = ElementRef(next, UIx(1), ModStatus.added);
3613                         break;
3614                     default:
3615                         const nextPrefix = matchedKeyPrefix[0 .. min(matchedKeyPrefix.length,
3616                                                                      DefaultBranch.prefixCapacity)]; // limit prefix branch capacity
3617                         Branch nextBranch = constructVariableSizeNode!(DefaultBranch)(1 + 1, // `curr` and `key`
3618                                                                                     nextPrefix);
3619                         nextBranch = insertNewAtBelowPrefix(nextBranch, curr.key[nextPrefix.length .. $]);
3620                         nextBranch = insertAtBelowPrefix(nextBranch, key[nextPrefix.length .. $], elementRef);
3621                         assert(elementRef);
3622                         next = Node(nextBranch);
3623                         break;
3624                     }
3625                     freeNode(curr);
3626                     return next;
3627                 }
3628             }
3629 
3630             return Node(insertAtAbovePrefix(expand(curr), key, elementRef));
3631         }
3632 
3633         Node insertAt(TwoLeaf3 curr, UKey key, out ElementRef elementRef) @safe pure nothrow @nogc
3634         {
3635             if (curr.keyLength == key.length)
3636             {
3637                 if (curr.contains(key)) { return Node(curr); }
3638                 if (!curr.keys.full)
3639                 {
3640                     assert(curr.keys.length == 1);
3641                     elementRef = ElementRef(Node(curr), UIx(curr.keys.length), ModStatus.added);
3642                     curr.keys.pushBack(key);
3643                     return Node(curr);
3644                 }
3645             }
3646             return Node(insertAtAbovePrefix(expand(curr), key, elementRef)); // NOTE stay at same (depth)
3647         }
3648 
3649         Node insertAt(TriLeaf2 curr, UKey key, out ElementRef elementRef) @safe pure nothrow @nogc
3650         {
3651             if (curr.keyLength == key.length)
3652             {
3653                 if (curr.contains(key)) { return Node(curr); }
3654                 if (!curr.keys.full)
3655                 {
3656                     elementRef = ElementRef(Node(curr), UIx(curr.keys.length), ModStatus.added);
3657                     curr.keys.pushBack(key);
3658                     return Node(curr);
3659                 }
3660             }
3661             return Node(insertAtAbovePrefix(expand(curr), key, elementRef)); // NOTE stay at same (depth)
3662         }
3663 
3664         Leaf1!Value insertAt(HeptLeaf1 curr, UIx key, out ElementRef elementRef) @safe pure nothrow @nogc
3665         {
3666             if (curr.contains(key)) { return Leaf1!Value(curr); }
3667             if (!curr.keys.full)
3668             {
3669                 elementRef = ElementRef(Node(curr), UIx(curr.keys.back), ModStatus.added);
3670                 curr.keys.pushBack(cast(Ix)key);
3671                 return Leaf1!Value(curr);
3672             }
3673             else            // curr is full
3674             {
3675                 assert(curr.keys.length == curr.capacity);
3676 
3677                 // pack `curr.keys` plus `key` into `nextKeys`
3678                 Ix[curr.capacity + 1] nextKeys;
3679                 nextKeys[0 .. curr.capacity] = curr.keys;
3680                 nextKeys[curr.capacity] = cast(Ix)key;
3681 
3682                 import std.algorithm.sorting : sort;
3683                 sort(nextKeys[]); // TODO move this sorting elsewhere
3684 
3685                 auto next = constructVariableSizeNode!(SparseLeaf1!Value)(nextKeys.length, nextKeys[]);
3686                 elementRef = ElementRef(Node(next), UIx(curr.capacity), ModStatus.added);
3687 
3688                 freeNode(curr);
3689                 return Leaf1!Value(next);
3690             }
3691         }
3692 
3693         Node insertAt(HeptLeaf1 curr, UKey key, out ElementRef elementRef)
3694             @safe pure nothrow @nogc
3695         {
3696             if (curr.keyLength == key.length)
3697             {
3698                 return Node(insertAt(curr, UIx(key[0]), elementRef)); // use `Ix key`-overload
3699             }
3700             return insertAt(Node(constructVariableSizeNode!(DefaultBranch)(1, Leaf1!Value(curr))), // current `key`
3701                             key, elementRef); // NOTE stay at same (depth)
3702         }
3703 
3704         /** Split `curr` using `prefix`. */
3705         Node split(OneLeafMax7 curr, UKey prefix, UKey key) // TODO key here is a bit malplaced
3706             @safe pure nothrow @nogc
3707         {
3708             assert(key.length);
3709 
3710             if (curr.key.length == key.length) // balanced tree possible
3711             {
3712                 switch (curr.key.length)
3713                 {
3714                 case 1:
3715                     if (prefix.length == 0)
3716                     {
3717                         freeNode(curr);
3718                         return Node(constructFixedSizeNode!(HeptLeaf1)(curr.key)); // TODO removing parameter has no effect. why?
3719                     }
3720                     break;
3721                 case 2:
3722                     freeNode(curr);
3723                     return Node(constructFixedSizeNode!(TriLeaf2)(curr.key));
3724                 case 3:
3725                     freeNode(curr);
3726                     return Node(constructFixedSizeNode!(TwoLeaf3)(curr.key));
3727                 default:
3728                     break;
3729                 }
3730             }
3731 
3732             // default case
3733             Branch next = constructVariableSizeNode!(DefaultBranch)(1 + 1, prefix); // current plus one more
3734             next = insertNewAtBelowPrefix(next, curr.key[prefix.length .. $]);
3735             freeNode(curr);   // remove old current
3736 
3737             return Node(next);
3738         }
3739 
3740         /** Destructively expand `curr` to make room for `capacityIncrement` more keys and return it. */
3741         Branch expand(OneLeafMax7 curr, size_t capacityIncrement = 1)
3742             @safe pure nothrow @nogc
3743         {
3744             assert(curr.key.length >= 2);
3745             typeof(return) next;
3746 
3747             if (curr.key.length <= DefaultBranch.prefixCapacity + 1) // if `key` fits in `prefix` of `DefaultBranch`
3748             {
3749                 next = constructVariableSizeNode!(DefaultBranch)(1 + capacityIncrement, curr.key[0 .. $ - 1], // all but last
3750                                                                      Leaf1!Value(constructFixedSizeNode!(HeptLeaf1)(curr.key.back))); // last as a leaf
3751             }
3752             else                // curr.key.length > DefaultBranch.prefixCapacity + 1
3753             {
3754                 next = constructVariableSizeNode!(DefaultBranch)(1 + capacityIncrement, curr.key[0 .. DefaultBranch.prefixCapacity]);
3755                 next = insertNewAtBelowPrefix(next, curr.key[DefaultBranch.prefixCapacity .. $]);
3756             }
3757 
3758             freeNode(curr);
3759             return next;
3760         }
3761 
3762         /** Destructively expand `curr` to make room for `capacityIncrement` more keys and return it. */
3763         Branch expand(TwoLeaf3 curr, size_t capacityIncrement = 1)
3764             @safe pure nothrow @nogc
3765         {
3766             typeof(return) next;
3767             if (curr.keys.length == 1) // only one key
3768             {
3769                 next = constructVariableSizeNode!(DefaultBranch)(1 + capacityIncrement);
3770                 next = insertNewAtAbovePrefix(next, // current keys plus one more
3771                                               curr.keys.at!0);
3772             }
3773             else
3774             {
3775                 next = constructVariableSizeNode!(DefaultBranch)(curr.keys.length + capacityIncrement, curr.prefix);
3776                 // TODO functionize and optimize to insertNewAtAbovePrefix(next, curr.keys)
3777                 foreach (key; curr.keys)
3778                 {
3779                     next = insertNewAtBelowPrefix(next, key[curr.prefix.length .. $]);
3780                 }
3781             }
3782             freeNode(curr);
3783             return next;
3784         }
3785 
3786         /** Destructively expand `curr` to make room for `capacityIncrement` more keys and return it. */
3787         Branch expand(TriLeaf2 curr, size_t capacityIncrement = 1)
3788             @safe pure nothrow @nogc
3789         {
3790             typeof(return) next;
3791             if (curr.keys.length == 1) // only one key
3792             {
3793                 next = constructVariableSizeNode!(DefaultBranch)(1 + capacityIncrement); // current keys plus one more
3794                 next = insertNewAtAbovePrefix(next, curr.keys.at!0);
3795             }
3796             else
3797             {
3798                 next = constructVariableSizeNode!(DefaultBranch)(curr.keys.length + capacityIncrement, curr.prefix);
3799                 // TODO functionize and optimize to insertNewAtAbovePrefix(next, curr.keys)
3800                 foreach (key; curr.keys)
3801                 {
3802                     next = insertNewAtBelowPrefix(next, key[curr.prefix.length .. $]);
3803                 }
3804             }
3805             freeNode(curr);
3806             return next;
3807         }
3808 
3809         /** Destructively expand `curr` making room for `nextKey` and return it. */
3810         Node expand(HeptLeaf1 curr, size_t capacityIncrement = 1)
3811         {
3812             auto next = constructVariableSizeNode!(SparseLeaf1!Value)(curr.keys.length + capacityIncrement, curr.keys);
3813             freeNode(curr);
3814             return Node(next);
3815         }
3816     }
3817 
3818     @safe pure nothrow @nogc
3819     {
3820         pragma(inline, true) void release(SparseLeaf1!Value* curr) { freeNode(curr); }
3821         pragma(inline, true) void release(DenseLeaf1!Value* curr) { freeNode(curr); }
3822 
3823         void release(SparseBranch* curr)
3824         {
3825             foreach (immutable sub; curr.subNodes[0 .. curr.subCount])
3826             {
3827                 release(sub); // recurse branch
3828             }
3829             if (curr.leaf1)
3830             {
3831                 release(curr.leaf1); // recurse leaf
3832             }
3833             freeNode(curr);
3834         }
3835 
3836         void release(DenseBranch* curr)
3837         {
3838             foreach (immutable sub; curr.subNodes[].filter!(sub => sub)) // TODO use static foreach
3839             {
3840                 release(sub); // recurse branch
3841             }
3842             if (curr.leaf1)
3843             {
3844                 release(curr.leaf1); // recurse leaf
3845             }
3846             freeNode(curr);
3847         }
3848 
3849         pragma(inline, true) void release(OneLeafMax7 curr) { freeNode(curr); }
3850         pragma(inline, true) void release(TwoLeaf3 curr) { freeNode(curr); }
3851         pragma(inline, true) void release(TriLeaf2 curr) { freeNode(curr); }
3852         pragma(inline, true) void release(HeptLeaf1 curr) { freeNode(curr); }
3853 
3854         /** Release `Leaf1!Value curr`. */
3855         void release(Leaf1!Value curr)
3856         {
3857             final switch (curr.typeIx) with (Leaf1!Value.Ix)
3858             {
3859             case undefined: break; // ignored
3860             case ix_HeptLeaf1: return release(curr.as!(HeptLeaf1));
3861             case ix_SparseLeaf1Ptr: return release(curr.as!(SparseLeaf1!Value*));
3862             case ix_DenseLeaf1Ptr: return release(curr.as!(DenseLeaf1!Value*));
3863             }
3864         }
3865 
3866         /** Release `Node curr`. */
3867         void release(Node curr)
3868         {
3869             final switch (curr.typeIx) with (Node.Ix)
3870             {
3871             case undefined: break; // ignored
3872             case ix_OneLeafMax7: return release(curr.as!(OneLeafMax7));
3873             case ix_TwoLeaf3: return release(curr.as!(TwoLeaf3));
3874             case ix_TriLeaf2: return release(curr.as!(TriLeaf2));
3875             case ix_HeptLeaf1: return release(curr.as!(HeptLeaf1));
3876             case ix_SparseLeaf1Ptr: return release(curr.as!(SparseLeaf1!Value*));
3877             case ix_DenseLeaf1Ptr: return release(curr.as!(DenseLeaf1!Value*));
3878             case ix_SparseBranchPtr: return release(curr.as!(SparseBranch*));
3879             case ix_DenseBranchPtr: return release(curr.as!(DenseBranch*));
3880             }
3881         }
3882 
3883         bool isHeapAllocatedNode(const Node curr)
3884         {
3885             final switch (curr.typeIx) with (Node.Ix)
3886             {
3887             case undefined: return false;
3888             case ix_OneLeafMax7: return false;
3889             case ix_TwoLeaf3: return false;
3890             case ix_TriLeaf2: return false;
3891             case ix_HeptLeaf1: return false;
3892             case ix_SparseLeaf1Ptr: return true;
3893             case ix_DenseLeaf1Ptr: return true;
3894             case ix_SparseBranchPtr: return true;
3895             case ix_DenseBranchPtr: return true;
3896             }
3897         }
3898     }
3899 
3900     void printAt(Node curr, size_t depth, uint subIx = uint.max) @safe
3901     {
3902         import std.range : repeat;
3903         import std.stdio : write, writeln;
3904 
3905         if (!curr) { return; }
3906 
3907         foreach (immutable i; 0 .. depth) { write('-'); } // prefix
3908         if (subIx != uint.max)
3909         {
3910             import std.string : format;
3911             write(format("%.2X ", subIx));
3912         }
3913 
3914         final switch (curr.typeIx) with (Node.Ix)
3915         {
3916         case undefined:
3917             assert(false, "Trying to print Node.undefined");
3918         case ix_OneLeafMax7:
3919             auto curr_ = curr.as!(OneLeafMax7);
3920             import std.conv : to;
3921             writeln(typeof(curr_).stringof, " #", curr_.key.length, ": ", curr_.to!string);
3922             break;
3923         case ix_TwoLeaf3:
3924             auto curr_ = curr.as!(TwoLeaf3);
3925             writeln(typeof(curr_).stringof, " #", curr_.keys.length, ": ", curr_.keys);
3926             break;
3927         case ix_TriLeaf2:
3928             auto curr_ = curr.as!(TriLeaf2);
3929             writeln(typeof(curr_).stringof, " #", curr_.keys.length, ": ", curr_.keys);
3930             break;
3931         case ix_HeptLeaf1:
3932             auto curr_ = curr.as!(HeptLeaf1);
3933             writeln(typeof(curr_).stringof, " #", curr_.keys.length, ": ", curr_.keys);
3934             break;
3935         case ix_SparseLeaf1Ptr:
3936             auto curr_ = curr.as!(SparseLeaf1!Value*);
3937             write(typeof(*curr_).stringof, " #", curr_.length, "/", curr_.capacity, " @", curr_);
3938             write(": ");
3939             bool other = false;
3940             foreach (immutable i, immutable ix; curr_.ixs)
3941             {
3942                 string s;
3943                 if (other)
3944                 {
3945                     s ~= keySeparator;
3946                 }
3947                 else
3948                 {
3949                     other = true;
3950                 }
3951                 import std.string : format;
3952                 s ~= format("%.2X", ix);
3953                 write(s);
3954                 static if (isValue)
3955                 {
3956                     write("=>", curr_.values[i]);
3957                 }
3958             }
3959             writeln();
3960             break;
3961         case ix_DenseLeaf1Ptr:
3962             auto curr_ = curr.as!(DenseLeaf1!Value*);
3963             write(typeof(*curr_).stringof, " #", curr_.count, " @", curr_);
3964             write(": ");
3965 
3966             // keys
3967             size_t ix = 0;
3968             bool other = false;
3969             if (curr_._ixBits.allOne)
3970             {
3971                 write("ALL");
3972             }
3973             else
3974             {
3975                 foreach (immutable keyBit; curr_._ixBits[])
3976                 {
3977                     string s;
3978                     if (keyBit)
3979                     {
3980                         if (other)
3981                         {
3982                             s ~= keySeparator;
3983                         }
3984                         else
3985                         {
3986                             other = true;
3987                         }
3988                         import std.string : format;
3989                         s ~= format("%.2X", ix);
3990                     }
3991                     write(s);
3992                     static if (isValue)
3993                     {
3994                         write("=>", curr_.values[ix]);
3995                     }
3996                     ++ix;
3997                 }
3998             }
3999 
4000             writeln();
4001             break;
4002         case ix_SparseBranchPtr:
4003             auto curr_ = curr.as!(SparseBranch*);
4004             write(typeof(*curr_).stringof, " #", curr_.subCount, "/", curr_.subCapacity, " @", curr_);
4005             if (!curr_.prefix.empty) { write(" prefix=", curr_.prefix.toString('_')); }
4006             writeln(":");
4007             if (curr_.leaf1)
4008             {
4009                 printAt(Node(curr_.leaf1), depth + 1);
4010             }
4011             foreach (immutable i, immutable subNode; curr_.subNodes)
4012             {
4013                 printAt(subNode, depth + 1, cast(uint)curr_.subIxs[i]);
4014             }
4015             break;
4016         case ix_DenseBranchPtr:
4017             auto curr_ = curr.as!(DenseBranch*);
4018             write(typeof(*curr_).stringof, " #", curr_.subCount, "/", radix, " @", curr_);
4019             if (!curr_.prefix.empty) { write(" prefix=", curr_.prefix.toString('_')); }
4020             writeln(":");
4021             if (curr_.leaf1)
4022             {
4023                 printAt(Node(curr_.leaf1), depth + 1);
4024             }
4025             foreach (immutable i, immutable subNode; curr_.subNodes)
4026             {
4027                 printAt(subNode, depth + 1, cast(uint)i);
4028             }
4029 
4030             break;
4031         }
4032     }
4033 
4034     struct RawRadixTree
4035     {
4036         alias NodeType = Node;
4037         alias BranchType = Branch;
4038         alias DefaultBranchType = DefaultBranch;
4039         alias ValueType = Value;
4040         alias RangeType = Range;
4041         alias StatsType = Stats;
4042         alias SparseBranchType = SparseBranch;
4043         alias DenseBranchType = DenseBranch;
4044         alias ElementRefType = ElementRef;
4045 
4046         import core.lifetime : emplace;
4047 
4048         /** Is `true` if this tree stores values of type `Value` along with keys. In
4049             other words: `this` is a $(I map) rather than a $(I set).
4050         */
4051         alias hasValue = isValue;
4052 
4053         Range opSlice() pure nothrow // TODO DIP-1000 scope
4054         {
4055             pragma(inline, true);
4056             return Range(_root, []);
4057         }
4058 
4059         /** Returns a duplicate of this tree if present.
4060             Shallowly duplicates the values in the map case.
4061         */
4062         typeof(this) dup() @trusted
4063         {
4064             pragma(inline, true);
4065             return typeof(return)(dupAt(_root), length);
4066         }
4067 
4068         Stats usageHistograms() const
4069         {
4070             if (!_root) { return typeof(return).init; }
4071             typeof(return) stats;
4072             _root.calculate!(Value)(stats);
4073             return stats;
4074         }
4075 
4076         @disable this(this);
4077 
4078         ~this() @nogc
4079         {
4080             pragma(inline, true);
4081             release(_root);
4082             debug _root = Node.init;
4083         }
4084 
4085         /** Removes all contents (elements). */
4086         void clear() @nogc
4087         {
4088             pragma(inline, true);
4089             release(_root);
4090             _root = null;
4091             _length = 0;
4092         }
4093 
4094         @safe pure nothrow @nogc
4095         {
4096             /** Returns: `true` if `key` is stored, `false` otherwise. */
4097             inout(Node) prefix(UKey keyPrefix, out UKey keyPrefixRest) inout
4098             {
4099                 pragma(inline, true);
4100                 return prefixAt(_root, keyPrefix, keyPrefixRest);
4101             }
4102 
4103 
4104             /** Lookup deepest node having whose key starts with `key`. */
4105             inout(Node) matchCommonPrefix(UKey key, out UKey keyRest) inout
4106             {
4107                 pragma(inline, true);
4108                 return matchCommonPrefixAt(_root, key, keyRest);
4109             }
4110 
4111             static if (isValue)
4112             {
4113                 /** Returns: `true` if `key` is stored, `false` otherwise. */
4114                 inout(Value*) contains(UKey key) inout
4115                 {
4116                     pragma(inline, true);
4117                     return containsAt(_root, key);
4118                 }
4119             }
4120             else
4121             {
4122                 /** Returns: `true` if `key` is stored, `false` otherwise. */
4123                 bool contains(UKey key) const
4124                 {
4125                     pragma(inline, true);
4126                     return containsAt(_root, key);
4127                 }
4128             }
4129 
4130             /** Insert `key` into `this` tree. */
4131             static if (isValue)
4132             {
4133                 Node insert(UKey key, in Value value, out ElementRef elementRef)
4134                 {
4135                     version(LDC) pragma(inline, true);
4136                     return _root = insertAt(_root, Elt!Value(key, value), elementRef);
4137                 }
4138             }
4139             else
4140             {
4141                 Node insert(UKey key, out ElementRef elementRef)
4142                 {
4143                     version(LDC) pragma(inline, true);
4144                     return _root = insertAt(_root, key, elementRef);
4145                 }
4146             }
4147 
4148             size_t countHeapNodes()
4149             {
4150                 pragma(inline, true);
4151                 return countHeapNodesAt(_root);
4152             }
4153 
4154             /** Returns: `true` iff tree is empty (no elements stored). */
4155             bool empty() const
4156             {
4157                 pragma(inline, true);
4158                 return !_root;
4159             }
4160 
4161             /** Returns: number of elements stored. */
4162             size_t length() const
4163             {
4164                 pragma(inline, true);
4165                 return _length;
4166             }
4167 
4168             Node rootNode() const
4169             {
4170                 pragma(inline, true);
4171                 return _root;
4172             }
4173 
4174         private:
4175             /** Returns: number of nodes used in `this` tree. Should always equal `Stats.heapNodeCount`. */
4176             // debug size_t heapAllocationBalance() { return _heapAllocBalance; }
4177         }
4178 
4179         void print() @safe const
4180         {
4181             printAt(_root, 0);
4182         }
4183 
4184         Node root()
4185         {
4186             pragma(inline, true);
4187             return _root;
4188         }
4189 
4190     private:
4191         Node _root;
4192         size_t _length; /// Number of elements (keys or key-value-pairs) currently stored under `_root`
4193     }
4194 }
4195 
4196 /** Append statistics of tree under `Node` `curr.` into `stats`.
4197  */
4198 static private void calculate(Value)(RawRadixTree!(Value).NodeType curr,
4199                                      ref RawRadixTree!(Value).StatsType stats)
4200     @safe pure nothrow /* TODO @nogc */
4201 {
4202     alias RT = RawRadixTree!(Value);
4203     ++stats.popByNodeType[curr.typeIx];
4204 
4205     final switch (curr.typeIx) with (RT.NodeType.Ix)
4206     {
4207     case undefined: break;
4208     case ix_OneLeafMax7: break; // TODO calculate()
4209     case ix_TwoLeaf3: break; // TODO calculate()
4210     case ix_TriLeaf2: break; // TODO calculate()
4211     case ix_HeptLeaf1: break; // TODO calculate()
4212     case ix_SparseLeaf1Ptr:
4213         ++stats.heapNodeCount;
4214         const curr_ = curr.as!(SparseLeaf1!Value*);
4215         assert(curr_.length);
4216         ++stats.popHist_SparseLeaf1[curr_.length - 1]; // TODO type-safe indexing
4217         stats.sparseLeaf1AllocatedSizeSum += curr_.allocatedSize;
4218         break;
4219     case ix_DenseLeaf1Ptr:
4220         const curr_ = curr.as!(DenseLeaf1!Value*);
4221         ++stats.heapNodeCount;
4222         immutable count = curr_._ixBits.countOnes; // number of non-zero sub-nodes
4223         assert(count <= curr_.capacity);
4224         ++stats.popHist_DenseLeaf1[count - 1]; // TODO type-safe indexing
4225         break;
4226     case ix_SparseBranchPtr:
4227         ++stats.heapNodeCount;
4228         curr.as!(RT.SparseBranchType*).calculate(stats);
4229         break;
4230     case ix_DenseBranchPtr:
4231         ++stats.heapNodeCount;
4232         curr.as!(RT.DenseBranchType*).calculate(stats);
4233         break;
4234     }
4235 }
4236 
4237 /** Append statistics of tree under `Leaf1!Value` `curr.` into `stats`.
4238  */
4239 static private void calculate(Value)(Leaf1!Value curr,
4240                                      ref RawRadixTree!(Value).StatsType stats)
4241     @safe pure nothrow /* TODO @nogc */
4242 {
4243     alias RT = RawRadixTree!(Value);
4244     ++stats.popByLeaf1Type[curr.typeIx];
4245 
4246     final switch (curr.typeIx) with (Leaf1!Value.Ix)
4247     {
4248     case undefined: break;
4249     case ix_HeptLeaf1: break; // TODO calculate()
4250     case ix_SparseLeaf1Ptr:
4251         ++stats.heapNodeCount;
4252         const curr_ = curr.as!(SparseLeaf1!Value*);
4253         assert(curr_.length);
4254         ++stats.popHist_SparseLeaf1[curr_.length - 1]; // TODO type-safe indexing
4255         break;
4256     case ix_DenseLeaf1Ptr:
4257         const curr_ = curr.as!(DenseLeaf1!Value*);
4258         ++stats.heapNodeCount;
4259         immutable count = curr_._ixBits.countOnes; // number of non-zero curr-nodes
4260         assert(count <= curr_.capacity);
4261         assert(count);
4262         ++stats.popHist_DenseLeaf1[count - 1]; // TODO type-safe indexing
4263         break;
4264     }
4265 }
4266 
4267 /** Remap fixed-length typed key `typedKey` to raw (untyped) key of type `UKey`.
4268     TODO DIP-1000 scope
4269 */
4270 UKey toFixedRawKey(TypedKey)(const scope TypedKey typedKey, UKey preallocatedFixedUKey) @trusted
4271 {
4272     enum radix = 2^^span;     // branch-multiplicity, typically either 2, 4, 16 or 256
4273     immutable key_ = typedKey.bijectToUnsigned;
4274 
4275     static assert(key_.sizeof == TypedKey.sizeof);
4276 
4277     enum nbits = 8*key_.sizeof; // number of bits in key
4278     enum chunkCount = nbits/span; // number of chunks in key_
4279     static assert(chunkCount*span == nbits, "Bitsize of TypedKey must be a multiple of span:" ~ span.stringof);
4280 
4281     // big-endian storage
4282     foreach (immutable i; 0 .. chunkCount) // for each chunk index
4283     {
4284         immutable bitShift = (chunkCount - 1 - i)*span; // most significant bit chunk first (MSBCF)
4285         preallocatedFixedUKey[i] = (key_ >> bitShift) & (radix - 1); // part of value which is also an index
4286     }
4287 
4288     return preallocatedFixedUKey[];
4289 }
4290 
4291 /** Remap typed key `typedKey` to raw (untyped) key of type `UKey`.
4292  */
4293 UKey toRawKey(TypedKey)(in TypedKey typedKey, ref Array!Ix rawUKey) @trusted
4294 if (isTrieableKeyType!TypedKey)
4295 {
4296     enum radix = 2^^span;     // branch-multiplicity, typically either 2, 4, 16 or 256
4297 
4298     import nxt.container_traits : isAddress;
4299     static if (isAddress!TypedKey)
4300     {
4301         static assert(0, "Shift TypedKey " ~ TypedKey.stringof ~ " down by its alignment before returning");
4302     }
4303 
4304     static if (isFixedTrieableKeyType!TypedKey)
4305     {
4306         rawUKey.length = TypedKey.sizeof;
4307         return typedKey.toFixedRawKey(rawUKey[]);
4308     }
4309     else static if (isArray!TypedKey)
4310     {
4311         alias EType = Unqual!(typeof(TypedKey.init[0]));
4312         static if (is(EType == char)) // TODO extend to support isTrieableKeyType!TypedKey
4313         {
4314             import std.string : representation;
4315             const ubyte[] ukey = typedKey.representation; // lexical byte-order
4316             return cast(Ix[])ukey;                        // TODO needed?
4317         }
4318         else static if (is(EType == wchar))
4319         {
4320             immutable ushort[] rKey = typedKey.representation; // lexical byte-order.
4321             // TODO MSByte-order of elements in rKey for ordered access and good branching performance
4322             immutable ubyte[] ukey = (cast(const ubyte*)rKey.ptr)[0 .. rKey[0].sizeof * rKey.length]; // TODO @trusted functionize. Reuse existing Phobos function?
4323             return ukey;
4324         }
4325         else static if (is(EType == dchar))
4326         {
4327             immutable uint[] rKey = typedKey.representation; // lexical byte-order
4328             // TODO MSByte-order of elements in rKey for ordered access and good branching performance
4329             immutable ubyte[] ukey = (cast(const ubyte*)rKey.ptr)[0 .. rKey[0].sizeof * rKey.length]; // TODO @trusted functionize. Reuse existing Phobos function?
4330             return ukey;
4331         }
4332         else static if (isFixedTrieableKeyType!E)
4333         {
4334             static assert(false, "TODO Convert array of typed fixed keys");
4335         }
4336         else
4337         {
4338             static assert(false, "TODO Handle typed key " ~ TypedKey.stringof);
4339         }
4340     }
4341     else static if (is(TypedKey == struct))
4342     {
4343         static if (TypedKey.tupleof.length == 1) // TypedKey is a wrapper type
4344         {
4345             return typedKey.tupleof[0].toRawKey(rawUKey);
4346         }
4347         else
4348         {
4349             enum members = __traits(allMembers, TypedKey);
4350             foreach (immutable i, immutable memberName; members) // for each member name in `struct TypedKey`
4351             {
4352                 immutable member = __traits(getMember, typedKey, memberName); // member
4353                 alias MemberType = typeof(member);
4354 
4355                 static if (i + 1 == members.length) // last member is allowed to be an array of fixed length
4356                 {
4357                     Array!Ix memberRawUKey;
4358                     const memberRawKey = member.toRawKey(memberRawUKey); // TODO DIP-1000 scope
4359                     rawUKey ~= memberRawUKey;
4360                 }
4361                 else                // non-last member must be fixed
4362                 {
4363                     static assert(isFixedTrieableKeyType!MemberType,
4364                                   "Non-last " ~ i.stringof ~ ":th member of type " ~ MemberType.stringof ~ " must be of fixed length");
4365                     Ix[MemberType.sizeof] memberRawUKey;
4366                     const memberRawKey = member.toFixedRawKey(memberRawUKey); // TODO DIP-1000 scope
4367                     rawUKey ~= memberRawUKey[];
4368                 }
4369             }
4370             return rawUKey[]; // TODO return immutable slice
4371         }
4372     }
4373     else
4374     {
4375         static assert(false, "TODO Handle typed key " ~ TypedKey.stringof);
4376     }
4377 }
4378 
4379 /** Remap raw untyped key `ukey` to typed key of type `TypedKey`. */
4380 inout(TypedKey) toTypedKey(TypedKey)(inout(Ix)[] ukey) @trusted
4381 if (isTrieableKeyType!TypedKey)
4382 {
4383     import nxt.container_traits : isAddress;
4384     static if (isAddress!TypedKey)
4385     {
4386         static assert(0, "Shift TypedKey " ~ TypedKey.stringof ~ " up by its alignment before returning");
4387     }
4388 
4389     static if (isFixedTrieableKeyType!TypedKey)
4390     {
4391         enum nbits = 8*TypedKey.sizeof; // number of bits in key
4392         enum chunkCount = nbits/span; // number of chunks in key_
4393         static assert(chunkCount*span == nbits, "Bitsize of TypedKey must be a multiple of span:" ~ span.stringof);
4394 
4395         // TODO reuse existing trait UnsignedOf!TypedKey
4396         static      if (TypedKey.sizeof == 1) { alias RawKey = ubyte; }
4397         else static if (TypedKey.sizeof == 2) { alias RawKey = ushort; }
4398         else static if (TypedKey.sizeof == 4) { alias RawKey = uint; }
4399         else static if (TypedKey.sizeof == 8) { alias RawKey = ulong; }
4400 
4401         RawKey bKey = 0;
4402 
4403         // big-endian storage
4404         foreach (immutable i; 0 .. chunkCount) // for each chunk index
4405         {
4406             immutable RawKey uix = cast(RawKey)ukey[i];
4407             immutable bitShift = (chunkCount - 1 - i)*span; // most significant bit chunk first (MSBCF)
4408             bKey |= uix << bitShift; // part of value which is also an index
4409         }
4410 
4411         TypedKey typedKey;
4412         bKey.bijectFromUnsigned(typedKey);
4413         return typedKey;
4414     }
4415     else static if (isArray!TypedKey)
4416     {
4417         static if (isArray!TypedKey &&
4418                    is(Unqual!(typeof(TypedKey.init[0])) == char))
4419         {
4420             static assert(char.sizeof == Ix.sizeof);
4421             return cast(inout(char)[])ukey;
4422         }
4423         // TODO handle wchar and dchar
4424         else
4425         {
4426             static assert(false, "TODO Handle typed key " ~ TypedKey.stringof);
4427         }
4428     }
4429     else static if (is(TypedKey == struct))
4430     {
4431         static if (TypedKey.tupleof.length == 1) // TypedKey is a wrapper type
4432         {
4433             alias WrappedTypedKey = typeof(TypedKey.tupleof[0]);
4434             return TypedKey(ukey.toTypedKey!(WrappedTypedKey));
4435         }
4436         else
4437         {
4438             TypedKey typedKey;
4439             size_t ix = 0;
4440             enum members = __traits(allMembers, TypedKey);
4441             foreach (immutable i, immutable memberName; members) // for each member name in `struct TypedKey`
4442             {
4443                 alias MemberType = typeof(__traits(getMember, typedKey, memberName));
4444 
4445                 static if (i + 1 != members.length) // last member is allowed to be an array of fixed length
4446                 {
4447                     static assert(isFixedTrieableKeyType!MemberType,
4448                                   "Non-last MemberType must be fixed length");
4449                 }
4450                 __traits(getMember, typedKey, memberName) = ukey[ix .. ix + MemberType.sizeof].toTypedKey!MemberType;
4451                 ix += MemberType.sizeof;
4452             }
4453             return typedKey;
4454         }
4455     }
4456     else
4457     {
4458         static assert(false, "TODO Handle typed key " ~ TypedKey.stringof);
4459     }
4460 }
4461 
4462 /** Radix-Tree with key of type `K` and value of type `V` (if non-`void`). */
4463 struct RadixTree(K, V)
4464 if (isTrieableKeyType!(K))
4465 {
4466     alias RawTree = RawRadixTree!(V);
4467 
4468     alias KeyType = K;
4469     alias ValueType = V;
4470 
4471     static if (RawTree.hasValue)
4472     {
4473         /** Element type with typed key and value. */
4474         struct TypedElt
4475         {
4476             K key;
4477             V value;
4478         }
4479 
4480         ref V opIndex(K key)
4481         {
4482             pragma(inline, true);
4483             V* value = contains(key);
4484             version(assert)
4485             {
4486                 import core.exception : RangeError;
4487                 if (value is null) { throw new RangeError("Range violation"); }
4488             }
4489             return *value;
4490         }
4491 
4492         auto opIndexAssign(in V value, K key)
4493         {
4494             _rawTree.ElementRefType elementRef; // reference to where element was added
4495 
4496             Array!Ix rawUKey;
4497             auto rawKey = key.toRawKey(rawUKey); // TODO DIP-1000 scope
4498 
4499             _rawTree.insert(rawKey, value, elementRef);
4500 
4501             immutable bool added = elementRef.node && elementRef.modStatus == ModStatus.added;
4502             _length += added;
4503             /* TODO return reference (via `auto ref` return typed) to stored
4504                value at `elementRef` instead, unless packed static_bitarray storage is used
4505                when `V is bool` */
4506             return value;
4507         }
4508 
4509         /** Insert `key`.
4510             Returns: `true` if `key` wasn't previously added, `false` otherwise.
4511         */
4512         bool insert(in K key, in V value) @nogc
4513         {
4514             _rawTree.ElementRefType elementRef; // indicates that key was added
4515 
4516             Array!Ix rawUKey;
4517             auto rawKey = key.toRawKey(rawUKey); // TODO DIP-1000 scope
4518 
4519             _rawTree.insert(rawKey, value, elementRef);
4520 
4521             // debug if (willFail) { dbg("WILL FAIL: elementRef:", elementRef, " key:", key); }
4522             if (elementRef.node)  // if `key` was added at `elementRef`
4523             {
4524                 // set value
4525                 final switch (elementRef.node.typeIx) with (_rawTree.NodeType.Ix)
4526                 {
4527                 case undefined:
4528                 case ix_OneLeafMax7:
4529                 case ix_TwoLeaf3:
4530                 case ix_TriLeaf2:
4531                 case ix_HeptLeaf1:
4532                 case ix_SparseBranchPtr:
4533                 case ix_DenseBranchPtr:
4534                     assert(false);
4535                 case ix_SparseLeaf1Ptr:
4536                     elementRef.node.as!(SparseLeaf1!V*).setValue(UIx(rawKey[$ - 1]), value);
4537                     break;
4538                 case ix_DenseLeaf1Ptr:
4539                     elementRef.node.as!(DenseLeaf1!V*).setValue(UIx(rawKey[$ - 1]), value);
4540                     break;
4541                 }
4542                 immutable bool hit = elementRef.modStatus == ModStatus.added;
4543                 _length += hit;
4544                 return hit;
4545             }
4546             else
4547             {
4548                 assert(false, "TODO warning no elementRef for key:"/*, key, " rawKey:", rawKey*/);
4549             }
4550         }
4551 
4552         /** Returns: pointer to value if `key` is contained in set, null otherwise. */
4553         inout(V*) contains(in K key) inout @nogc
4554         {
4555             version(LDC) pragma(inline, true);
4556             Array!Ix rawUKey;
4557             auto rawKey = key.toRawKey(rawUKey); // TODO DIP-1000 scope
4558             return _rawTree.contains(rawKey);
4559         }
4560 
4561         /** AA-style key-value range. */
4562         Range byKeyValue() @nogc // TODO inout?, TODO DIP-1000 scope
4563         {
4564             pragma(inline, true);
4565             return this.opSlice;
4566         }
4567     }
4568     else
4569     {
4570         @nogc:
4571         alias TypedElt = K;
4572 
4573         /** Insert `key`.
4574             Returns: `true` if `key` wasn't previously added, `false` otherwise.
4575         */
4576         bool insert(K key)
4577         {
4578             _rawTree.ElementRefType elementRef; // indicates that elt was added
4579 
4580             Array!Ix rawUKey;
4581             auto rawKey = key.toRawKey(rawUKey); // TODO DIP-1000 scope
4582 
4583             _rawTree.insert(rawKey, elementRef);
4584 
4585             immutable bool hit = elementRef.node && elementRef.modStatus == ModStatus.added;
4586             _length += hit;
4587             return hit;
4588         }
4589 
4590         nothrow:
4591 
4592         /** Returns: `true` if `key` is stored, `false` otherwise. */
4593         bool contains(in K key) inout
4594         {
4595             version(LDC) pragma(inline, true);
4596             Array!Ix rawUKey;
4597             auto rawKey = key.toRawKey(rawUKey); // TODO DIP-1000 scope
4598             return _rawTree.contains(rawKey);
4599         }
4600 
4601         /** AA-style key range. */
4602         Range byKey() @nogc // TODO inout?. TODO DIP-1000 scope
4603         {
4604             pragma(inline, true);
4605             return this.opSlice;
4606         }
4607     }
4608 
4609     /** Supports $(B `K` in `this`) syntax. */
4610     auto opBinaryRight(string op)(in K key) inout
4611         if (op == "in")
4612     {
4613         pragma(inline, true);
4614         return contains(key);   // TODO return `_rawTree.ElementRefType`
4615     }
4616 
4617     Range opSlice() @system @nogc // TODO inout?
4618     {
4619         version(LDC) pragma(inline, true);
4620         return Range(_root, []);
4621     }
4622 
4623     /** Get range over elements whose key starts with `keyPrefix`.
4624         The element equal to `keyPrefix` is return as an empty instance of the type.
4625      */
4626     auto prefix(K keyPrefix) @system
4627     {
4628         Array!Ix rawUKey;
4629         auto rawKeyPrefix = keyPrefix.toRawKey(rawUKey);
4630 
4631         UKey rawKeyPrefixRest;
4632         auto prefixedRootNode = _rawTree.prefix(rawKeyPrefix, rawKeyPrefixRest);
4633 
4634         return Range(prefixedRootNode,
4635                      rawKeyPrefixRest);
4636     }
4637 
4638     /** Typed Range. */
4639     private static struct Range
4640     {
4641         @nogc:
4642 
4643         this(RawTree.NodeType root,
4644              UKey keyPrefixRest)
4645         {
4646             pragma(inline, true);
4647             _rawRange = _rawTree.RangeType(root, keyPrefixRest);
4648         }
4649 
4650         TypedElt front() const
4651         {
4652             pragma(inline, true);
4653             static if (RawTree.hasValue)
4654             {
4655                 return typeof(return)(_rawRange.lowKey.toTypedKey!K,
4656                                       _rawRange._front._cachedFrontValue);
4657             }
4658             else
4659             {
4660                 return _rawRange.lowKey.toTypedKey!K;
4661             }
4662         }
4663 
4664         TypedElt back() const
4665         {
4666             pragma(inline, true);
4667             static if (RawTree.hasValue)
4668             {
4669                 return typeof(return)(_rawRange.highKey.toTypedKey!K,
4670                                       _rawRange._back._cachedFrontValue);
4671             }
4672             else
4673             {
4674                 return _rawRange.highKey.toTypedKey!K;
4675             }
4676         }
4677 
4678         @property typeof(this) save()
4679         {
4680             version(LDC) pragma(inline, true);
4681             typeof(return) copy;
4682             copy._rawRange = this._rawRange.save;
4683             return copy;
4684         }
4685 
4686         RawTree.RangeType _rawRange;
4687         alias _rawRange this;
4688     }
4689 
4690     /** Raw Range. */
4691     private static struct RawRange
4692     {
4693         this(RawTree.NodeType root,
4694              UKey keyPrefixRest)
4695         {
4696             pragma(inline, true);
4697             this._rawRange = _rawTree.RangeType(root, keyPrefixRest);
4698         }
4699 
4700         static if (RawTree.hasValue)
4701         {
4702             const(Elt!V) front() const
4703             {
4704                 pragma(inline, true);
4705                 return typeof(return)(_rawRange.lowKey,
4706                                       _rawRange._front._cachedFrontValue);
4707             }
4708 
4709             const(Elt!V) back() const
4710             {
4711                 pragma(inline, true);
4712                 return typeof(return)(_rawRange.highKey,
4713                                       _rawRange._back._cachedFrontValue);
4714             }
4715         }
4716         else
4717         {
4718             const(Elt!V) front() const
4719             {
4720                 pragma(inline, true);
4721                 return _rawRange.lowKey;
4722             }
4723             const(Elt!V) back() const
4724             {
4725                 pragma(inline, true);
4726                 return _rawRange.highKey;
4727             }
4728         }
4729 
4730         @property typeof(this) save()
4731         {
4732             pragma(inline, true);
4733             typeof(return) copy;
4734             copy._rawRange = this._rawRange.save;
4735             return copy;
4736         }
4737 
4738         RawTree.RangeType _rawRange;
4739         alias _rawRange this;
4740     }
4741 
4742     /** This function searches with policy `sp` to find the largest right subrange
4743         on which pred(value, x) is true for all x (e.g., if pred is "less than",
4744         returns the portion of the range with elements strictly greater than
4745         value).
4746         TODO Add template param (SearchPolicy sp)
4747 
4748         TODO replace `matchCommonPrefix` with something more clever directly
4749         finds the next element after rawKey and returns a TreeRange at that point
4750     */
4751     auto upperBound(K key) @system
4752     {
4753         Array!Ix rawUKey;
4754         auto rawKey = key.toRawKey(rawUKey);
4755 
4756         UKey rawKeyRest;
4757         auto prefixedRootNode = _rawTree.matchCommonPrefix(rawKey, rawKeyRest);
4758 
4759         return UpperBoundRange(prefixedRootNode,
4760                                rawKey[0 .. $ - rawKeyRest.length],
4761                                rawKeyRest);
4762     }
4763 
4764     /** Typed Upper Bound Range.
4765      */
4766     private static struct UpperBoundRange
4767     {
4768         @nogc:
4769 
4770         this(RawTree.NodeType root,
4771              UKey rawKeyPrefix, UKey rawKeyRest)
4772         {
4773             _rawRange = _rawTree.RangeType(root, []);
4774             _rawKeyPrefix = rawKeyPrefix;
4775 
4776             // skip values
4777             import std.algorithm.comparison : cmp;
4778             while (!_rawRange.empty &&
4779                    cmp(_rawRange._front.frontKey, rawKeyRest) <= 0)
4780             {
4781                 _rawRange.popFront();
4782             }
4783         }
4784 
4785         TypedElt front()
4786         {
4787             Array!Ix wholeRawKey = _rawKeyPrefix.dup;
4788             wholeRawKey ~= _rawRange.lowKey;
4789             static if (RawTree.hasValue)
4790             {
4791                 return typeof(return)(wholeRawKey[].toTypedKey!K,
4792                                       _rawRange._front._cachedFrontValue);
4793             }
4794             else
4795             {
4796                 return wholeRawKey[].toTypedKey!K;
4797             }
4798         }
4799 
4800         @property typeof(this) save()
4801         {
4802             pragma(inline, true);
4803             typeof(return) copy;
4804             copy._rawRange = this._rawRange.save;
4805             copy._rawKeyPrefix = this._rawKeyPrefix.dup;
4806             return copy;
4807         }
4808 
4809         RawTree.RangeType _rawRange;
4810         alias _rawRange this;
4811         Array!Ix _rawKeyPrefix;
4812     }
4813 
4814     static if (RawTree.hasValue)
4815     {
4816         import std.algorithm.iteration : map;
4817 
4818         // /** Get range of map keys. */
4819         // auto byKey()
4820         // {
4821         //     return this[].map!(e => e[0]);
4822         // }
4823 
4824         // /** Get range of map values. */
4825         // auto byValue()
4826         // {
4827         //     return this[].map!(e => e[1]);
4828         // }
4829     }
4830 
4831     /** Returns a duplicate of this tree.
4832         Shallowly duplicates the values in the map case.
4833     */
4834     typeof(this) dup()
4835     {
4836         return typeof(this)(this._rawTree.dup);
4837     }
4838 
4839     RawTree _rawTree;
4840     alias _rawTree this;
4841 }
4842 alias PatriciaTrie = RadixTree;
4843 alias RadixTrie = RadixTree;
4844 alias CompactPrefixTree = RadixTree;
4845 
4846 template RadixTreeSet(K)
4847 if (isTrieableKeyType!(K))
4848 {
4849     alias RadixTreeSet = RadixTree!(K, void);
4850 }
4851 
4852 /** Print `tree`. */
4853 void print(Key, Value)(const ref RadixTree!(Key, Value) tree) @safe
4854 if (isTrieableKeyType!(K))
4855 {
4856     print(tree._rawTree);
4857 }
4858 
4859 /** Keys are stored in a way that they can't be accessed by reference so we
4860     allow array (and string) keys to be of mutable type.
4861 */
4862 template MutableKey(Key)
4863 {
4864     static if (isArray!Key)
4865     {
4866         alias MutableKey = const(Unqual!(typeof(Key.init[0])))[];
4867     }
4868     else
4869     {
4870         alias MutableKey = Key;
4871     }
4872 }
4873 
4874 /** Instantiator for the set-version of `RadixTree` where value-type is `void` (unused). */
4875 RadixTree!(MutableKey!Key, void) radixTreeSet(Key)() @nogc
4876 {
4877     return typeof(return)();
4878 }
4879 
4880 /** Instantiator for the map-version of `RadixTree` where value-type is `Value`. */
4881 RadixTree!(MutableKey!Key, Value) radixTreeMap(Key, Value)()
4882 {
4883     return typeof(return)();
4884 }
4885 
4886 @safe pure nothrow @nogc unittest
4887 { version(showAssertTags) dbg();
4888     version(enterSingleInfiniteMemoryLeakTest)
4889     {
4890         while (true)
4891         {
4892             checkNumeric!(bool, float, double,
4893                           long, int, short, byte,
4894                           ulong, uint, ushort, ubyte);
4895         }
4896     }
4897     else
4898     {
4899         checkNumeric!(bool, float, double,
4900                       long, int, short, byte,
4901                       ulong, uint, ushort, ubyte);
4902     }
4903 }
4904 
4905 /// exercise all switch-cases in `RawRadixTree.prefixAt()`
4906 /*TODO @safe*/ pure nothrow
4907 /*TODO:@nogc*/ unittest
4908 { version(showAssertTags) dbg();
4909     import std.algorithm.comparison : equal;
4910     alias Key = string;
4911     auto set = radixTreeSet!(Key);
4912 
4913     set.clear();
4914     set.insert(`-----1`);
4915     assert(set.prefix(`-----`).equal([`1`]));
4916     assert(set.prefix(`-----_`).empty);
4917     assert(set.prefix(`-----____`).empty);
4918     set.insert(`-----2`);
4919     assert(set.prefix(`-----`).equal([`1`, `2`]));
4920     assert(set.prefix(`-----_`).empty);
4921     assert(set.prefix(`-----____`).empty);
4922     set.insert(`-----3`);
4923     assert(set.prefix(`-----`).equal([`1`, `2`, `3`]));
4924     assert(set.prefix(`-----_`).empty);
4925     assert(set.prefix(`-----____`).empty);
4926     set.insert(`-----4`);
4927     assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`]));
4928     assert(set.prefix(`-----_`).empty);
4929     assert(set.prefix(`-----____`).empty);
4930     set.insert(`-----5`);
4931     assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`]));
4932     assert(set.prefix(`-----_`).empty);
4933     assert(set.prefix(`-----____`).empty);
4934     set.insert(`-----6`);
4935     assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`, `6`]));
4936     assert(set.prefix(`-----_`).empty);
4937     assert(set.prefix(`-----____`).empty);
4938     set.insert(`-----7`);
4939     assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`, `6`, `7`]));
4940     assert(set.prefix(`-----_`).empty);
4941     assert(set.prefix(`-----____`).empty);
4942     set.insert(`-----8`);
4943     assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`]));
4944     assert(set.prefix(`-----_`).empty);
4945     assert(set.prefix(`-----____`).empty);
4946     set.insert(`-----11`);
4947     assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `3`, `4`, `5`, `6`, `7`, `8`]));
4948     set.insert(`-----22`);
4949     assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `22`, `3`, `4`, `5`, `6`, `7`, `8`]));
4950     set.insert(`-----33`);
4951     assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `22`, `3`, `33`, `4`, `5`, `6`, `7`, `8`]));
4952     set.insert(`-----44`);
4953     assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `22`, `3`, `33`, `4`, `44`, `5`, `6`, `7`, `8`]));
4954 
4955     set.clear();
4956     set.insert(`-----11`);
4957     assert(set.prefix(`-----`).equal([`11`]));
4958     set.insert(`-----22`);
4959     assert(set.prefix(`-----`).equal([`11`, `22`]));
4960     assert(set.prefix(`-----_`).empty);
4961     assert(set.prefix(`-----___`).empty);
4962 
4963     set.clear();
4964     set.insert(`-----111`);
4965     assert(set.prefix(`-----`).equal([`111`]));
4966     set.insert(`-----122`);
4967     assert(set.prefix(`-----`).equal([`111`, `122`]));
4968     set.insert(`-----133`);
4969     assert(set.prefix(`-----`).equal([`111`, `122`, `133`]));
4970     assert(set.prefix(`-----1`).equal([`11`, `22`, `33`]));
4971     assert(set.prefix(`-----1_`).empty);
4972     assert(set.prefix(`-----1___`).empty);
4973 
4974     set.clear();
4975     set.insert(`-----1111`);
4976     assert(set.prefix(`-----`).equal([`1111`]));
4977     assert(set.prefix(`-----_`).empty);
4978     assert(set.prefix(`-----___`).empty);
4979 
4980     set.clear();
4981     set.insert(`-----11111`);
4982     assert(set.prefix(`-----`).equal([`11111`]));
4983     assert(set.prefix(`-----_`).empty);
4984     assert(set.prefix(`-----___`).empty);
4985     set.insert(`-----12222`);
4986     assert(set.prefix(`-----`).equal([`11111`, `12222`]));
4987     assert(set.prefix(`-----_`).empty);
4988     assert(set.prefix(`-----___`).empty);
4989     assert(set.prefix(`-----12`).equal([`222`]));
4990     assert(set.prefix(`-----12_`).empty);
4991     assert(set.prefix(`-----12____`).empty);
4992 }
4993 
4994 /// test floating-point key range sortedness
4995 /*@ TODO safe */ pure nothrow @nogc unittest
4996 { version(showAssertTags) dbg();
4997     alias T = double;
4998 
4999     auto set = radixTreeSet!(T);
5000 
5001     import std.range: isForwardRange;
5002     static assert(isForwardRange!(typeof(set[])));
5003 
5004     set.insert(-1.1e6);
5005     set.insert(-2.2e9);
5006     set.insert(-1.1);
5007     set.insert(+2.2);
5008     set.insert(T.max);
5009     set.insert(-T.max);
5010     set.insert(-3.3);
5011     set.insert(-4.4);
5012     set.insert(+4.4);
5013     set.insert(+3.3);
5014     set.insert(+1.1e6);
5015     set.insert(+2.2e9);
5016 
5017     import std.algorithm.sorting : isSorted;
5018     assert(set[].isSorted);
5019 }
5020 
5021 version(unittest)
5022 auto testScalar(uint span, Keys...)() @safe
5023 if (Keys.length != 0)
5024 {
5025     import std.traits : isIntegral, isFloatingPoint;
5026     import std.range : iota;
5027     foreach (Key; Keys)
5028     {
5029         auto set = radixTreeSet!(Key);
5030 
5031         static if (isIntegral!Key)
5032         {
5033             immutable low = max(Key.min, -1_000);
5034             immutable high = min(Key.max, 1_000);
5035             immutable factor = 1;
5036         }
5037         else static if (isFloatingPoint!Key)
5038         {
5039             immutable low = -1_000;
5040             immutable high = 1_000;
5041             immutable factor = 1;
5042         }
5043         else static if (is(Key == bool))
5044         {
5045             immutable low = false;
5046             immutable high = true;
5047             immutable factor = 1;
5048         }
5049 
5050         foreach (immutable uk_; low.iota(high + 1))
5051         {
5052             immutable uk = factor*uk_;
5053             immutable Key key = cast(Key)uk;
5054             assert(set.insert(key));  // insert new value returns `true` (previously not in set)
5055             assert(!set.insert(key)); // reinsert same value returns `false` (already in set)
5056         }
5057 
5058         import std.algorithm.comparison : equal;
5059         import std.algorithm.iteration : map;
5060         () @trusted { assert(set[].equal(low.iota(high + 1).map!(uk => cast(Key)uk))); } (); // TODO remove @trusted when opSlice support DIP-1000
5061 
5062         import std.algorithm.sorting : isSorted;
5063         () @trusted { assert(set[].isSorted); } (); // TODO remove @trusted when opSlice support DIP-1000
5064     }
5065 }
5066 
5067 ///
5068 @safe pure nothrow @nogc unittest
5069 { version(showAssertTags) dbg();
5070     testScalar!(8,
5071                 bool,
5072                 double, float,
5073                 long, int, short, byte,
5074                 ulong, uint, ushort, ubyte);
5075 }
5076 
5077 ///
5078 @safe pure nothrow @nogc unittest
5079 { version(showAssertTags) dbg();
5080     alias Key = ubyte;
5081     auto set = radixTreeSet!(Key);
5082 
5083     assert(!set.root);
5084 
5085     foreach (immutable i; 0 .. 7)
5086     {
5087         assert(!set.contains(i));
5088         assert(set.insert(i));
5089         assert(set.root.peek!HeptLeaf1);
5090     }
5091 
5092     foreach (immutable i; 7 .. 48)
5093     {
5094         assert(!set.contains(i));
5095         assert(set.insert(i));
5096         assert(set.root.peek!(SparseLeaf1!void*));
5097     }
5098 
5099     foreach (immutable i; 48 .. 256)
5100     {
5101         assert(!set.contains(i));
5102         assert(set.insert(i));
5103         assert(set.root.peek!(DenseLeaf1!void*));
5104     }
5105 }
5106 
5107 /** Calculate and print statistics of `tree`. */
5108 void showStatistics(RT)(const ref RT tree) // why does `in`RT tree` trigger a copy ctor here
5109 {
5110     import std.conv : to;
5111     import std.stdio : writeln;
5112 
5113     auto stats = tree.usageHistograms;
5114 
5115     writeln("Population By Node Type: ", stats.popByNodeType);
5116     writeln("Population By Leaf1 Type: ", stats.popByLeaf1Type);
5117 
5118     writeln("SparseBranch Population Histogram: ", stats.popHist_SparseBranch);
5119     writeln("DenseBranch Population Histogram: ", stats.popHist_DenseBranch);
5120 
5121     writeln("SparseLeaf1 Population Histogram: ", stats.popHist_SparseLeaf1);
5122     writeln("DenseLeaf1 Population Histogram: ", stats.popHist_DenseLeaf1);
5123 
5124     size_t totalBytesUsed = 0;
5125 
5126     // Node-usage
5127     foreach (const index, const pop; stats.popByNodeType) // TODO use stats.byPair when added to typecons_ex.d
5128     {
5129         size_t bytesUsed = 0;
5130         const ix = cast(RT.NodeType.Ix)index;
5131         final switch (ix) with (RT.NodeType.Ix)
5132         {
5133         case undefined: continue; // ignore
5134         case ix_OneLeafMax7:
5135             bytesUsed = pop*OneLeafMax7.sizeof;
5136             break;
5137         case ix_TwoLeaf3:
5138             bytesUsed = pop*TwoLeaf3.sizeof;
5139             break;
5140         case ix_TriLeaf2:
5141             bytesUsed = pop*TriLeaf2.sizeof;
5142             break;
5143         case ix_HeptLeaf1:
5144             bytesUsed = pop*HeptLeaf1.sizeof;
5145             break;
5146         case ix_SparseLeaf1Ptr:
5147             bytesUsed = stats.sparseLeaf1AllocatedSizeSum; // variable length struct
5148             totalBytesUsed += bytesUsed;
5149             break;
5150         case ix_DenseLeaf1Ptr:
5151             bytesUsed = pop*DenseLeaf1!(RT.ValueType).sizeof;
5152             totalBytesUsed += bytesUsed;
5153             break;
5154         case ix_SparseBranchPtr:
5155             bytesUsed = stats.sparseBranchAllocatedSizeSum; // variable length struct
5156             totalBytesUsed += bytesUsed;
5157             break;
5158         case ix_DenseBranchPtr:
5159             bytesUsed = pop*RT.DenseBranchType.sizeof;
5160             totalBytesUsed += bytesUsed;
5161             break;
5162         }
5163         writeln(pop, " number of ",
5164                 ix.to!string[3 .. $], // TODO Use RT.NodeType.indexTypeName(ix)
5165                 " uses ", bytesUsed/1e6, " megabytes");
5166     }
5167 
5168     writeln("Tree uses ", totalBytesUsed/1e6, " megabytes");
5169 }
5170 
5171 /// test map from `uint` to values of type `double`
5172 @safe pure nothrow @nogc unittest
5173 { version(showAssertTags) dbg();
5174     alias Key = uint;
5175     alias Value = uint;
5176 
5177     auto map = radixTreeMap!(Key, Value);
5178     assert(map.empty);
5179 
5180     static assert(map.hasValue);
5181 
5182     static Value keyToValue(Key key) @safe pure nothrow @nogc { return cast(Value)((key + 1)*radix); }
5183 
5184     foreach (immutable i; 0 .. SparseLeaf1!Value.maxCapacity)
5185     {
5186         assert(!map.contains(i));
5187         assert(map.length == i);
5188         map[i] = keyToValue(i);
5189         assert(map.contains(i));
5190         assert(*map.contains(i) == keyToValue(i));
5191         assert(i in map);
5192         assert(*(i in map) == keyToValue(i));
5193         assert(map.length == i + 1);
5194     }
5195 
5196     foreach (immutable i; SparseLeaf1!Value.maxCapacity .. radix)
5197     {
5198         assert(!map.contains(i));
5199         assert(map.length == i);
5200         map[i] = keyToValue(i);
5201         assert(map.contains(i));
5202         assert(*map.contains(i) == keyToValue(i));
5203         assert(i in map);
5204         assert(*(i in map) == keyToValue(i));
5205         assert(map.length == i + 1);
5206     }
5207 
5208     void testRange() @trusted
5209     {
5210         size_t i = 0;
5211         foreach (immutable keyValue; map[])
5212         {
5213             assert(keyValue.key == i);
5214             assert(keyValue.value == keyToValue(cast(Key)i)); // TODO use typed key instead of cast(Key)
5215             ++i;
5216         }
5217     }
5218 
5219     testRange();
5220 }
5221 
5222 /** Check string types in `Keys`. */
5223 auto testString(Keys...)(size_t count, uint maxLength)
5224 if (Keys.length != 0)
5225 {
5226     void testContainsAndInsert(Set, Key)(ref Set set, Key key)
5227     if (isSomeString!Key)
5228     {
5229         import std.conv : to;
5230         immutable failMessage = `Failed for key: "` ~ key.to!string ~ `"`;
5231 
5232         assert(!set.contains(key), failMessage);
5233 
5234         assert(set.insert(key), failMessage);
5235         assert(set.contains(key), failMessage);
5236 
5237         assert(!set.insert(key), failMessage);
5238         assert(set.contains(key), failMessage);
5239     }
5240 
5241     import std.algorithm.comparison : equal;
5242     import std.datetime.stopwatch : StopWatch, AutoStart;
5243 
5244     foreach (Key; Keys)
5245     {
5246         auto set = radixTreeSet!(Key);
5247         assert(set.empty);
5248 
5249         immutable sortedKeys = randomUniqueSortedStrings(count, maxLength);
5250 
5251         auto sw1 = StopWatch(AutoStart.yes);
5252         foreach (immutable key; sortedKeys)
5253         {
5254             testContainsAndInsert(set, key);
5255         }
5256         sw1.stop;
5257 
5258         version(show)
5259         {
5260             import std.conv : to;
5261             version(show) import std.stdio : writeln;
5262             version(show) writeln("Test for contains and insert took ", sw1.peek());
5263         }
5264 
5265         auto sw2 = StopWatch(AutoStart.yes);
5266 
5267         void testPrefix() @trusted
5268         {
5269             assert(set[].equal(sortedKeys));
5270             import std.algorithm.iteration : filter, map;
5271             assert(set.prefix("a")
5272                       .equal(sortedKeys.filter!(x => x.length && x[0] == 'a')
5273                                        .map!(x => x[1 .. $])));
5274             assert(set.prefix("aa")
5275                       .equal(sortedKeys.filter!(x => x.length >= 2 && x[0] == 'a' && x[1] == 'a')
5276                                        .map!(x => x[2 .. $])));
5277         }
5278 
5279         testPrefix();
5280 
5281         sw2.stop;
5282         version(show)
5283         {
5284             import std.stdio : writeln;
5285             writeln("Compare took ", sw2.peek());
5286         }
5287     }
5288 }
5289 
5290 ///
5291 @safe /* TODO pure nothrow @nogc */
5292 unittest
5293 { version(showAssertTags) dbg();
5294     testString!(string)(512, 8);
5295     testString!(string)(512, 32);
5296 }
5297 
5298 /// test map to values of type `bool`
5299 @safe pure nothrow @nogc unittest
5300 { version(showAssertTags) dbg();
5301     alias Key = uint;
5302     alias Value = bool;
5303 
5304     auto map = radixTreeMap!(Key, Value);
5305     assert(map.empty);
5306 
5307     static assert(map.hasValue);
5308     map.insert(Key.init, Value.init);
5309 }
5310 
5311 /// test packing of set elements
5312 @safe pure nothrow @nogc unittest
5313 { version(showAssertTags) dbg();
5314     auto set = radixTreeSet!(ulong);
5315     enum N = HeptLeaf1.capacity;
5316 
5317     foreach (immutable i; 0 .. N)
5318     {
5319         assert(!set.contains(i));
5320 
5321         assert(set.insert(i));
5322         assert(set.contains(i));
5323 
5324         assert(!set.insert(i));
5325         assert(set.contains(i));
5326     }
5327 
5328     foreach (immutable i; N .. 256)
5329     {
5330         assert(!set.contains(i));
5331 
5332         assert(set.insert(i));
5333         assert(set.contains(i));
5334 
5335         assert(!set.insert(i));
5336         assert(set.contains(i));
5337     }
5338 
5339     foreach (immutable i; 256 .. 256 + N)
5340     {
5341         assert(!set.contains(i));
5342 
5343         assert(set.insert(i));
5344         assert(set.contains(i));
5345 
5346         assert(!set.insert(i));
5347         assert(set.contains(i));
5348     }
5349 
5350     foreach (immutable i; 256 + N .. 256 + 256)
5351     {
5352         assert(!set.contains(i));
5353 
5354         assert(set.insert(i));
5355         assert(set.contains(i));
5356 
5357         assert(!set.insert(i));
5358         assert(set.contains(i));
5359     }
5360 }
5361 
5362 ///
5363 @safe pure nothrow @nogc unittest
5364 { version(showAssertTags) dbg();
5365     auto set = radixTreeSet!(ubyte);
5366     alias Set = typeof(set);
5367 
5368     foreach (immutable i; 0 .. HeptLeaf1.capacity)
5369     {
5370         assert(!set.contains(i));
5371 
5372         assert(set.insert(i));
5373         assert(set.contains(i));
5374 
5375         assert(!set.insert(i));
5376         assert(set.contains(i));
5377 
5378         immutable rootRef = set.root.peek!(HeptLeaf1);
5379         assert(rootRef);
5380     }
5381 
5382     foreach (immutable i; HeptLeaf1.capacity .. 256)
5383     {
5384         assert(!set.contains(i));
5385 
5386         assert(set.insert(i));
5387         assert(set.contains(i));
5388 
5389         assert(!set.insert(i));
5390         assert(set.contains(i));
5391     }
5392 }
5393 
5394 ///
5395 @safe pure nothrow @nogc unittest
5396 { version(showAssertTags) dbg();
5397     import std.meta : AliasSeq;
5398     foreach (T; AliasSeq!(ushort, uint))
5399     {
5400         auto set = radixTreeSet!(T);
5401         alias Set = typeof(set);
5402 
5403         foreach (immutable i; 0 .. 256)
5404         {
5405             assert(!set.contains(i));
5406 
5407             assert(set.insert(i));
5408             assert(set.contains(i));
5409 
5410             assert(!set.insert(i));
5411             assert(set.contains(i));
5412         }
5413 
5414         // 256
5415         assert(!set.contains(256));
5416 
5417         assert(set.insert(256));
5418         assert(set.contains(256));
5419 
5420         assert(!set.insert(256));
5421         assert(set.contains(256));
5422 
5423         // 257
5424         assert(!set.contains(257));
5425 
5426         assert(set.insert(257));
5427         assert(set.contains(257));
5428 
5429         assert(!set.insert(257));
5430         assert(set.contains(257));
5431 
5432         immutable rootRef = set.root.peek!(Set.DefaultBranchType*);
5433         assert(rootRef);
5434         immutable root = *rootRef;
5435         assert(root.prefix.length == T.sizeof - 2);
5436 
5437     }
5438 }
5439 
5440 /** Generate `count` number of random unique strings of minimum length 1 and
5441     maximum length of `maxLength`.
5442  */
5443 private static auto randomUniqueSortedStrings(size_t count, uint maxLength) @trusted pure nothrow
5444 {
5445     import std.random : Random, uniform;
5446     auto gen = Random();
5447 
5448     // store set of unique keys using a builtin D associative array (AA)
5449     bool[string] stringSet;  // set of strings using D's AA
5450 
5451     try
5452     {
5453         while (stringSet.length < count)
5454         {
5455             const length = uniform(1, maxLength, gen);
5456             auto key = new char[length];
5457             foreach (immutable ix; 0 .. length)
5458             {
5459                 key[ix] = cast(char)('a' + 0.uniform(26, gen));
5460             }
5461             stringSet[key[].idup] = true;
5462         }
5463     }
5464     catch (Exception e)
5465     {
5466         import nxt.dbgio : dbg;
5467         dbg("Couldn't randomize");
5468     }
5469 
5470     import std.array : array;
5471     import std.algorithm.sorting : sort;
5472     auto keys = stringSet.byKey.array;
5473     sort(keys);
5474     return keys;
5475 }
5476 
5477 /** Create a set of words from the file `/usr/share/dict/words`. */
5478 private void benchmarkReadDictWords(Value)(in size_t maxCount)
5479 {
5480     import std.range : chain;
5481 
5482     const path = "/usr/share/dict/words";
5483 
5484     enum hasValue = !is(Value == void);
5485     static if (hasValue)
5486     {
5487         auto rtr = radixTreeMap!(string, Value);
5488     }
5489     else
5490     {
5491         auto rtr = radixTreeSet!(string);
5492     }
5493     assert(rtr.empty);
5494 
5495     enum show = false;
5496 
5497     string[] firsts = [];       // used to test various things
5498     size_t count = 0;
5499 
5500     import std.datetime.stopwatch : StopWatch, AutoStart;
5501     auto sw = StopWatch(AutoStart.yes);
5502 
5503     import std.stdio : File;
5504     foreach (const word; chain(firsts, File(path).byLine))
5505     {
5506         if (count >= maxCount) { break; }
5507         import nxt.array_algorithm : endsWith;
5508         import std.range.primitives : empty;
5509         if (!word.empty &&
5510             !word.endsWith(`'s`)) // skip genitive forms
5511         {
5512             assert(!rtr.contains(word));
5513 
5514             static if (show)
5515             {
5516                 import std.string : representation;
5517                 // dbg(`word:"`, word, `" of length:`, word.length,
5518                 //     ` of representation:`, word.representation);
5519                 // debug rtr.willFail = word == `amiable`;
5520                 // if (rtr.willFail)
5521                 // {
5522                 //     rtr.print();
5523                 // }
5524             }
5525 
5526             static if (hasValue)
5527             {
5528                 assert(rtr.insert(word, count));
5529                 const hitA = rtr.contains(word);
5530                 assert(hitA);
5531                 assert(*hitA == count);
5532 
5533                 assert(!rtr.insert(word, count));
5534                 const hitB = rtr.contains(word);
5535                 assert(hitB);
5536                 assert(*hitB == count);
5537             }
5538             else
5539             {
5540                 assert(rtr.insert(word));
5541                 assert(rtr.contains(word));
5542 
5543                 assert(!rtr.insert(word));
5544                 assert(rtr.contains(word));
5545             }
5546             ++count;
5547         }
5548     }
5549     sw.stop;
5550     version(show)
5551     {
5552         import std.stdio : writeln;
5553         writeln("Added ", count, " words from ", path, " in ", sw.peek());
5554         rtr.showStatistics();
5555     }
5556 }
5557 
5558 unittest
5559 { version(showAssertTags) dbg();
5560     const maxCount = 1000;
5561     benchmarkReadDictWords!(void)(maxCount);
5562     benchmarkReadDictWords!(size_t)(maxCount);
5563 }
5564 
5565 static private void testSlice(T)(ref T x) @trusted
5566 {
5567     auto xr = x[];
5568 }
5569 
5570 bool testEqual(T, U)(ref T x, ref U y) @trusted
5571 {
5572     import std.algorithm.comparison : equal;
5573     return equal(x[], y[]);
5574 }
5575 
5576 /** Check correctness when span is `span` and for each `Key` in `Keys`. */
5577 version(unittest)
5578 private auto checkNumeric(Keys...)()
5579 if (Keys.length != 0)
5580 {
5581     import std.traits : isIntegral, isFloatingPoint;
5582     foreach (immutable it; 0 .. 1)
5583     {
5584         struct TestValueType { int i = 42; float f = 43; char ch = 'a'; }
5585         alias Value = TestValueType;
5586         import std.meta : AliasSeq;
5587         foreach (Key; Keys)
5588         {
5589             auto set = radixTreeSet!(Key);
5590             auto map = radixTreeMap!(Key, Value);
5591 
5592             assert(set.empty);
5593             assert(map.empty);
5594 
5595             testSlice(set);
5596             testSlice(map);
5597 
5598             static assert(!set.hasValue);
5599             static assert(map.hasValue);
5600 
5601             static if (is(Key == bool) ||
5602                        isIntegral!Key ||
5603                        isFloatingPoint!Key)
5604             {
5605                 static if (isIntegral!Key)
5606                 {
5607                     immutable low = max(Key.min, -1000);
5608                     immutable high = min(Key.max, 1000);
5609                     immutable factor = 1;
5610                 }
5611                 else static if (isFloatingPoint!Key)
5612                 {
5613                     immutable low = -1_000;
5614                     immutable high = 1_000;
5615                     immutable factor = 100;
5616                 }
5617                 else static if (is(Key == bool))
5618                 {
5619                     immutable low = false;
5620                     immutable high = true;
5621                     immutable factor = 1;
5622                 }
5623                 else
5624                 {
5625                     static assert(false, "Support Key " ~ Key.stringof);
5626                 }
5627                 immutable length = high - low + 1;
5628 
5629                 foreach (immutable uk_; low .. high + 1)
5630                 {
5631                     immutable uk = factor*uk_;
5632                     immutable Key key = cast(Key)uk;
5633 
5634                     assert(!set.contains(key)); // `key` should not yet be in `set`
5635                     assert(!map.contains(key)); // `key` should not yet be in 'map
5636 
5637                     assert(key !in set);        // alternative syntax
5638                     assert(key !in map);        // alternative syntax
5639 
5640                     // insertion of new `key` is `true` (previously not stored)
5641                     assert(set.insert(key));
5642                     assert(map.insert(key, Value.init));
5643 
5644                     // `key` should now be in `set` and `map`
5645                     assert(set.contains(key));
5646                     assert(map.contains(key));
5647 
5648                     // reinsertion of same `key` is `false` (already in stored)
5649                     assert(!set.insert(key));
5650                     assert(!map.insert(key, Value.init));
5651 
5652                     // `key` should now be `stored`
5653                     assert(set.contains(key));
5654                     assert(map.contains(key));
5655 
5656                     // alternative syntax
5657                     assert(key in set);
5658                     assert(key in map);
5659 
5660                     if (key != Key.max)        // except last value
5661                     {
5662                         assert(!set.contains(cast(Key)(key + 1))); // next key is not yet in set
5663                     }
5664                 }
5665                 assert(set.length == length);
5666             }
5667             else
5668             {
5669                 static assert(false, `Handle type: "` ~ Key.stringof ~ `"`);
5670             }
5671 
5672             auto setDup = set.dup();
5673             auto mapDup = map.dup();
5674 
5675             assert(testEqual(set, setDup));
5676             assert(testEqual(map, mapDup));
5677 
5678             set.clear();
5679 
5680             static assert(map.hasValue);
5681             map.clear();
5682         }
5683     }
5684 }
5685 
5686 /** Benchmark performance and memory usage when span is `span`. */
5687 private void benchmarkTimeAndSpace()
5688 {
5689     version(show) import std.stdio : writeln;
5690 
5691     struct TestValueType { int i = 42; float f = 43; char ch = 'a'; }
5692     alias Value = TestValueType;
5693     import std.meta : AliasSeq;
5694     foreach (Key; AliasSeq!(uint)) // just benchmark uint for now
5695     {
5696         auto set = radixTreeSet!(Key);
5697         alias Set = set;
5698         assert(set.empty);
5699 
5700         static assert(!set.hasValue);
5701 
5702         import std.datetime.stopwatch : StopWatch, AutoStart;
5703 
5704         enum n = 1_000_000;
5705 
5706         immutable useUniqueRandom = false;
5707 
5708         import std.range : generate, take;
5709         import std.random : uniform;
5710         auto randomSamples = generate!(() => uniform!Key).take(n);
5711 
5712         {
5713             auto sw = StopWatch(AutoStart.yes);
5714 
5715             foreach (immutable Key k; randomSamples)
5716             {
5717                 if (useUniqueRandom)
5718                 {
5719                     assert(set.insert(k));
5720                 }
5721                 else
5722                 {
5723                     set.insert(k);
5724                 }
5725 
5726                 /* second insert of same key should always return `false` to
5727                    indicate that key was already stored */
5728                 static if (false) { assert(!set.insert(k)); }
5729             }
5730 
5731             version(show) writeln("trie: Added ", n, " ", Key.stringof, "s of size ", n*Key.sizeof/1e6, " megabytes in ", sw.peek());
5732             set.showStatistics();
5733         }
5734 
5735         {
5736             auto sw = StopWatch(AutoStart.yes);
5737             bool[int] aa;
5738 
5739             foreach (Key k; randomSamples) { aa[k] = true; }
5740 
5741             version(show) writeln("D-AA: Added ", n, " ", Key.stringof, "s of size ", n*Key.sizeof/1e6, " megabytes in ", sw.peek());
5742         }
5743 
5744         auto map = radixTreeMap!(Key, Value);
5745         assert(map.empty);
5746         static assert(map.hasValue);
5747 
5748         map.insert(Key.init, Value.init);
5749     }
5750 }
5751 
5752 ///
5753 @safe pure nothrow @nogc unittest
5754 { version(showAssertTags) dbg();
5755     struct S { int x; }
5756 
5757     alias Key = S;
5758     auto set = radixTreeSet!(Key);
5759 
5760     assert(!set.contains(S(43)));
5761 
5762     assert(set.insert(S(43)));
5763     assert(set.contains(S(43)));
5764 
5765     assert(!set.insert(S(43)));
5766     assert(set.contains(S(43)));
5767 }
5768 
5769 /** Static Iota.
5770  *
5771  * TODO Move to Phobos std.range.
5772  */
5773 template iota(size_t from, size_t to)
5774 if (from <= to)
5775 {
5776     alias iota = iotaImpl!(to - 1, from);
5777 }
5778 private template iotaImpl(size_t to, size_t now)
5779 {
5780     import std.meta : AliasSeq;
5781     static if (now >= to)
5782     {
5783         alias iotaImpl = AliasSeq!(now);
5784     }
5785     else
5786     {
5787         alias iotaImpl = AliasSeq!(now, iotaImpl!(to, now + 1));
5788     }
5789 }
5790 
5791 unittest
5792 { version(showAssertTags) dbg();
5793     version(benchmark) benchmarkTimeAndSpace();
5794 }
5795 
5796 import nxt.qcmeman;
5797 
5798 version(unittest)
5799 {
5800     import nxt.array_help : s;
5801     version(showAssertTags) import nxt.dbgio : dbg;
5802 }