1 // Written in the D programming language.
2 
3 module swisstable.map;
4 
5 import swisstable.group;
6 import swisstable.hash;
7 import swisstable.table;
8 
9 import std.experimental.allocator.mallocator : Mallocator;
10 
11 /**
12 Swiss tables hash map
13 
14 Examples:
15 ---
16 // Initialize Map with capacity
17 auto map = Map!(string, string)(10);  // Default capacity is 0
18 map["key"] = "value";
19 auto p = "key" in map;
20 if (p)
21   *p = "new value";
22 writeln(map["key"]);  // Print "new value"
23 
24 // Support AA properties
25 map.length;
26 map.clear;
27 map.rehash;
28 map.dup;
29 K[] keys = map.keys;
30 V[] values = map.values;
31 foreach (k; map.byKey()) {}
32 foreach (v; map.byValue()) {}
33 foreach (e; map.byKeyValue()) {}  // e has key and value properties
34 ---
35 
36 Params:
37  K = key type
38  V = value type
39  Allocator = Allocator type to use internal allocation. Default is "shared const Mallocator"
40  initSlotInIndex = Assign V.init instead of RangeError when key not found. Default is "true"
41 
42 `initSlotInIndex` is used for `++map[key]` use-cases.
43 
44 NOTE: Map doesn't guarantee pointer stability. Range and returned pointers are invalidated after insert/rehash.
45 
46 Copyright: Copyright 2024- Masahiro Nakagawa
47 Authros:   Masahiro Nakagawa
48 License:   $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache License Version 2.0)
49  */
50 struct Map(K, V, Allocator = shared const Mallocator, bool initSlotInIndex = true)
51 {
52     import std.traits : hasIndirections;
53     import std.typecons : Tuple;
54 
55     // These string enums are used in Table template
56     enum CompCondition = "key == _slots[offset].key";
57     enum HashKeyArg = "slot.key";
58 
59     alias SlotType = Tuple!(K, "key", V, "value");
60     mixin Table!(Map, K, SlotType, Allocator, hasIndirections!K || hasIndirections!V);
61 
62   public:
63     static if (initSlotInIndex) {
64         /**
65          * $(B map[key]) syntax support.
66          *
67          * With initSlotInIndex=true, $(B key)'s slot will be initialized if key doesn't exist.
68          */
69         ref V opIndex(in K key)
70         {
71             import std.traits : Unqual;
72 
73             bool found;
74             size_t pos = findOrPrepareInsert(key, found);
75             if (!found)
76                 _slots[pos] = SlotType(cast(Unqual!K)(key), V.init);
77 
78             return _slots[pos].value;
79         }
80     } else {
81         ref inout(V) opIndex(in K key) inout
82         {
83             import core.exception : onRangeError;
84 
85             bool found;
86             size_t pos = find(key, found);
87             if (found)
88                 return _slots[pos].value;
89 
90             onRangeError();
91         }
92     }
93 
94     /**
95      * $(B key in map) syntax support.
96      *
97      * Returns: pointer to value corresponding to key
98      */
99     inout(V)* opBinaryRight(string op)(in K key) inout if (op == "in")
100     {
101         bool found;
102         size_t pos = find(key, found);
103         if (found)
104             return &_slots[pos].value;
105 
106         return null;
107     }
108 
109     /**
110      * $(B map[key] = value) syntax support.
111      */    
112     void opIndexAssign(in V value, in K key)
113     {
114         import std.traits : Unqual;
115 
116         bool found;
117         size_t pos = findOrPrepareInsert(key, found);
118         if (found)
119             _slots[pos].value = cast(Unqual!V)value;
120         else
121             _slots[pos] = SlotType(cast(Unqual!K)key, cast(Unqual!V)value);
122     }
123 
124     /**
125      * Remove key from the map.
126      *
127      * Params:
128      *  key = key to search for
129      *
130      * Returns: true if given key does exist. otherwise false.
131      */
132     bool remove(in K key)
133     {
134         if (_size == 0)
135             return false;
136 
137         bool found;
138         size_t pos = find(key, found);
139         if (found) {
140             // destroy(_slots[pos]) is better?
141             destroy!(false)(_slots[pos]);
142 
143             // erase_meta_only in original implementation
144             --_size;
145             if (wasNeverFull(_control, _capacity, pos)) {
146                 setCtrl(_control, _capacity, pos, Control.Empty);
147                 growthInfo.overwriteFullAsEmpty();
148                 return true;
149             }
150 
151             growthInfo.overwriteFullAsTombstone();
152             setCtrl(_control, _capacity, pos, Control.Tombstone);
153             return true;
154         }
155 
156         return false;
157     }
158 
159     /// Returns: a GC allocated dynamic array, the elements of which are the keys in the map.
160     @property K[] keys() const nothrow
161     {
162         return funcKeysValuesBody!(K, "slot.key")();
163     }
164 
165     /// Returns: a GC allocated dynamic array, the elements of which are the values in the map.
166     @property V[] values() const nothrow
167     {
168         return funcKeysValuesBody!(V, "slot.value")();
169     }
170 
171     /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the keys of the map.
172     Range!(IterMode.Key) byKey() nothrow
173     {
174         return typeof(return)(_control, _slots.ptr);
175     }
176 
177     /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the values of the map.
178     Range!(IterMode.Value) byValue() nothrow
179     {
180         return typeof(return)(_control, _slots.ptr);
181     }
182 
183     /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the key-value pairs of the map.
184     Range!(IterMode.Both) byKeyValue() nothrow
185     {
186         return typeof(return)(_control, _slots.ptr);
187     }
188 
189     /// $(B foreach) support
190     int opApply(scope int delegate(const ref K,  ref V) dg)
191     {
192         int result;
193         auto ctrl = _control;
194         auto slot = _slots.ptr;
195 
196         skipEmptyOrTombstoneSlots(ctrl, slot);
197         while (*ctrl != Control.Sentinel) {
198             result = dg(slot.key, slot.value);
199             if (result)
200                 break;
201 
202             ++ctrl;
203             ++slot;
204             skipEmptyOrTombstoneSlots(ctrl, slot);
205         }
206 
207         return result;
208     }
209 
210   private @trusted:
211     enum IterMode { Key, Value, Both }
212 
213     /// ForwardRange for byXXX functions
214     static struct Range(IterMode Mode)
215     {
216       private:
217         Control* _control = emptyGroup;
218         SlotType* _slots;
219 
220       public nothrow @trusted:
221         @disable this();
222 
223         this(Control* ctrl, SlotType* slots)
224         {
225             _control = ctrl;
226             _slots = slots;
227             skipEmptyOrTombstoneSlots(_control, _slots);
228         }
229 
230         @property bool empty() const
231         {
232             return *_control == Control.Sentinel;
233         }
234 
235         static if (Mode == IterMode.Both) {
236             @property ref SlotType front()
237             {
238                 return *_slots;
239             }
240         } else static if (Mode == IterMode.Key) {
241             @property ref K front()
242             {
243                 return _slots.key;
244             }
245         } else {
246             @property ref V front()
247             {
248                 return _slots.value;
249             }
250         }
251 
252         void popFront()
253         {
254             ++_control;
255             ++_slots;
256             skipEmptyOrTombstoneSlots(_control, _slots);
257         }
258 
259         typeof(this) save()
260         {
261             return typeof(this)(_control, _slots);
262         }
263     }
264 }
265 
266 unittest
267 {
268     Map!(string, int) map;
269 
270     map["k0"] = 9;
271     map["k1"]++;
272 
273     assert(map.length == 2);
274     assert("k1" in map);
275     assert(map["k0"] == 9);
276     assert(map["k1"] == 1);
277 }
278 
279 unittest
280 {
281     // initSlotInIndex is false
282     import core.exception : RangeError;
283 
284     Map!(int, int, shared const Mallocator, false) map;
285 
286     map[0] = 9;
287     try {
288         map[1]++;
289         assert(false, "don't reach here");
290     } catch (RangeError) {
291         assert(map.length == 1);
292         assert(0 in map);
293     }
294 }
295 
296 unittest
297 {
298     // remove and rehash
299     import std.algorithm : sort;
300     import std.conv : text;
301 
302     enum N = 100;
303     string[] keys = new string[](N);
304     Map!(string, string) map;
305 
306     foreach (i; 0..N) {
307         string k = text("k", i);
308         keys[i] = k;
309         map[k] = text("v", i);;
310     }
311     assert(map.length == 100);
312 
313     foreach (i; 0..N / 2)
314         map.remove(keys[i]);
315     assert(map.length == N / 2);
316     assert(map.capacity == normalizeCapacity(N));
317 
318     map.rehash;
319 
320     assert(map.capacity == normalizeCapacity(N / 2));
321     foreach (i; N / 2..N)
322         assert(keys[i] in map);
323 
324     auto ks = map.keys.sort;
325     auto vs = map.values.sort;
326     assert(ks[0] == "k50");
327     assert(vs[0] == "v50");
328     assert(ks[$ - 1] == "k99");
329     assert(vs[$ - 1] == "v99");
330 }
331 
332 unittest
333 {
334     // dup
335     import std.algorithm : sort, equal;
336     import std.conv : text;
337 
338     Map!(int, string) map;
339 
340     enum N = 100;
341     foreach (i; 0..N)
342         map[i] = text("v", i);
343 
344     auto nmap = map.dup;
345     assert(nmap.length == map.length);
346     assert(nmap._capacity == map.capacity);
347     assert(equal(nmap.keys.sort, map.keys.sort));
348     assert(equal(nmap.values.sort, map.values.sort));
349 
350     nmap[10] = "hey";
351     assert(nmap[10] == "hey");
352     assert(map[10]  == "v10");
353 }
354 
355 unittest
356 {
357     // clear
358     import std.algorithm : sort, equal;
359     import std.range : empty;
360     import std.conv : text;
361 
362     Map!(int, string) map;
363 
364     enum N = 100;
365     foreach (i; 0..N)
366         map[i] = text("v", i);
367 
368     auto oldCap = map.capacity;
369     map.clear;
370 
371     assert(map.length == 0);
372     assert(map.capacity == oldCap);
373     assert(map.keys.empty);
374     assert(map.values.empty);
375 
376     foreach (i; 0..10)
377         map[i] = text("v", i);
378     map.clear(false);
379 
380     assert(map.length == 0);
381     assert(map.capacity == 0);
382     assert(map.keys.empty);
383     assert(map.values.empty);
384 }
385 
386 unittest
387 {
388     // struct key-value test
389     import std.conv : text;
390 
391     static struct Test
392     {
393         int a;
394         double b;
395         string c;
396 
397         this(ref return scope Test rhs) nothrow @safe
398         {
399             a = rhs.a;
400             b = rhs.b;
401             c = rhs.c;
402         }
403     }
404 
405     enum N = 100;
406     Test[] tests;
407     Map!(Test, Test) map;
408 
409     foreach (i; 0..N) {
410         auto t = Test(i, cast(double)i + 0.1, text("v", i));
411         tests ~= t;
412         map[t] = t;
413         assert(map[t] == t);
414     }
415 
416     foreach (i; 0..N) {
417         if (i % 2 == 0) {
418             map.remove(tests[i]);
419             assert(!(tests[i] in map));
420         }
421     }
422     assert(map.length == N / 2);
423 
424     foreach (i; 0..N) {
425         if (i % 2 == 1) {
426             auto t = tests[i];
427             auto p = t in map;
428             assert(p);
429             p.a = 100000;
430             p.c = "test";
431             assert(map[t].a == 100000);
432             assert(map[t].c == "test");
433         }
434     }
435 
436     map.clear;
437 
438     // Test temporal objects
439     foreach (i; 0..N) {
440         map[Test(i, 0, text(i))] = Test(i, 0, text(i));
441         assert(Test(i, 0, text(i)) in map);
442         assert(map.remove(Test(i, 0, text(i))));
443     }
444     assert(map.length == 0);
445 }
446 
447 unittest
448 {
449     // byXXX
450     import std.algorithm : canFind;
451     import std.conv : text;
452     import std.range : walkLength;
453 
454     enum N = 100;
455     int[] keys = new int[](N);
456     string[] vals = new string[](N);
457     Map!(int, string) map;
458 
459     foreach (ref k; map.byKey()) assert(false);
460     foreach (ref v; map.byValue()) assert(false);
461     foreach (ref e; map.byKeyValue()) assert(false);
462 
463     foreach (i; 0..N) {
464         string v = text("v", i);
465         keys[i] = i;
466         vals[i] = v;
467         map[i] = v;
468     }
469 
470     auto kr = map.byKey();
471     foreach (ref k; kr.save) {
472         assert(keys.canFind(k));
473         assert(k in map);
474     }
475     assert(walkLength(kr) == N);
476 
477     auto vr = map.byValue();
478     foreach (ref v; vr.save)
479         assert(vals.canFind(v));
480     assert(walkLength(vr) == N);
481 
482     auto kvr = map.byKeyValue();
483     foreach (ref e; kvr.save) {
484         assert(keys.canFind(e.key));
485         assert(vals.canFind(e.value));
486         assert(e.key in map);
487         assert(map[e.key] == e.value);
488     }
489     assert(walkLength(kvr) == N);
490 
491     // opApply
492     int count = 0;
493     foreach (ref k, ref v; map) {
494         count++;
495         assert(keys.canFind(k));
496         assert(vals.canFind(v));
497     }
498     assert(count == N);
499 }