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