1 module dstruct.map;
2 
3 import core.memory;
4 import core.exception;
5 
6 import core.stdc..string: memcpy, memset;
7 
8 import std.range : ElementType;
9 import std.traits : Unqual, isPointer;
10 
11 import dstruct.support;
12 
13 private enum EntryState {
14     empty = 0,
15     occupied = 1,
16     deleted = 2,
17 }
18 
19 private enum SearchFor {
20     empty = 1,
21     occupied = 2,
22     deleted = 4,
23     notDeleted = empty | occupied,
24     notOccupied = empty | deleted,
25     any = empty | occupied | deleted,
26 }
27 
28 private enum is64Bit = is(size_t == ulong);
29 
30 // Given a size, compute the space in bytes required for a member when
31 // the member is aligned to a size_t.sizeof boundary.
32 private size_t alignedSize(size_t size) {
33     if (size % size_t.sizeof) {
34         // If there's a remainder, then our boundary is a little ahead.
35         return cast(size_t) (size / size_t.sizeof) + 1 * size_t.sizeof;
36     }
37 
38     // If the size is cleanly divisible, it will fit right into place.
39     return size;
40 }
41 
42 private enum isHashIdentical(T) =
43     is(T : uint)
44     | is(T : char)
45     | is(T : wchar)
46     | is(T : dchar)
47     | isPointer!T
48     | (is64Bit && is(T : ulong));
49 
50 /**
51  * An item from a map.
52  *
53  * The keys and the values in the map are references into the map itself.
54  */
55 struct Entry(K, V) {
56 private:
57     K _key;
58     V _value;
59     EntryState _state = EntryState.empty;
60 
61     static if (!isHashIdentical!K) {
62         size_t _hash;
63     }
64 public:
65     /**
66      * A key from the map.
67      */
68     @nogc @safe pure nothrow
69     @property ref inout(K) key() inout {
70         return _key;
71     }
72 
73     /**
74      * A value from the map.
75      */
76     @nogc @safe pure nothrow
77     @property ref inout(V) value() inout {
78         return _value;
79     }
80 }
81 
82 // TODO: Test different entry sizes.
83 
84 static if(__VERSION__ < 2066) {
85     private alias SafeGetHashType = size_t delegate(const(void*)) pure nothrow;
86 } else {
87     // Use mixin to hide UDA errors from old D compilers, it will never
88     // execute the mixin, so it won't report an error in syntax.
89     mixin("private alias SafeGetHashType = size_t delegate(const(void*)) @nogc pure nothrow;");
90 }
91 
92 @nogc @trusted pure nothrow
93 private size_t computeHash(K)(ref K key) if (!isHashIdentical!K) {
94     // Cast so we can keep our function qualifiers.
95     return (cast(SafeGetHashType) &(typeid(K).getHash))(&key);
96 }
97 
98 @nogc @trusted pure nothrow
99 private size_t castHash(K)(K key) if (isHashIdentical!K) {
100     return cast(size_t) key;
101 }
102 
103 private enum size_t minimumBucketListSize = 8;
104 
105 @nogc @safe pure nothrow
106 private size_t newBucketListSize(size_t currentLength) {
107     return currentLength * 2;
108 }
109 
110 @trusted
111 private size_t bucketListSearch(SearchFor searchFor, K, V)
112 (ref const(Entry!(K, V)[]) bucketList, const(K) key)
113 if (isHashIdentical!K) {
114     size_t index = castHash(key) & (bucketList.length - 1);
115 
116     foreach(j; 1 .. bucketList.length) {
117         static if (searchFor == SearchFor.notOccupied) {
118             if (bucketList[index]._state != EntryState.occupied) {
119                 return index;
120             }
121 
122             if (bucketList[index]._key == key) {
123                 return index;
124             }
125         } else {
126             static if (searchFor & SearchFor.empty) {
127                 if (bucketList[index]._state == EntryState.empty) {
128                     return index;
129                 }
130             }
131 
132             static if (searchFor & SearchFor.deleted) {
133                 if (bucketList[index]._state == EntryState.deleted
134                 && bucketList[index]._key == key) {
135                     return index;
136                 }
137             }
138 
139             static if (searchFor & SearchFor.occupied) {
140                 if (bucketList[index]._state == EntryState.occupied
141                 && bucketList[index]._key == key) {
142                     return index;
143                 }
144             }
145         }
146 
147         index = (index + j) & (bucketList.length - 1);
148     }
149 
150     assert(false, "Slot not found!");
151 }
152 
153 @trusted
154 private size_t bucketListSearch(SearchFor searchFor, K, V)
155 (ref const(Entry!(K, V)[]) bucketList, size_t hash, const(K) key)
156 if (!isHashIdentical!K) {
157     size_t index = hash & (bucketList.length - 1);
158 
159     foreach(j; 1 .. bucketList.length) {
160         static if (searchFor == SearchFor.notOccupied) {
161             if (bucketList[index]._state != EntryState.occupied) {
162                 return index;
163             }
164 
165             if (bucketList[index]._hash == hash
166             && bucketList[index]._key == key) {
167                 return index;
168             }
169         } else {
170             static if (searchFor & SearchFor.empty) {
171                 if (bucketList[index]._state == EntryState.empty) {
172                     return index;
173                 }
174             }
175 
176             static if (searchFor & SearchFor.deleted) {
177                 if (bucketList[index]._state == EntryState.deleted
178                 && bucketList[index]._hash == hash
179                 && bucketList[index]._key == key) {
180                     return index;
181                 }
182 
183             }
184 
185             static if (searchFor & SearchFor.occupied) {
186                 if (bucketList[index]._state == EntryState.occupied
187                 && bucketList[index]._hash == hash
188                 && bucketList[index]._key == key) {
189                     return index;
190                 }
191             }
192         }
193 
194         index = (index + j) & (bucketList.length - 1);
195     }
196 
197     assert(false, "Slot not found!");
198 }
199 
200 // Add an entry into the bucket list.
201 // memcpy is used here because some types have immutable members which cannot
202 // be changed, so we have to force them into the array this way.
203 @nogc @trusted pure nothrow
204 private void setEntry(K, V)(ref Entry!(K, V)[] bucketList,
205 size_t index, auto ref K key, auto ref V value) {
206     enum valueOffset = alignedSize(K.sizeof);
207 
208     // Copy the key and value into the entry.
209     memcpy(cast(void*) &bucketList[index], &key, K.sizeof);
210     memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof);
211 
212     bucketList[index]._state = EntryState.occupied;
213 }
214 
215 @nogc @trusted pure nothrow
216 private void setEntry(K, V)(ref Entry!(K, V)[] bucketList,
217 size_t index, size_t hash, auto ref K key, auto ref V value) {
218     enum valueOffset = alignedSize(K.sizeof);
219 
220     // Copy the key and value into the entry.
221     memcpy(cast(void*) &bucketList[index], &key, K.sizeof);
222     memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof);
223 
224     bucketList[index]._hash = hash;
225     bucketList[index]._state = EntryState.occupied;
226 }
227 
228 // Update just the value for an entry.
229 @nogc @trusted pure nothrow
230 private void updateEntryValue(K, V)(ref Entry!(K, V)[] bucketList,
231 size_t index, auto ref V value) {
232     enum valueOffset = alignedSize(K.sizeof);
233 
234     memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof);
235 }
236 
237 @nogc @trusted pure nothrow
238 private void zeroEntryValue(K, V)(ref Entry!(K, V)[] bucketList,
239 size_t index) {
240     enum valueOffset = alignedSize(K.sizeof);
241 
242     memset(cast(void*) &bucketList[index] + valueOffset, 0, V.sizeof);
243 }
244 
245 @nogc @trusted pure nothrow
246 private bool thresholdPassed(size_t length, size_t bucketCount) {
247     return length * 2 >= bucketCount;
248 }
249 
250 /**
251  * This struct implements a hashmap type, much like the standard associative
252  * array type.
253  *
254  * This map should be almost totally usable in @safe pure nothrow functions.
255  *
256  * An empty map will be a valid object, and will not result in any allocations.
257  */
258 struct HashMap(K, V)
259 if(isAssignmentCopyable!(Unqual!K) && isAssignmentCopyable!(Unqual!V)) {
260     alias ThisType = typeof(this);
261 
262     private Entry!(K, V)[] _bucketList;
263     private size_t _length;
264 
265     /**
266      * Construct a hashmap reserving a minimum of :minimumSize: space
267      * for the bucket list. The actual space allocated may be some number
268      * larger than the requested size, but it will be enough to fit
269      * as many items as requested without another allocation.
270      *
271      * Params:
272      * minimumSize = The minimum size for the hashmap.
273      */
274     @safe pure nothrow
275     this(size_t minimumSize) {
276         if (minimumSize == 0) {
277             // 0 is a special case.
278             return;
279         }
280 
281         if (minimumSize <= minimumBucketListSize / 2) {
282             _bucketList = new Entry!(K, V)[](minimumBucketListSize);
283         } else {
284             // Find the next largest power of two which will fit this size.
285             size_t size = 8;
286 
287             while (thresholdPassed(minimumSize, size)) {
288                 size *= 2;
289             }
290 
291             _bucketList = new Entry!(K, V)[](newBucketListSize(size));
292         }
293     }
294 
295     @trusted
296     private void copyToBucketList(ref Entry!(K, V)[] newBucketList) const {
297         foreach(ref entry; _bucketList) {
298             if (entry._state != EntryState.occupied) {
299                 // Skip holes in the container.
300                 continue;
301             }
302 
303             static if (isHashIdentical!K) {
304                 size_t index =
305                     bucketListSearch!(SearchFor.empty, K, V)
306                     (newBucketList, cast(K) entry._key);
307 
308                 newBucketList.setEntry(
309                     index,
310                     cast(K) entry._key,
311                     cast(V) entry._value
312                 );
313             } else {
314                 size_t index =
315                     bucketListSearch!(SearchFor.empty, K, V)
316                     (newBucketList, entry._hash, cast(K) entry._key);
317 
318                 newBucketList.setEntry(
319                     index,
320                     entry._hash,
321                     cast(K) entry._key,
322                     cast(V) entry._value
323                 );
324             }
325         }
326     }
327 
328     @trusted
329     private void resize(size_t newBucketListLength) in {
330         assert(newBucketListLength > _bucketList.length);
331     } body {
332         auto newBucketList = new Entry!(K, V)[](newBucketListLength);
333 
334         copyToBucketList(newBucketList);
335 
336         _bucketList = newBucketList;
337     }
338 
339     /**
340      * Set a value in the map.
341      *
342      * Params:
343      *     key = The key in the map.
344      *     value = A value to set in the map.
345      */
346     void opIndexAssign(V value, K key) {
347         static if (!isHashIdentical!K) {
348             size_t hash = computeHash(key);
349         }
350 
351         if (_bucketList.length == 0) {
352             // 0 length is a special case.
353             _length = 1;
354             resize(minimumBucketListSize);
355 
356             static if (isHashIdentical!K) {
357                 size_t index = castHash(key) & (_bucketList.length - 1);
358 
359                 _bucketList.setEntry(index, key, value);
360             } else {
361                 size_t index = hash & (_bucketList.length - 1);
362 
363                 _bucketList.setEntry(index, hash, key, value);
364             }
365 
366             return;
367         }
368 
369         static if (isHashIdentical!K) {
370             size_t index =
371                 bucketListSearch!(SearchFor.notDeleted, K, V)
372                 (_bucketList, key);
373         } else {
374             size_t index =
375                 bucketListSearch!(SearchFor.notDeleted, K, V)
376                 (_bucketList, hash, key);
377         }
378 
379         if (_bucketList[index]._state != EntryState.occupied) {
380             // This slot is not occupied, so insert the entry here.
381             static if (isHashIdentical!K) {
382                 _bucketList.setEntry(index, key, value);
383             } else {
384                 _bucketList.setEntry(index, hash, key, value);
385             }
386 
387             ++_length;
388 
389             if (thresholdPassed(_length, _bucketList.length)) {
390                 // Resize the bucketList, as it passed the threshold.
391                 resize(newBucketListSize(_bucketList.length));
392             }
393         } else {
394             // We have this key already, so update the value.
395             _bucketList.updateEntryValue(index, value);
396         }
397     }
398 
399     /**
400      * Implement the 'in' operator for a map.
401      *
402      * The in operator on a map will return a pointer to a value, which will
403      * be null when no corresponding value is set for a given key.
404      *
405      * Params:
406      *     key = The key in the map.
407      *
408      * Returns:
409      *     A pointer to a value, a null pointer if a value is not set.
410      */
411     inout(V)* opBinaryRight(string op)(K key) inout if (op == "in") {
412         if (_bucketList.length == 0)
413             return null;
414 
415         static if (isHashIdentical!K) {
416             size_t index =
417                 bucketListSearch!(SearchFor.notDeleted, K, V)
418                 (_bucketList, key);
419         } else {
420             size_t index =
421                 bucketListSearch!(SearchFor.notDeleted, K, V)
422                 (_bucketList, computeHash(key), key);
423         }
424 
425         if (_bucketList[index]._state == EntryState.empty) {
426             return null;
427         }
428 
429         return &(_bucketList[index]._value);
430     }
431 
432     /**
433      * Retrieve a value from the map.
434      *
435      * If a value is not set for the given key, a RangeError will be thrown.
436      *
437      * Params:
438      *     key = The key in the map.
439      *
440      * Returns:
441      *     A value from the map.
442      */
443     ref inout(V) opIndex(K key) inout {
444         static if (isHashIdentical!K) {
445             size_t index =
446                 bucketListSearch!(SearchFor.notDeleted, K, V)
447                 (_bucketList, key);
448         } else {
449             size_t index =
450                 bucketListSearch!(SearchFor.notDeleted, K, V)
451                 (_bucketList, computeHash(key), key);
452         }
453 
454         assert(
455             _bucketList[index]._state != EntryState.empty,
456             "Key not found in HashMap!"
457         );
458 
459         return _bucketList[index]._value;
460     }
461 
462     /**
463      * Get a value from the map, or return the given default value, which
464      * is lazy-evaluated.
465      *
466      * Params:
467      *     key = The key in the map.
468      *     def = A lazy default value.
469      *
470      * Returns:
471      *     A value from the map, or the default value.
472      */
473     V get(V2)(K key, lazy V2 def) const if(is(V2 : V)) {
474         if (_bucketList.length == 0)
475             return def;
476 
477         static if (isHashIdentical!K) {
478             size_t index =
479                 bucketListSearch!(SearchFor.notDeleted, K, V)
480                 (_bucketList, key);
481         } else {
482             size_t index =
483                 bucketListSearch!(SearchFor.notDeleted, K, V)
484                 (_bucketList, computeHash(key), key);
485         }
486 
487         if (_bucketList[index]._state == EntryState.empty) {
488             return def();
489         }
490 
491         return _bucketList[index]._value;
492     }
493 
494     /**
495      * Get a value from the map, or return V.init if a value is not set for
496      * a given key.
497      *
498      * Params:
499      *     key = The key in the map.
500      *
501      * Returns:
502      *     A value from the map, or the default value.
503      */
504     inout(V) get(K key) inout {
505         static if (isHashIdentical!K) {
506             size_t index =
507                 bucketListSearch!(SearchFor.notDeleted, K, V)
508                 (_bucketList, key);
509         } else {
510             size_t index =
511                 bucketListSearch!(SearchFor.notDeleted, K, V)
512                 (_bucketList, computeHash(key), key);
513         }
514 
515 
516         if (_bucketList[index]._state == EntryState.empty) {
517             return V.init;
518         }
519 
520         return _bucketList[index]._value;
521     }
522 
523     /**
524      * Get or create a value from/in the map.
525      *
526      * Given a key, and a lazy evaluated default value,
527      * attempt to retrieve a value from the map. If a value for the given
528      * key is not set, set the provided default value in the map and
529      * return that.
530      *
531      * The value will be returned by reference.
532      *
533      * Params:
534      *     key = The key in the map.
535      *     def = A lazy default value.
536      *
537      * Returns:
538      *     A reference to the value in the map.
539      */
540     ref V setDefault(V2)(K key, lazy V2 value) if (is(V2 : V)) {
541         static if (!isHashIdentical!K) {
542             size_t hash = computeHash(key);
543         }
544 
545         if (_bucketList.length == 0) {
546             // 0 length is a special case.
547             _length = 1;
548             resize(minimumBucketListSize);
549 
550             static if (isHashIdentical!K) {
551                 size_t index = castHash(key) & (_bucketList.length - 1);
552 
553                 _bucketList.setEntry(index, key, V.init);
554             } else {
555                 size_t index = hash & (_bucketList.length - 1);
556 
557                 _bucketList.setEntry(index, hash, key, V.init);
558             }
559 
560             return _bucketList[index].value;
561         }
562 
563         static if (isHashIdentical!K) {
564             size_t index =
565                 bucketListSearch!(SearchFor.notDeleted, K, V)
566                 (_bucketList, key);
567         } else {
568             size_t index =
569                 bucketListSearch!(SearchFor.notDeleted, K, V)
570                 (_bucketList, hash, key);
571         }
572 
573         if (_bucketList[index]._state == EntryState.empty) {
574             // The entry is empty, so we can insert the value here.
575             static if (isHashIdentical!K) {
576                 _bucketList.setEntry(index, key, value());
577             } else {
578                 _bucketList.setEntry(index, hash, key, value());
579             }
580 
581             ++_length;
582 
583             if (thresholdPassed(_length, _bucketList.length)) {
584                 // Resize the bucketList, as it passed the threshold.
585                 resize(newBucketListSize(_bucketList.length));
586 
587                 // Update the index, it has now changed.
588                 static if (isHashIdentical!K) {
589                     index = bucketListSearch!(SearchFor.notDeleted, K, V)
590                         (_bucketList, key);
591                 } else {
592                     index = bucketListSearch!(SearchFor.notDeleted, K, V)
593                         (_bucketList, hash, key);
594                 }
595             }
596         }
597 
598         // Return a reference to the value.
599         return _bucketList[index]._value;
600     }
601 
602     /**
603      * Get or create a value from/in a hashmap.
604      *
605      * Given a key attempt to retrieve a value from the hashmap.
606      * If a value for the given key is not set, set the value in
607      * the associative array to the default value for the value's type.
608      *
609      * The value will be returned by reference.
610      *
611      * Params:
612      *     key = The key in the map.
613      *
614      * Returns:
615      *     A reference to the value in the map.
616      */
617     ref V setDefault(K key) {
618         static if (!isHashIdentical!K) {
619             size_t hash = computeHash(key);
620         }
621 
622         if (_bucketList.length == 0) {
623             // 0 length is a special case.
624             _length = 1;
625             resize(minimumBucketListSize);
626 
627             static if (isHashIdentical!K) {
628                 size_t index = castHash(key) & (_bucketList.length - 1);
629 
630                 _bucketList.setEntry(index, key, V.init);
631             } else {
632                 size_t index = hash & (_bucketList.length - 1);
633 
634                 _bucketList.setEntry(index, hash, key, V.init);
635             }
636 
637             return _bucketList[index].value;
638         }
639 
640         static if (isHashIdentical!K) {
641             size_t index =
642                 bucketListSearch!(SearchFor.notDeleted, K, V)
643                 (_bucketList, key);
644         } else {
645             size_t index =
646                 bucketListSearch!(SearchFor.notDeleted, K, V)
647                 (_bucketList, hash, key);
648         }
649 
650         if (_bucketList[index]._state == EntryState.empty) {
651             // The entry is empty, so we can insert the value here.
652             static if (isHashIdentical!K) {
653                 _bucketList.setEntry(index, key, V.init);
654             } else {
655                 _bucketList.setEntry(index, hash, key, V.init);
656             }
657 
658             ++_length;
659 
660             if (thresholdPassed(_length, _bucketList.length)) {
661                 // Resize the bucketList, as it passed the threshold.
662                 resize(newBucketListSize(_bucketList.length));
663 
664                 // Update the index, it has now changed.
665                 static if (isHashIdentical!K) {
666                     index = bucketListSearch!(SearchFor.notDeleted, K, V)
667                         (_bucketList, key);
668                 } else {
669                     index = bucketListSearch!(SearchFor.notDeleted, K, V)
670                         (_bucketList, hash, key);
671                 }
672             }
673         }
674 
675         // Return a reference to the value.
676         return _bucketList[index]._value;
677     }
678 
679     /**
680      * Remove a entry from the map if it is set, given a key.
681      *
682      * Params:
683      *     key = The key in the map.
684      *
685      * Returns:
686      *     true if a value was removed, otherwise false.
687      */
688     bool remove(K key) {
689         if (_bucketList.length == 0)
690             return false;
691 
692         static if (isHashIdentical!K) {
693             size_t index =
694                 bucketListSearch!(SearchFor.any, K, V)
695                 (_bucketList, key);
696         } else {
697             size_t index =
698                 bucketListSearch!(SearchFor.any, K, V)
699                 (_bucketList, computeHash(key), key);
700         }
701 
702         if (_bucketList[index]._state == EntryState.occupied) {
703             --_length;
704 
705             // Zero the value and mark the slot as 'deleted', which is
706             // treated often the same as 'empty', only we can skip over
707             // deleted values to search for more values.
708             _bucketList.zeroEntryValue(index);
709             _bucketList[index]._state = EntryState.deleted;
710 
711             return true;
712         }
713 
714         return false;
715     }
716 
717     /**
718      * The length of the map.
719      *
720      * Returns: The number of entries in the map, in constant time.
721      */
722     @nogc @safe pure nothrow
723     @property size_t length() const {
724         return _length;
725     }
726 
727     /**
728      * Returns: True if this map is empty.
729      */
730     @nogc @safe pure nothrow
731     @property bool empty() const {
732         return _length == 0;
733     }
734 
735     /**
736      * Implement boolean conversion for a map.
737      *
738      * Returns: True if this set is not empty.
739      */
740     @nogc @safe pure nothrow
741     bool opCast(T: bool)() const {
742         return !empty;
743     }
744 
745     static if(isDupable!K && isDupable!V) {
746         /**
747          * Copy an existing map into a new mutable map.
748          *
749          * Returns:
750          *     The fresh copy of the map.
751          */
752         @safe pure nothrow
753         HashMap!(K, V) dup() const {
754             if (_length == 0) {
755                 // Just return nothing special for length 0.
756                 return HashMap!(K, V).init;
757             }
758 
759             // Create a new map large enough to fit all of our values.
760             auto newMap = HashMap!(K, V)(_length);
761             newMap._length = _length;
762 
763             copyToBucketList(newMap._bucketList);
764 
765             return newMap;
766         }
767     }
768 
769 
770     /**
771      * Test if two maps are equal.
772      *
773      * Params:
774      *     otherMap = Another map.
775      *
776      * Returns:
777      *     true only if the maps are equal in length, keys, and values.
778      */
779     bool opEquals(ref const(HashMap!(K, V)) otherMap) const {
780         if (_length != otherMap._length) {
781             return false;
782         }
783 
784         foreach(ref entry; _bucketList) {
785             if (entry._state != EntryState.occupied) {
786                 // Skip holes in the container.
787                 continue;
788             }
789 
790             static if (isHashIdentical!K) {
791                 size_t index =
792                     bucketListSearch!(SearchFor.notDeleted, K, V)
793                     (otherMap._bucketList, entry._key);
794             } else {
795                 size_t index =
796                     bucketListSearch!(SearchFor.notDeleted, K, V)
797                     (otherMap._bucketList, entry._hash, entry._key);
798             }
799 
800             if (otherMap._bucketList[index]._state == EntryState.empty) {
801                 return false;
802             }
803         }
804 
805         return true;
806     }
807 
808     /// ditto
809     bool opEquals(const(HashMap!(K, V)) otherMap) const {
810         return opEquals(otherMap);
811     }
812 }
813 
814 template HashMapKeyType(T) {
815     alias HashMapKeyType = typeof(ElementType!(typeof(T._bucketList))._key);
816 }
817 
818 template HashMapValueType(T) {
819     alias HashMapValueType = typeof(ElementType!(typeof(T._bucketList))._value);
820 }
821 
822 // Test that is is not possible to create a map with a key or a value type
823 // which cannot be copy-assigned.
824 unittest {
825     struct NonCopyable { @disable this(this); }
826 
827     assert(!__traits(compiles, HashMap!(NonCopyable, int)));
828     assert(__traits(compiles, HashMap!(NonCopyable*, int)));
829     assert(!__traits(compiles, HashMap!(int, NonCopyable)));
830     assert(__traits(compiles, HashMap!(int, NonCopyable*)));
831 }
832 
833 // Check setting values, retrieval, removal, and lengths.
834 unittest {
835     HashMap!(int, string) map;
836 
837     map[1] = "a";
838     map[2] = "b";
839     map[3] = "c";
840 
841     map[1] = "x";
842     map[2] = "y";
843     map[3] = "z";
844 
845     assert(map.length == 3);
846 
847     assert(map[1] == "x");
848     assert(map[2] == "y");
849     assert(map[3] == "z");
850 
851     assert(map.remove(3));
852     assert(map.remove(2));
853     assert(map.remove(1));
854 
855     assert(!map.remove(1));
856 
857     assert(map.length == 0);
858 }
859 
860 unittest {
861     HashMap!(int, string) map;
862 
863     map[1] = "a";
864     map[2] = "b";
865     map[3] = "c";
866     map[4] = "d";
867     map[5] = "e";
868     map[6] = "f";
869     map[7] = "g";
870     map[8] = "h";
871     map[9] = "i";
872 
873     map[1] = "x";
874     map[2] = "y";
875     map[3] = "z";
876 
877     assert(map.length == 9);
878 
879     assert(map[1] == "x");
880     assert(map[2] == "y");
881     assert(map[3] == "z");
882     assert(map[4] == "d");
883     assert(map[5] == "e");
884     assert(map[6] == "f");
885     assert(map[7] == "g");
886     assert(map[8] == "h");
887     assert(map[9] == "i");
888 
889     assert(map.remove(3));
890     assert(map.remove(2));
891     assert(map.remove(1));
892 
893     assert(!map.remove(1));
894 
895     assert(map.length == 6);
896 }
897 
898 // Test the map with heavy collisions.
899 unittest {
900     struct BadHashObject {
901         int value;
902 
903         this(int value) {
904             this.value = value;
905         }
906 
907         @safe nothrow
908         size_t toHash() const {
909             return 0;
910         }
911 
912         @nogc @safe nothrow pure
913         bool opEquals(ref const BadHashObject other) const {
914             return value == other.value;
915         }
916     }
917 
918     HashMap!(BadHashObject, string) map;
919     enum size_t mapSize = 100;
920 
921     foreach(num; 0 .. mapSize) {
922         map[BadHashObject(cast(int) num)] = "a";
923     }
924 
925     assert(map.length == mapSize);
926 }
927 
928 // Test preallocated maps;
929 unittest {
930     auto map = HashMap!(int, string)(3);
931 
932     assert(map._bucketList.length == minimumBucketListSize);
933 }
934 
935 // Test the 'in' operator.
936 unittest {
937     // We'll test that our attributes work.
938     @safe pure nothrow
939     void runTest() {
940         HashMap!(int, string) map;
941 
942         map[1] = "a";
943         map[2] = "b";
944         map[3] = "c";
945 
946         // Test that @nogc works.
947         @nogc @safe pure nothrow
948         void runNoGCPart(typeof(map) map) {
949             assert((4 in map) is null);
950 
951             assert(*(1 in map) == "a");
952             assert(*(2 in map) == "b");
953             assert(*(3 in map) == "c");
954         }
955 
956         runNoGCPart(map);
957     }
958 
959     runTest();
960 }
961 
962 // Test the map with a weird type which makes assignment harder.
963 unittest {
964     struct WeirdType {
965     // The alignment could cause memory issues so we'll check for that.
966     align(1):
967         // This immutable member means we need to use memset above to set
968         // keys or values.
969         immutable(byte)* foo = null;
970         size_t x = 3;
971 
972         @nogc @safe pure nothrow
973         this(int value) {
974             x = value;
975         }
976 
977         @nogc @safe pure nothrow
978         size_t toHash() const {
979             return x;
980         }
981 
982         @nogc @safe pure nothrow
983         bool opEquals(ref const(WeirdType) other) const {
984             return foo == other.foo && x == other.x;
985         }
986 
987         @nogc @safe pure nothrow
988         bool opEquals(const(WeirdType) other) const {
989             return opEquals(other);
990         }
991     }
992 
993     @safe pure nothrow
994     void runTest() {
995         HashMap!(WeirdType, string) map;
996 
997         map[WeirdType(10)] = "a";
998 
999         @nogc @safe pure nothrow
1000         void runNoGCPart(typeof(map) map) {
1001             assert(map[WeirdType(10)] == "a");
1002         }
1003 
1004         runNoGCPart(map);
1005     }
1006 
1007     runTest();
1008 }
1009 
1010 // Test get with default init
1011 unittest {
1012     @safe pure nothrow
1013     void runTest() {
1014         HashMap!(int, string) map;
1015 
1016         map[1] = "a";
1017 
1018         @nogc @safe pure nothrow
1019         void runNoGCPart(typeof(map) map) {
1020             assert(map.get(1) == "a");
1021             assert(map.get(2) is null);
1022         }
1023         runNoGCPart(map);
1024     }
1025 
1026     runTest();
1027 
1028 }
1029 
1030 // Test length, empty, and cast(bool)
1031 unittest {
1032     @safe pure nothrow
1033     void runTest() {
1034         HashMap!(int, string) map;
1035 
1036         @nogc @safe pure nothrow
1037         void runNoGCPart1(typeof(map) map) {
1038             assert(map.length == 0);
1039             assert(map.empty);
1040 
1041             if (map) {
1042                 assert(false, "cast(bool) failed for an empty map");
1043             }
1044         }
1045 
1046         @nogc @safe pure nothrow
1047         void runNoGCPart2(typeof(map) map) {
1048             assert(map.length == 1);
1049             assert(!map.empty);
1050 
1051             if (!map) {
1052                 assert(false, "cast(bool) failed for an non-empty map");
1053             }
1054         }
1055 
1056         @nogc @safe pure nothrow
1057         void runNoGCPart3(typeof(map) map) {
1058             assert(map.length == 0);
1059             assert(map.empty);
1060 
1061             if (map) {
1062                 assert(false, "cast(bool) failed for an empty map");
1063             }
1064         }
1065 
1066         runNoGCPart1(map);
1067         map[1] = "a";
1068         runNoGCPart2(map);
1069         map.remove(1);
1070         runNoGCPart3(map);
1071     }
1072 
1073     runTest();
1074 }
1075 
1076 // BUG: The lazy argument here cannot be made to be nothrow, @nogc, etc.
1077 // Test get with a given default.
1078 unittest {
1079     HashMap!(int, string) map;
1080 
1081     map[1] = "a";
1082 
1083     assert(map.get(1, "b") == "a");
1084     assert(map.get(2, "b") == "b");
1085 }
1086 
1087 // Test opEquals
1088 unittest {
1089     @safe pure nothrow
1090     void runTest() {
1091         HashMap!(string, string) leftMap;
1092         HashMap!(string, string) rightMap;
1093 
1094         // Give the left one a bit more, and take away from it.
1095         leftMap["a"] = "1";
1096         leftMap["b"] = "2";
1097         leftMap["c"] = "3";
1098         leftMap["d"] = "4";
1099         leftMap["e"] = "5";
1100         leftMap["f"] = "6";
1101         leftMap["g"] = "7";
1102         leftMap["h"] = "8";
1103         leftMap["i"] = "9";
1104         leftMap["j"] = "10";
1105 
1106         rightMap["a"] = "1";
1107         rightMap["b"] = "2";
1108         rightMap["c"] = "3";
1109 
1110         @nogc @safe pure nothrow
1111         void runNoGCPart(typeof(leftMap) leftMap, typeof(rightMap) rightMap) {
1112             // Remove the extra keys
1113             leftMap.remove("d");
1114             leftMap.remove("e");
1115             leftMap.remove("f");
1116             leftMap.remove("g");
1117             leftMap.remove("h");
1118             leftMap.remove("i");
1119             leftMap.remove("j");
1120 
1121             // Now the two maps should have different bucketLists, but they
1122             // should still be considered equal.
1123             assert(leftMap == rightMap);
1124         }
1125 
1126         runNoGCPart(leftMap, rightMap);
1127     }
1128 
1129     runTest();
1130 }
1131 
1132 // Test setDefault with default init
1133 unittest {
1134     @safe pure nothrow
1135     void runTest() {
1136         HashMap!(int, string) map;
1137 
1138         map[1] = "a";
1139 
1140         // setDefault for basic types with no explicit default ought to
1141         // be nothrow.
1142         assert(map.setDefault(1) == "a");
1143         assert(map.setDefault(2) is null);
1144 
1145         assert(map.length == 2);
1146 
1147         assert(map[2] is null);
1148     }
1149 
1150     runTest();
1151 }
1152 
1153 // Test setDefault with a given value.
1154 unittest {
1155     HashMap!(int, string) map;
1156 
1157     map[1] = "a";
1158 
1159     assert(map.setDefault(1, "b") == "a");
1160     assert(map.setDefault(2, "b") == "b");
1161 
1162     assert(map.length == 2);
1163 
1164     assert(map[2] == "b");
1165 }
1166 
1167 // Test setDefault with a given value which can be implicitly converted.
1168 unittest {
1169     HashMap!(int, long) map;
1170 
1171     map[1] = 2;
1172 
1173     assert(map.setDefault(1, 3) == 2);
1174 
1175     int x = 4;
1176 
1177     assert(map.setDefault(2, x) == 4);
1178 
1179     assert(map.length == 2);
1180 
1181     assert(map[2] == 4);
1182 }
1183 
1184 // Test operations work with empty map (issue #3).
1185 unittest {
1186     HashMap!(int, string) emptyMap;
1187     assert(0 !in emptyMap);
1188     assert(!emptyMap.remove(0));
1189     assert(emptyMap.get(0, "a") == "a");
1190 }
1191 
1192 /**
1193  * A range through a series of items in the map.
1194  */
1195 struct KeyValueRange(K, V) {
1196 private:
1197     Entry!(K, V)[] _bucketList = null;
1198 public:
1199     @nogc @safe pure nothrow
1200     this(Entry!(K, V)[] bucketList) {
1201         foreach(index, ref entry; bucketList) {
1202             if (entry._state == EntryState.occupied) {
1203                 // Use a slice of the bucketList starting here.
1204                 _bucketList = bucketList[index .. $];
1205 
1206                 return;
1207             }
1208         }
1209     }
1210 
1211     @nogc @trusted pure nothrow
1212     this(const(Entry!(K, V)[]) bucketList) {
1213         this(cast(Entry!(K, V)[]) bucketList);
1214     }
1215 
1216     @nogc @safe pure nothrow
1217     inout(typeof(this)) save() inout {
1218         return this;
1219     }
1220 
1221     @nogc @safe pure nothrow
1222     @property
1223     bool empty() const {
1224         // We can check that the bucketList is empty to check if this range is
1225         // empty, because we will clear it after we pop the last item.
1226         return _bucketList.length == 0;
1227     }
1228 
1229     @nogc @safe pure nothrow
1230     @property
1231     ref inout(Entry!(K, V)) front() inout in {
1232         assert(!empty());
1233     } body {
1234         return _bucketList[0];
1235     }
1236 
1237     @nogc @safe pure nothrow
1238     void popFront() in {
1239         assert(!empty());
1240     } body {
1241         foreach(index; 1 .. _bucketList.length) {
1242             if (_bucketList[index]._state == EntryState.occupied) {
1243                 // Use a slice of the bucketList starting here.
1244                 _bucketList = _bucketList[index .. $];
1245 
1246                 return;
1247             }
1248         }
1249 
1250         // Clear the bucketList if we hit the end.
1251         _bucketList = null;
1252     }
1253 }
1254 
1255 /**
1256  * Produce a range through the items of a map. (A key-value pair)
1257  *
1258  * Params:
1259  *     map = A map.
1260  * Returns:
1261  *     A range running through the items in the map.
1262  */
1263 @nogc @safe pure nothrow
1264 auto byKeyValue(K, V)(auto ref HashMap!(K, V) map) {
1265     if (map.length == 0) {
1266         // Empty ranges should not have to traverse the bucketList at all.
1267         return KeyValueRange!(K, V).init;
1268     }
1269 
1270     return KeyValueRange!(K, V)(map._bucketList);
1271 }
1272 
1273 /// ditto
1274 @nogc @trusted pure nothrow
1275 auto byKeyValue(K, V)(auto ref const(HashMap!(K, V)) map) {
1276     alias RealK = HashMapKeyType!(typeof(map));
1277     alias RealV = HashMapValueType!(typeof(map));
1278 
1279     if (map.length == 0) {
1280         return KeyValueRange!(RealK, RealV).init;
1281     }
1282 
1283     return KeyValueRange!(RealK, RealV)(
1284         cast(Entry!(RealK, RealV)[])
1285         map._bucketList
1286     );
1287 }
1288 
1289 /// ditto
1290 @nogc @trusted pure nothrow
1291 auto byKeyValue(K, V)(auto ref immutable(HashMap!(K, V)) map) {
1292     alias RealK = HashMapKeyType!(typeof(map));
1293     alias RealV = HashMapValueType!(typeof(map));
1294 
1295     if (map.length == 0) {
1296         return KeyValueRange!(RealK, RealV).init;
1297     }
1298 
1299     return KeyValueRange!(RealK, RealV)(
1300         cast(Entry!(RealK, RealV)[])
1301         map._bucketList
1302     );
1303 }
1304 
1305 unittest {
1306     HashMap!(int, string) map;
1307 
1308     map[1] = "a";
1309     map[2] = "b";
1310     map[3] = "c";
1311 
1312     int[] keyList;
1313     string[] valueList;
1314 
1315     foreach(item; map.byKeyValue()) {
1316         keyList ~= item.key;
1317         valueList ~= item.value;
1318     }
1319 
1320     // From the way the bucketLists are distributed, we know we'll get this back.
1321     assert(keyList == [1, 2, 3]);
1322     assert(valueList == ["a", "b", "c"]);
1323 }
1324 
1325 unittest {
1326     HashMap!(string, string) mmap;
1327     const(HashMap!(string, string)) cmap;
1328     immutable(HashMap!(string, string)) imap;
1329 
1330     auto mItems = mmap.byKeyValue();
1331     auto cItems = cmap.byKeyValue();
1332     auto iItems = imap.byKeyValue();
1333 
1334     assert(is(typeof(mItems.front.key) == string));
1335     assert(is(typeof(cItems.front.key) == const(string)));
1336     assert(is(typeof(iItems.front.key) == immutable(string)));
1337     assert(is(typeof(mItems.front.value) == string));
1338     assert(is(typeof(cItems.front.value) == const(string)));
1339     assert(is(typeof(iItems.front.value) == immutable(string)));
1340 }
1341 
1342 // Test that the ranges can be created from r-values.
1343 unittest {
1344     auto func() {
1345         HashMap!(int, string) map;
1346 
1347         map[1] = "a";
1348         map[2] = "b";
1349         map[3] = "c";
1350 
1351         return map;
1352     }
1353 
1354     auto keyRange = func().byKey();
1355     auto valueRange = func().byValue();
1356     auto itemRange = func().byKeyValue();
1357 }
1358 
1359 /**
1360  * This is a range which runs through a series of keys in map.
1361  */
1362 struct KeyRange(K, V) {
1363 private:
1364     KeyValueRange!(K, V) _keyValueRange;
1365 public:
1366     @nogc @safe pure nothrow
1367     private this()(auto ref Entry!(K, V)[] bucketList) {
1368         _keyValueRange = KeyValueRange!(K, V)(bucketList);
1369     }
1370 
1371     ///
1372     @nogc @safe pure nothrow
1373     inout(typeof(this)) save() inout {
1374         return this;
1375     }
1376 
1377     ///
1378     @nogc @safe pure nothrow
1379     @property
1380     bool empty() const {
1381         return _keyValueRange.empty;
1382     }
1383 
1384     ///
1385     @nogc @trusted pure nothrow
1386     @property
1387     ref inout(K) front() inout {
1388         return _keyValueRange.front.key;
1389     }
1390 
1391     ///
1392     @nogc @safe pure nothrow
1393     void popFront() {
1394         _keyValueRange.popFront();
1395     }
1396 }
1397 
1398 /**
1399  * Produce a range through the keys of a map.
1400  *
1401  * Params:
1402  *     map = A map.
1403  * Returns:
1404  *     A range running through the keys in the map.
1405  */
1406 @nogc @safe pure nothrow
1407 auto byKey(K, V)(auto ref HashMap!(K, V) map) {
1408     if (map.length == 0) {
1409         return KeyRange!(K, V).init;
1410     }
1411 
1412     return KeyRange!(K, V)(map._bucketList);
1413 }
1414 
1415 /// ditto
1416 @nogc @trusted pure nothrow
1417 auto byKey(K, V)(auto ref const(HashMap!(K, V)) map) {
1418     alias RealK = HashMapKeyType!(typeof(map));
1419     alias RealV = HashMapValueType!(typeof(map));
1420 
1421     if (map.length == 0) {
1422         return KeyRange!(RealK, RealV).init;
1423     }
1424 
1425     return KeyRange!(RealK, RealV)(
1426         cast(Entry!(RealK, RealV)[])
1427         map._bucketList
1428     );
1429 }
1430 
1431 /// ditto
1432 @nogc @trusted pure nothrow
1433 auto byKey(K, V)(auto ref immutable(HashMap!(K, V)) map) {
1434     alias RealK = HashMapKeyType!(typeof(map));
1435     alias RealV = HashMapValueType!(typeof(map));
1436 
1437     if (map.length == 0) {
1438         return KeyRange!(RealK, RealV).init;
1439     }
1440 
1441     return KeyRange!(RealK, RealV)(
1442         cast(Entry!(RealK, RealV)[])
1443         map._bucketList
1444     );
1445 }
1446 
1447 unittest {
1448     HashMap!(int, string) map;
1449 
1450     map[1] = "a";
1451     map[2] = "b";
1452     map[3] = "c";
1453 
1454     int[] keyList;
1455 
1456     foreach(ref key; map.byKey()) {
1457         keyList ~= key;
1458     }
1459 
1460     // From the way the bucketLists are distributed, we know we'll get this back.
1461     assert(keyList == [1, 2, 3]);
1462 }
1463 
1464 unittest {
1465     HashMap!(string, string) mmap;
1466     const(HashMap!(string, string)) cmap;
1467     immutable(HashMap!(string, string)) imap;
1468 
1469     auto mKeys = mmap.byKey();
1470     auto cKeys = cmap.byKey();
1471     auto iKeys = imap.byKey();
1472 
1473     assert(is(typeof(mKeys.front) == string));
1474     assert(is(typeof(cKeys.front) == const(string)));
1475     assert(is(typeof(iKeys.front) == immutable(string)));
1476 }
1477 
1478 /**
1479  * This is a range which runs through a series of values in a map.
1480  */
1481 struct ValueRange(K, V) {
1482 private:
1483     KeyValueRange!(K, V) _keyValueRange;
1484 public:
1485     @nogc @safe pure nothrow
1486     private this()(auto ref Entry!(K, V)[] bucketList) {
1487         _keyValueRange = KeyValueRange!(K, V)(bucketList);
1488     }
1489 
1490     ///
1491     @nogc @safe pure nothrow
1492     inout(typeof(this)) save() inout {
1493         return this;
1494     }
1495 
1496     ///
1497     @nogc @safe pure nothrow
1498     @property
1499     bool empty() const {
1500         return _keyValueRange.empty;
1501     }
1502 
1503     ///
1504     @nogc @trusted pure nothrow
1505     @property
1506     ref inout(V) front() inout {
1507         return _keyValueRange.front.value;
1508     }
1509 
1510     ///
1511     @nogc @safe pure nothrow
1512     void popFront() {
1513         _keyValueRange.popFront();
1514     }
1515 }
1516 
1517 /**
1518  * Produce a range through the values of a map.
1519  *
1520  * Params:
1521  *     map = A map.
1522  * Returns:
1523  *     A range running through the values in the map.
1524  */
1525 @nogc @safe pure nothrow
1526 auto byValue(K, V)(auto ref HashMap!(K, V) map) {
1527     if (map.length == 0) {
1528         return ValueRange!(K, V).init;
1529     }
1530 
1531     return ValueRange!(K, V)(map._bucketList);
1532 }
1533 
1534 /// ditto
1535 @nogc @trusted pure nothrow
1536 auto byValue(K, V)(auto ref const(HashMap!(K, V)) map) {
1537     alias RealK = HashMapKeyType!(typeof(map));
1538     alias RealV = HashMapValueType!(typeof(map));
1539 
1540     if (map.length == 0) {
1541         return ValueRange!(RealK, RealV).init;
1542     }
1543 
1544     return ValueRange!(RealK, RealV)(
1545         cast(Entry!(RealK, RealV)[])
1546         map._bucketList
1547     );
1548 }
1549 
1550 /// ditto
1551 @nogc @trusted pure nothrow
1552 auto byValue(K, V)(auto ref immutable(HashMap!(K, V)) map) {
1553     alias RealK = HashMapKeyType!(typeof(map));
1554     alias RealV = HashMapValueType!(typeof(map));
1555 
1556     if (map.length == 0) {
1557         return ValueRange!(RealK, RealV).init;
1558     }
1559 
1560     return ValueRange!(RealK, RealV)(
1561         cast(Entry!(RealK, RealV)[])
1562         map._bucketList
1563     );
1564 }
1565 
1566 unittest {
1567     HashMap!(int, string) map;
1568 
1569     map[1] = "a";
1570     map[2] = "b";
1571     map[3] = "c";
1572 
1573     string[] valueList = [];
1574 
1575     foreach(ref value; map.byValue()) {
1576         valueList ~= value;
1577     }
1578 
1579     // From the way the buckets are distributed, we know we'll get this back.
1580     assert(valueList == ["a", "b", "c"]);
1581 }
1582 
1583 unittest {
1584     HashMap!(string, string) mmap;
1585     const(HashMap!(string, string)) cmap;
1586     immutable(HashMap!(string, string)) imap;
1587 
1588     auto mValues = mmap.byValue();
1589     auto cValues = cmap.byValue();
1590     auto iValues = imap.byValue();
1591 
1592     assert(is(typeof(mValues.front) == string));
1593     assert(is(typeof(cValues.front) == const(string)));
1594     assert(is(typeof(iValues.front) == immutable(string)));
1595 }
1596 
1597 unittest {
1598     const(HashMap!(int, int)) createMap() {
1599         HashMap!(int, int) map;
1600 
1601         map[3] = 4;
1602         map[4] = 7;
1603 
1604         return map;
1605     }
1606 
1607     auto map = createMap();
1608     auto newMap = map.dup;
1609     // Test r-values.
1610     auto thirdMap = createMap().dup();
1611 
1612     assert(map == newMap);
1613 }
1614 
1615 unittest {
1616     HashMap!(int, void[0]) map;
1617 
1618     auto x = map.dup;
1619 }