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