1 module cachetools.containers.hashmap;
2 
3 import std.traits;
4 import std.format;
5 import std.typecons;
6 
7 import core.memory;
8 import core.bitop;
9 
10 private import std.experimental.allocator;
11 private import std.experimental.allocator.mallocator : Mallocator;
12 
13 private import cachetools.internal;
14 private import cachetools.hash;
15 private import cachetools.containers.lists;
16 
17 class KeyNotFound : Exception {
18     this(string msg = "key not found") @safe {
19         super(msg);
20     }
21 }
22 ///
23 /// Return true if it is worth to store values inline in hash table
24 /// V footprint should be small enough
25 ///
26 package bool SmallValueFootprint(V)() {
27     import std.traits;
28 
29     static if (isNumeric!V || isSomeString!V || isSomeChar!V || isPointer!V) {
30         return true;
31     } else static if (is(V == struct) && V.sizeof <= (void*).sizeof) {
32         return true;
33     } else static if (is(V == class) && __traits(classInstanceSize, V) <= (void*).sizeof) {
34         return true;
35     } else
36         return false;
37 }
38 
39 private bool keyEquals(K)(const K a, const K b) {
40     static if (is(K == class)) {
41         if (a is b) {
42             return true;
43         }
44         if (a is null || b is null) {
45             return false;
46         }
47         return a.opEquals(b);
48     } else {
49         return a == b;
50     }
51 }
52 
53 @safe nothrow unittest {
54     class C {
55         int c;
56         this(int v) {
57             c = v;
58         }
59 
60         bool opEquals(const C other) const nothrow @safe {
61             return c == other.c;
62         }
63     }
64 
65     C a = new C(0);
66     C b = new C(1);
67     C c = a;
68     C d = new C(0);
69     assert(!keyEquals(a, b));
70     assert(keyEquals(a, c));
71     assert(keyEquals(a, d));
72     assert(keyEquals(1, 1));
73 }
74 
75 struct HashMap(K, V, Allocator = Mallocator) {
76 
77     enum initial_buckets_num = 32;
78     enum grow_factor = 2;
79     enum inlineValues = true; //SmallValueFootprint!V()|| is(V==class);
80 
81     alias StoredKeyType = StoredType!K;
82     alias StoredValueType = StoredType!V;
83 
84     private {
85         alias allocator = Allocator.instance;
86         static if (hash_t.sizeof == 8) {
87             enum EMPTY_HASH = 0x00_00_00_00_00_00_00_00;
88             enum DELETED_HASH = 0x10_00_00_00_00_00_00_00;
89             enum ALLOCATED_HASH = 0x20_00_00_00_00_00_00_00;
90             enum TYPE_MASK = 0xF0_00_00_00_00_00_00_00;
91             enum HASH_MASK = 0x0F_FF_FF_FF_FF_FF_FF_FF;
92         } else static if (hash_t.sizeof == 4) {
93             enum EMPTY_HASH = 0x00_00_00_00;
94             enum DELETED_HASH = 0x10_00_00_00;
95             enum ALLOCATED_HASH = 0x20_00_00_00;
96             enum TYPE_MASK = 0xF0_00_00_00;
97             enum HASH_MASK = 0x0F_FF_FF_FF;
98         }
99 
100         struct _Bucket {
101             hash_t hash;
102             StoredKeyType key;
103             static if (inlineValues) {
104                 StoredValueType value;
105             } else {
106                 V* value_ptr;
107             }
108             string toString() const {
109                 import std.format;
110 
111                 static if (inlineValues) {
112                     return "%s, hash: %0x,key: %s, value: %s".format([EMPTY_HASH : "free", DELETED_HASH : "deleted",
113                 ALLOCATED_HASH : "allocated"][cast(long)(hash & TYPE_MASK)], hash, key, value);
114                 } else {
115                     return "%s, hash: %0x, key: %s, value: %s".format([EMPTY_HASH : "free", DELETED_HASH : "deleted",
116                 ALLOCATED_HASH : "allocated"][cast(long)(hash & TYPE_MASK)],
117                             hash, key, value_ptr !is null ? format("%s", *value_ptr) : "-");
118                 }
119             }
120         }
121 
122         _Bucket[] _buckets;
123         int _buckets_num;
124         int _mask;
125         int _allocated;
126         int _deleted;
127         int _empty;
128     }
129 
130     ~this() @safe {
131         if (_buckets_num > 0) {
132             static if (!inlineValues) {
133                 for (int i = 0; i < _buckets_num; i++) {
134                     auto t = _buckets[i].hash;
135                     if (t <= DELETED_HASH) {
136                         continue;
137                     }
138                     (() @trusted { dispose(allocator, _buckets[i].value_ptr); })();
139                 }
140             }
141             (() @trusted {
142                 GC.removeRange(_buckets.ptr);
143                 dispose(allocator, _buckets);
144             })();
145         }
146     }
147 
148     invariant {
149         assert(_allocated >= 0 && _deleted >= 0 && _empty >= 0);
150         assert(_allocated + _deleted + _empty == _buckets_num);
151     }
152     ///
153     /// Find any unallocated bucket starting from start_index (inclusive)
154     /// Returns index on success or hash_t.max on fail
155     ///
156     //private hash_t findEmptyIndex(const hash_t start_index) pure const @safe @nogc
157     //in
158     //{
159     //    assert(start_index < _buckets_num);
160     //}
161     //do {
162     //    hash_t index = start_index;
163     //
164     //    do {
165     //        () @nogc {debug(cachetools) tracef("test index %d for non-ALLOCATED", index);}();
166     //        if ( _buckets[index].hash < ALLOCATED_HASH )
167     //        {
168     //            return index;
169     //        }
170     //        index = (index + 1) & _mask;
171     //    } while(index != start_index);
172     //    return hash_t.max;
173     //}
174     ///
175     /// Find allocated bucket for given key and computed hash starting from start_index
176     /// Returns: index if bucket found or hash_t.max otherwise
177     ///
178     /// Inherits @nogc and from K opEquals()
179     ///
180     private hash_t findEntryIndex(const hash_t start_index, const hash_t hash, in K key) pure const @safe
181     in {
182         assert(hash < DELETED_HASH); // we look for real hash
183         assert(start_index < _buckets_num); // start position inside array
184     }
185     do {
186         hash_t index = start_index;
187 
188         do {
189             immutable h = _buckets[index].hash;
190 
191             debug (cachetools)
192                 safe_tracef("test entry index %d (%s) for key %s", index, _buckets[index], key);
193 
194             if (h == EMPTY_HASH) {
195                 break;
196             }
197 
198             if (h >= ALLOCATED_HASH && (h & HASH_MASK) == hash
199                     && keyEquals(_buckets[index].key, key)) {
200                 //() @nogc @trusted {debug(cachetools) tracef("test entry index %d for key %s - success", index, key);}();
201                 return index;
202             }
203             index = (index + 1) & _mask;
204         }
205         while (index != start_index);
206         return hash_t.max;
207     }
208 
209     ///
210     /// Find place where we can insert(DELETED or EMPTY bucket) or update existent (ALLOCATED)
211     /// bucket for key k and precomputed hash starting from start_index
212     ///
213     ///
214     /// Inherits @nogc from K opEquals()
215     ///
216     private hash_t findUpdateIndex(const hash_t start_index, const hash_t computed_hash, in K key) pure const @safe
217     in {
218         assert(computed_hash < DELETED_HASH);
219         assert(start_index < _buckets_num);
220     }
221     do {
222         hash_t index = start_index;
223 
224         do {
225             immutable h = _buckets[index].hash;
226 
227             debug (cachetools)
228                 safe_tracef("test update index %d (%s) for key %s", index, _buckets[index], key);
229 
230             if (h <= DELETED_HASH) // empty or deleted
231             {
232                 debug (cachetools)
233                     safe_tracef("test update index %d (%s) for key %s - success",
234                             index, _buckets[index], key);
235                 return index;
236             }
237             assert((h & TYPE_MASK) == ALLOCATED_HASH);
238             if ((h & HASH_MASK) == computed_hash && keyEquals(_buckets[index].key, key)) {
239                 debug (cachetools)
240                     safe_tracef("test update index %d (%s) for key %s - success",
241                             index, _buckets[index], key);
242                 return index;
243             }
244             index = (index + 1) & _mask;
245         }
246         while (index != start_index);
247         return hash_t.max;
248     }
249     ///
250     /// Find unallocated entry in the buckets slice
251     /// We use this function during resize() only.
252     ///
253     private long findEmptyIndexExtended(const hash_t start_index,
254             in ref _Bucket[] buckets, int new_mask) pure const @safe @nogc
255     in {
256         assert(start_index < buckets.length);
257     }
258     do {
259         hash_t index = start_index;
260 
261         do {
262             immutable t = buckets[index].hash;
263 
264             debug (cachetools)
265                 safe_tracef("test empty index %d (%s)", index, buckets[index]);
266 
267             if (t <= DELETED_HASH) // empty or deleted
268             {
269                 return index;
270             }
271 
272             index = (index + 1) & new_mask;
273         }
274         while (index != start_index);
275         return hash_t.max;
276     }
277 
278     private bool tooMuchDeleted() pure const @safe @nogc {
279         //
280         // _deleted > _buckets_num / 8
281         //
282         return _deleted << 3 > _buckets_num;
283     }
284 
285     private bool tooHighLoad() pure const @safe @nogc {
286         //
287         // _allocated/_buckets_num > 0.8
288         // 5 * allocated > 4 * buckets_num
289         //
290         return _allocated + (_allocated << 2) > _buckets_num << 2;
291     }
292 
293     private void doResize(int dest) @safe {
294         immutable _new_buckets_num = dest;
295         immutable _new_mask = dest - 1;
296         _Bucket[] _new_buckets = makeArray!(_Bucket)(allocator, _new_buckets_num);
297 
298         () @trusted {
299             GC.addRange(_new_buckets.ptr, _new_buckets_num * _Bucket.sizeof);
300         }();
301 
302         // iterate over entries
303 
304         debug (cachetools)
305             safe_tracef("start resizing: old loadfactor: %s", (1.0 * _allocated) / _buckets_num);
306 
307         for (int i = 0; i < _buckets_num; i++) {
308             immutable hash_t h = _buckets[i].hash;
309             if (h < ALLOCATED_HASH) { // empty or deleted
310                 continue;
311             }
312 
313             immutable hash_t start_index = h & _new_mask;
314             immutable new_position = findEmptyIndexExtended(start_index, _new_buckets, _new_mask);
315 
316             debug (cachetools)
317                 safe_tracef("old hash: %0x, old pos: %d, new_pos: %d", h, i, new_position);
318 
319             assert(new_position >= 0);
320             assert(_new_buckets[cast(hash_t) new_position].hash == EMPTY_HASH);
321 
322             _new_buckets[cast(hash_t) new_position] = _buckets[i];
323         }
324         (() @trusted {
325             GC.removeRange(_buckets.ptr);
326             dispose(allocator, _buckets);
327         })();
328         _buckets = _new_buckets;
329         _buckets_num = _new_buckets_num;
330         assert(_popcnt(_buckets_num) == 1, "Buckets number must be power of 2");
331         _mask = _buckets_num - 1;
332         _deleted = 0;
333         _empty = _buckets_num - _allocated;
334 
335         debug (cachetools)
336             safe_tracef("resizing done: new loadfactor: %s", (1.0 * _allocated) / _buckets_num);
337     }
338 
339     ///
340     /// Lookup methods
341     ///
342     V* opBinaryRight(string op)(in K k) @safe if (op == "in") {
343 
344         if (_buckets_num == 0)
345             return null;
346 
347         immutable computed_hash = hash_function(k) & HASH_MASK;
348         immutable start_index = computed_hash & _mask;
349         immutable lookup_index = findEntryIndex(start_index, computed_hash, k);
350         if (lookup_index == hash_t.max) {
351             return null;
352         }
353         static if (inlineValues) {
354             static if (is(V == StoredValueType)) {
355                 return &_buckets[lookup_index].value;
356             } else {
357                 V* r = () @trusted {
358                     return cast(V*)&_buckets[lookup_index].value;
359                 }();
360                 return r;
361             }
362         } else {
363             return _buckets[lookup_index].value_ptr;
364         }
365     }
366 
367     ref V getOrAdd(T)(K k, T defaultValue) @safe {
368         V* v = k in this;
369         if (v) {
370             return *v;
371         }
372         static if (isAssignable!(V, T)) {
373             return *put(k, defaultValue);
374         } else static if (isCallable!T && isAssignable!(V, ReturnType!T)) {
375             return *put(k, defaultValue());
376         } else {
377             static assert(0, "what?");
378         }
379     }
380 
381     alias require = getOrAdd;
382 
383     /// Attention: this can't return ref as default value can be rvalue
384     V get(T)(K k, T defaultValue) @safe {
385         V* v = k in this;
386         if (v) {
387             return *v;
388         }
389         static if (isAssignable!(V, T)) {
390             return defaultValue;
391         } else static if (isCallable!T && isAssignable!(V, ReturnType!T)) {
392             return defaultValue();
393         } else {
394             static assert(0, "You must call 'get' with default value of HashMap 'value' type, or with callable, returning HashMap 'value'");
395         }
396     }
397 
398     ///
399     /// Attention: you can't use this method in @nogc code.
400     /// Usual aa[key] method.
401     /// Throws exception if key not found
402     ///
403     ref V opIndex(in K k) @safe {
404         V* v = k in this;
405         if (v !is null) {
406             return *v;
407         }
408         throw new KeyNotFound();
409     }
410 
411     ///
412     /// Modifiers
413     ///
414     void opIndexAssign(V v, K k) @safe {
415         put(k, v);
416     }
417     ///
418     /// put pair (k,v) into hash.
419     /// it must be @safe, it inherits @nogc properties from K and V
420     /// It can resize hashtable it is overloaded or has too much deleted entries
421     ///
422     V* put(K k, V v) @safe
423     out {
424         assert(__result !is null);
425     }
426     do {
427         if (!_buckets_num) {
428             _buckets_num = _empty = initial_buckets_num;
429             assert(_popcnt(_buckets_num) == 1, "Buckets number must be power of 2");
430             _mask = _buckets_num - 1;
431             _buckets = makeArray!(_Bucket)(allocator, _buckets_num);
432             () @trusted {
433                 GC.addRange(_buckets.ptr, _buckets_num * _Bucket.sizeof);
434             }();
435         }
436 
437         debug (cachetools)
438             safe_tracef("put k: %s, v: %s", k, v);
439 
440         if (tooHighLoad) {
441             doResize(grow_factor * _buckets_num);
442         }
443 
444         V* r; //result
445         immutable computed_hash = hash_function(k) & HASH_MASK;
446         immutable start_index = computed_hash & _mask;
447         immutable placement_index = findUpdateIndex(start_index, computed_hash, k);
448         assert(placement_index >= 0);
449         _Bucket* bucket = &_buckets[placement_index];
450         immutable h = bucket.hash;
451 
452         debug (cachetools)
453             safe_tracef("start_index: %d, placement_index: %d", start_index, placement_index);
454 
455         if (h < ALLOCATED_HASH) {
456             _allocated++;
457             _empty--;
458         }
459         static if (inlineValues) {
460             debug (cachetools)
461                 safe_tracef("place inline buckets[%d] '%s'='%s'", placement_index, k, v);
462             bucket.value = v;
463             static if (is(V == StoredValueType)) {
464                 r = &bucket.value;
465             } else {
466                 () @trusted { r = cast(V*)&bucket.value; }();
467             }
468         } else {
469             debug (cachetools)
470                 safe_tracef("place with allocation buckets[%d] '%s'='%s'", placement_index, k, v);
471             if ((bucket.hash & TYPE_MASK) == ALLOCATED_HASH) {
472                 // we just replace what we already allocated
473                 r = (bucket.value_ptr);
474                 *r = v;
475             } else {
476                 r = bucket.value_ptr = make!(V)(allocator);
477                 *r = v;
478             }
479         }
480         bucket.hash = computed_hash | ALLOCATED_HASH;
481         bucket.key = k;
482         return r;
483     }
484 
485     bool remove(K k) @safe {
486 
487         if (tooMuchDeleted) {
488             // do not shrink, just compact table
489             doResize(_buckets_num);
490         }
491 
492         debug (cachetools)
493             safe_tracef("remove k: %s", k);
494 
495         immutable computed_hash = hash_function(k) & HASH_MASK;
496         immutable start_index = computed_hash & _mask;
497         immutable lookup_index = findEntryIndex(start_index, computed_hash, k);
498         if (lookup_index == hash_t.max) {
499             // nothing to remove
500             return false;
501         }
502 
503         assert((_buckets[lookup_index].hash & TYPE_MASK) == ALLOCATED_HASH,
504                 "tried to remove non allocated bucket");
505 
506         static if (inlineValues) {
507             // what we have to do with removed values XXX?
508         } else {
509             // what we have to do with removed values XXX?
510             // free space
511             (() @trusted { dispose(allocator, _buckets[lookup_index].value_ptr); })();
512             _buckets[lookup_index].value_ptr = null;
513         }
514 
515         _allocated--;
516         immutable next_index = (lookup_index + 1) & _mask;
517         // if next bucket is EMPTY, then we can convert all DELETED buckets down staring from current to EMPTY buckets
518         if (_buckets[next_index].hash == EMPTY_HASH) {
519             _empty++;
520             _buckets[lookup_index].hash = EMPTY_HASH;
521             auto free_index = (lookup_index - 1) & _mask;
522             while (free_index != lookup_index) {
523                 if (_buckets[free_index].hash != DELETED_HASH) {
524                     break;
525                 }
526                 _buckets[free_index].hash = EMPTY_HASH;
527                 _deleted--;
528                 _empty++;
529                 free_index = (free_index - 1) & _mask;
530             }
531             assert(free_index != lookup_index, "table full of deleted buckets?");
532         } else {
533             _buckets[lookup_index].hash = DELETED_HASH;
534             _deleted++;
535         }
536         return true;
537     }
538 
539     void clear() @safe {
540         static if (!inlineValues) {
541             // dispose every element
542             foreach (k; byKey) {
543                 remove(k);
544             }
545         }
546         (() @trusted {
547             GC.removeRange(_buckets.ptr);
548             dispose(allocator, _buckets);
549         })();
550         _buckets = makeArray!(_Bucket)(allocator, _buckets_num);
551         _allocated = _deleted = 0;
552         _empty = _buckets_num;
553     }
554 
555     auto length() const pure nothrow @nogc @safe {
556         return _allocated;
557     }
558 
559     auto size() const pure nothrow @nogc @safe {
560         return _buckets_num;
561     }
562 
563     auto byKey() pure @safe @nogc {
564         struct _kvRange {
565             int _pos;
566             ulong _buckets_num;
567             _Bucket[] _buckets;
568             this(_Bucket[] _b) {
569                 _buckets = _b;
570                 _buckets_num = _b.length;
571                 _pos = 0;
572                 while (_pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH) {
573                     _pos++;
574                 }
575             }
576 
577             bool empty() const pure nothrow @safe @nogc {
578                 return _pos == _buckets_num;
579             }
580 
581             K front() {
582                 return _buckets[_pos].key;
583             }
584 
585             void popFront() pure nothrow @safe @nogc {
586                 _pos++;
587                 while (_pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH) {
588                     _pos++;
589                 }
590             }
591         }
592 
593         return _kvRange(_buckets);
594     }
595 
596     auto byValue() pure @safe {
597         struct _kvRange {
598             int _pos;
599             ulong _buckets_num;
600             _Bucket[] _buckets;
601             this(_Bucket[] _b) {
602                 _buckets = _b;
603                 _buckets_num = _b.length;
604                 _pos = 0;
605                 while (_pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH) {
606                     _pos++;
607                 }
608             }
609 
610             bool empty() const pure nothrow @safe @nogc {
611                 return _pos == _buckets_num;
612             }
613 
614             V front() {
615                 static if (inlineValues) {
616                     return _buckets[_pos].value;
617                 } else {
618                     return *(_buckets[_pos].value_ptr);
619                 }
620             }
621 
622             void popFront() pure nothrow @safe @nogc {
623                 _pos++;
624                 while (_pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH) {
625                     _pos++;
626                 }
627             }
628         }
629 
630         return _kvRange(_buckets);
631     }
632 
633     auto byPair() pure @safe {
634         import std.typecons;
635 
636         struct _kvRange {
637             int _pos;
638             ulong _buckets_num;
639             _Bucket[] _buckets;
640             this(_Bucket[] _b) {
641                 _buckets = _b;
642                 _buckets_num = _b.length;
643                 _pos = 0;
644                 while (_pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH) {
645                     _pos++;
646                 }
647             }
648 
649             bool empty() const pure nothrow @safe @nogc {
650                 return _pos == _buckets_num;
651             }
652 
653             auto front() @safe {
654                 static if (inlineValues) {
655                     return Tuple!(K, "key", V, "value")(_buckets[_pos].key, _buckets[_pos].value);
656                 } else {
657                     return Tuple!(K, "key", V, "value")(_buckets[_pos].key,
658                             *(_buckets[_pos].value_ptr));
659                 }
660             }
661 
662             void popFront() pure nothrow @safe @nogc {
663                 _pos++;
664                 while (_pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH) {
665                     _pos++;
666                 }
667             }
668         }
669 
670         return _kvRange(_buckets);
671     }
672 }
673 
674 /// Tests
675 
676 /// test immutable struct and class as Key type
677 @safe unittest {
678     import std.experimental.logger;
679 
680     globalLogLevel = LogLevel.info;
681     () @nogc nothrow{
682         struct S {
683             int s;
684         }
685 
686         HashMap!(immutable S, int) hs1;
687         immutable ss = S(1);
688         hs1[ss] = 1;
689         assert(ss in hs1 && *(ss in hs1) == 1);
690         HashMap!(int, immutable S) hs2;
691         hs2[1] = ss;
692         assert(1 in hs2 && *(1 in hs2) == ss);
693         assert(!(2 in hs2));
694     }();
695 
696     // class
697     class C {
698         int v;
699         this(int _v) pure inout {
700             v = _v;
701         }
702 
703         bool opEquals(const C o) pure const @safe @nogc nothrow {
704             return v == o.v;
705         }
706 
707         override hash_t toHash() const @safe @nogc {
708             return hash_function(v);
709         }
710     }
711 
712     HashMap!(immutable C, int) hc1;
713     immutable cc = new immutable C(1);
714     hc1[cc] = 1;
715     assert(hc1[cc] == 1);
716     HashMap!(int, immutable C) hc2;
717     hc2[1] = cc;
718     assert(hc2[1] is cc);
719 }
720 
721 @safe unittest {
722     // test class as key
723     import std.experimental.logger;
724 
725     globalLogLevel = LogLevel.info;
726     class A {
727         int v;
728 
729         bool opEquals(const A o) pure const @safe @nogc nothrow {
730             return v == o.v;
731         }
732 
733         override hash_t toHash() const @safe @nogc {
734             return hash_function(v);
735         }
736 
737         this(int v) {
738             this.v = v;
739         }
740 
741         override string toString() const {
742             import std.format;
743 
744             return "A(%d)".format(v);
745         }
746     }
747 
748     globalLogLevel = LogLevel.info;
749     auto x = new A(1);
750     auto y = new A(2);
751     HashMap!(A, string) dict;
752     dict.put(x, "x");
753     dict.put(y, "y");
754 }
755 
756 @safe unittest {
757     import std.experimental.logger;
758 
759     globalLogLevel = LogLevel.info;
760     () @nogc nothrow{
761         HashMap!(int, int) int2int;
762         foreach (i; 0 .. 15) {
763             int2int.put(i, i);
764         }
765         assert(int2int.length() == 15);
766         foreach (i; 0 .. 15) {
767             assert(i in int2int);
768         }
769         foreach (i; 0 .. 15) {
770             int2int.remove(i);
771         }
772         assert(int2int.length() == 0);
773     }();
774     () @nogc nothrow{
775         struct LargeStruct {
776             ulong a;
777             ulong b;
778         }
779 
780         HashMap!(int, LargeStruct) int2ls;
781         foreach (i; 1 .. 5) {
782             int2ls.put(i, LargeStruct(i, i));
783         }
784         int2ls.put(33, LargeStruct(33, 33)); // <- follow key 1, move key 2 on pos 3
785         assert(1 in int2ls, "1 not in hash");
786         assert(2 in int2ls, "2 not in hash");
787         assert(3 in int2ls, "3 not in hash");
788         assert(4 in int2ls, "4 not in hash");
789         assert(33 in int2ls, "33 not in hash");
790         int2ls.remove(33);
791         int2ls.put(2, LargeStruct(2, 2)); // <- must replace key 2 on pos 3
792         assert(2 in int2ls, "2 not in hash");
793     }();
794 }
795 
796 @safe unittest {
797     import std.experimental.logger;
798 
799     globalLogLevel = LogLevel.info;
800     () @nogc nothrow{
801         assert(SmallValueFootprint!int());
802         assert(SmallValueFootprint!double());
803         struct SmallStruct {
804             ulong a;
805         }
806         //assert(SmallValueFootprint!SmallStruct);
807         struct LargeStruct {
808             ulong a;
809             ulong b;
810         }
811 
812         assert(!SmallValueFootprint!LargeStruct);
813         class SmallClass {
814             ulong a;
815         }
816         //assert(!SmallValueFootprint!SmallClass);
817 
818         HashMap!(int, string) int2string;
819         auto u = int2string.put(1, "one");
820         {
821             auto v = 1 in int2string;
822             assert(v !is null);
823             assert(*v == "one");
824         }
825         assert(2 !in int2string);
826         u = int2string.put(32 + 1, "33");
827         assert(33 in int2string);
828         assert(int2string.remove(33));
829         assert(!int2string.remove(33));
830 
831         HashMap!(int, LargeStruct) int2LagreStruct;
832         int2LagreStruct.put(1, LargeStruct(1, 2));
833         {
834             auto v = 1 in int2LagreStruct;
835             assert(v !is null);
836             assert(*v == LargeStruct(1, 2));
837         }
838     }();
839 
840     globalLogLevel = LogLevel.info;
841 }
842 
843 @safe unittest {
844     import std.experimental.logger;
845     import std.experimental.allocator.gc_allocator;
846 
847     globalLogLevel = LogLevel.info;
848     static int i;
849     () @safe @nogc nothrow{
850         struct LargeStruct {
851             ulong a;
852             ulong b;
853             ~this() @safe @nogc {
854                 i++;
855             }
856         }
857 
858         HashMap!(int, LargeStruct) int2LagreStruct;
859         int2LagreStruct.put(1, LargeStruct(1, 2));
860     }();
861     globalLogLevel = LogLevel.info;
862 }
863 
864 @safe unittest  /* not nothrow as opIndex may throw */ {
865     import std.typecons;
866 
867     alias K = Tuple!(int, int);
868     alias V = int;
869     HashMap!(K, V) h;
870     K k0 = K(0, 1);
871     V v0 = 1;
872     h.put(k0, v0);
873     int* v = k0 in h;
874     assert(v);
875     assert(*v == 1);
876     h[k0] = v0;
877     assert(h[k0] == v0);
878 }
879 
880 @safe nothrow unittest {
881     class c {
882         int a;
883         this(int a) {
884             this.a = a;
885         }
886 
887         override hash_t toHash() const pure @nogc @safe {
888             return hash_function(a);
889         }
890 
891         bool opEquals(const c other) pure const nothrow @safe @nogc {
892             return this is other || this.a == other.a;
893         }
894     }
895 
896     alias K = c;
897     alias V = int;
898     K k0 = new c(0);
899     V v0 = 1;
900     () @nogc nothrow{
901         HashMap!(K, V) h;
902         h.put(k0, v0);
903         int* v = k0 in h;
904         assert(v);
905         assert(*v == 1);
906         h[k0] = 2;
907         v = k0 in h;
908         assert(*v == 2);
909     }();
910 }
911 
912 /// Test if we can work with non-@nogc opEquals for class-key.
913 /// opEquals anyway must be non-@system.
914 @safe nothrow unittest {
915     class c {
916         int a;
917         this(int a) {
918             this.a = a;
919         }
920 
921         override hash_t toHash() const pure @safe {
922             int[] _ = [1, 2, 3]; // this cause GC
923             return hash_function(a);
924         }
925 
926         bool opEquals(const c other) const pure nothrow @safe {
927             auto _ = [1, 2, 3]; // this cause GC
928             return this is other || this.a == other.a;
929         }
930     }
931 
932     alias K = c;
933     alias V = int;
934     HashMap!(K, V) h;
935     K k0 = new c(0);
936     V v0 = 1;
937     h.put(k0, v0);
938     int* v = k0 in h;
939     assert(v);
940     assert(*v == 1);
941 
942 }
943 ///
944 /// test byKey, byValue, byPair
945 ///
946 @safe nothrow unittest {
947     import std.algorithm;
948     import std.array;
949     import std.stdio;
950 
951     HashMap!(int, string) m;
952     m[1] = "one";
953     m[2] = "two";
954     m[10] = "ten";
955     assert(equal(m.byKey.array.sort, [1, 2, 10]));
956     assert(equal(m.byValue.array.sort, ["one", "ten", "two"]));
957     assert(equal(m.byPair.map!"tuple(a.key, a.value)".array.sort, [tuple(1,
958             "one"), tuple(2, "two"), tuple(10, "ten")]));
959     m.remove(1);
960     m.remove(10);
961     assert(equal(m.byPair.map!"tuple(a.key, a.value)".array.sort, [tuple(2, "two")]));
962     m.remove(2);
963     assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0);
964     m.remove(2);
965     assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0);
966 }
967 /// 
968 /// compare equivalence to AA
969 ///
970 /* not @safe because of AA */
971 unittest {
972     import std.random;
973     import std.array;
974     import std.algorithm;
975     import std.stdio;
976     import std.experimental.logger;
977 
978     enum iterations = 400_000;
979 
980     globalLogLevel = LogLevel.info;
981 
982     HashMap!(int, int) hashMap;
983     int[int] AA;
984 
985     auto rnd = Random(unpredictableSeed);
986 
987     foreach (i; 0 .. iterations) {
988         int k = uniform(0, iterations, rnd);
989         hashMap.put(k, i);
990         AA[k] = i;
991     }
992     assert(equal(AA.keys().sort(), hashMap.byKey().array.sort()));
993     assert(equal(AA.values().sort(), hashMap.byValue().array.sort()));
994     assert(AA.length == hashMap.length);
995 }
996 ///
997 /// check remove
998 ///
999 @safe unittest {
1000     // test removal while iterating
1001     import std.random;
1002     import std.array;
1003     import std.algorithm;
1004     import std.stdio;
1005     import std.experimental.logger;
1006 
1007     enum iterations = 400_000;
1008 
1009     globalLogLevel = LogLevel.info;
1010 
1011     HashMap!(int, int) hashMap;
1012 
1013     auto rnd = Random(unpredictableSeed);
1014 
1015     foreach (i; 0 .. iterations) {
1016         int k = uniform(0, iterations, rnd);
1017         hashMap[k] = i;
1018     }
1019     foreach (k; hashMap.byKey) {
1020         assert(hashMap.remove(k));
1021     }
1022     assert(hashMap.length == 0);
1023 }
1024 ///
1025 /// test clear
1026 ///
1027 @safe @nogc nothrow unittest {
1028     // test clear
1029 
1030     HashMap!(int, int) hashMap;
1031 
1032     foreach (i; 0 .. 100) {
1033         hashMap[i] = i;
1034     }
1035     hashMap.clear();
1036     assert(hashMap.length == 0);
1037     hashMap[1] = 1;
1038     assert(1 in hashMap && hashMap.length == 1);
1039 }
1040 ///
1041 /// test getOrAdd with value
1042 ///
1043 @safe @nogc nothrow unittest {
1044     // test of nogc getOrAdd
1045 
1046     HashMap!(int, int) hashMap;
1047 
1048     foreach (i; 0 .. 100) {
1049         hashMap[i] = i;
1050     }
1051     auto v = hashMap.getOrAdd(-1, -1);
1052     assert(-1 in hashMap && v == -1);
1053 }
1054 
1055 ///
1056 /// test getOrAdd with callable
1057 ///
1058 @safe @nogc nothrow unittest {
1059     // test of nogc getOrAdd with lazy default value
1060 
1061     HashMap!(int, int) hashMap;
1062 
1063     foreach (i; 0 .. 100) {
1064         hashMap[i] = i;
1065     }
1066     int v = hashMap.getOrAdd(-1, () => -1);
1067     assert(-1 in hashMap && v == -1);
1068     assert(hashMap.get(-1, 0) == -1); // key -1 is in hash, return value
1069     assert(hashMap.get(-2, 0) == 0); // key -2 not in map, return default value
1070     assert(hashMap.get(-3, () => 0) == 0); // ditto
1071 }
1072 
1073 ///
1074 /// test getOrAdd with complex  data
1075 ///
1076 @safe unittest {
1077     import std.socket;
1078 
1079     HashMap!(string, Socket) socketPool;
1080     Socket s0 = socketPool.getOrAdd("http://example.com",
1081             () => new Socket(AddressFamily.INET, SocketType.STREAM));
1082     assert(s0 !is null);
1083     assert(s0.addressFamily == AddressFamily.INET);
1084     Socket s1 = socketPool.getOrAdd("http://example.com",
1085             () => new Socket(AddressFamily.INET, SocketType.STREAM));
1086     assert(s1 !is null);
1087     assert(s1 is s0);
1088 }
1089 ///
1090 /// test with real class (socket)
1091 ///
1092 @safe unittest {
1093     import std.socket;
1094 
1095     class Connection {
1096         Socket s;
1097         bool opEquals(const Connection other) const pure @safe {
1098             return s is other.s;
1099         }
1100 
1101         override hash_t toHash() const @safe {
1102             return hash_function(s.handle);
1103         }
1104     }
1105 
1106     HashMap!(Connection, string) socketPool;
1107 }
1108 
1109 @safe unittest {
1110     // test of non-@nogc getOrAdd with lazy default value
1111     import std.conv;
1112     import std.exception;
1113 
1114     class C {
1115         string v;
1116         this(int _v) @safe {
1117             v = to!string(_v);
1118         }
1119     }
1120 
1121     HashMap!(int, C) hashMap;
1122 
1123     foreach (i; 0 .. 100) {
1124         hashMap[i] = new C(i);
1125     }
1126     C v = hashMap.getOrAdd(-1, () => new C(-1));
1127     assert(-1 in hashMap && v.v == "-1");
1128     assert(hashMap[-1].v == "-1");
1129     hashMap[-1].v ~= "1";
1130     assert(hashMap[-1].v == "-11");
1131     assertThrown!KeyNotFound(hashMap[-2]);
1132     // check lazyness
1133     bool called;
1134     v = hashMap.getOrAdd(-1, delegate C() { called = true; return new C(0); });
1135     assert(!called);
1136     v = hashMap.getOrAdd(-2, delegate C() { called = true; return new C(0); });
1137     assert(called);
1138 }
1139 ///
1140 /// test if we can handle some exotic value type
1141 ///
1142 @safe @nogc nothrow unittest {
1143     // test of nogc getOrAdd with lazy default value
1144     // corner case when V is callable
1145 
1146     alias F = int function() @safe @nogc nothrow;
1147 
1148     F one = function() { return 1; };
1149     F two = function() { return 2; };
1150     F three = function() { return 3; };
1151     F four = function() { return 4; };
1152     HashMap!(int, F) hashMap;
1153     hashMap.put(1, one);
1154     hashMap.put(2, two);
1155     auto p = 1 in hashMap;
1156     assert(p);
1157     assert((*p)() == 1);
1158     p = 2 in hashMap;
1159     assert(p);
1160     assert((*p)() == 2);
1161     auto f3 = hashMap.getOrAdd(3, () => function int() { return 3; }); // used as default()
1162     assert(f3() == 3);
1163     auto f4 = hashMap.getOrAdd(4, four);
1164     assert(f4() == 4);
1165 }
1166 
1167 // test get()
1168 @safe @nogc nothrow unittest {
1169     HashMap!(int, int) hashMap;
1170     int i = hashMap.get(1, 55);
1171     assert(i == 55);
1172     i = hashMap.get(1, () => 66);
1173     assert(i == 66);
1174     hashMap[1] = 1;
1175     i = hashMap.get(1, () => 66);
1176     assert(i == 1);
1177 }