1 module stdx.collections.hashtable;
2 
3 import stdx.collections.common;
4 import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
5        theAllocator, processAllocator, make, dispose;
6 import std.experimental.allocator.building_blocks.affix_allocator;
7 import std.experimental.allocator.gc_allocator;
8 //import stdx.collections.pair : Pair;
9 import stdx.collections.slist : SList;
10 import stdx.collections.array : Array;
11 
12 debug(CollectionHashtable) import std.stdio;
13 
14 version(unittest)
15 {
16     import std.experimental.allocator.mallocator;
17     import std.experimental.allocator.building_blocks.stats_collector;
18 
19     private alias Alloc = StatsCollector!(
20                         AffixAllocator!(Mallocator, uint),
21                         Options.bytesUsed
22     );
23     Alloc _allocator;
24 }
25 
26 struct Hashtable(K, V)
27 {
28     import std.traits : isImplicitlyConvertible, Unqual, isArray;
29     import std.range.primitives : isInputRange, isForwardRange, isInfinite,
30            ElementType, hasLength;
31     import std.variant : Algebraic;
32     import std.conv : emplace;
33     import std.typecons : Tuple, Nullable;
34     import core.atomic : atomicOp;
35 
36 private:
37     alias KVPair = Tuple!(K, "key", V, "value");
38     Array!(SList!(KVPair)) _buckets;
39     Array!size_t _numElems; // This needs to be ref counted
40     static enum double loadFactor = 0.75;
41 
42     alias LocalAllocT = AffixAllocator!(RCIAllocator, size_t);
43     alias SharedAllocT = AffixAllocator!(RCISharedAllocator, size_t);
44     alias MutableAlloc = Algebraic!(LocalAllocT, SharedAllocT);
45     Mutable!MutableAlloc _ouroborosAllocator;
46 
47     /// Returns the actual allocator from ouroboros
48     @trusted ref auto allocator(this Q)()
49     {
50         static if (is(Q == immutable) || is(Q == const))
51         {
52             assert(_ouroborosAllocator.get.peek!(SharedAllocT) !is null);
53             return _ouroborosAllocator.get.get!(SharedAllocT);
54         }
55         else
56         {
57             assert(_ouroborosAllocator.get.peek!(LocalAllocT) !is null);
58             return _ouroborosAllocator.get.get!(LocalAllocT);
59         }
60     }
61 
62 public:
63     /// Constructs the ouroboros allocator from allocator if the ouroboros
64     //allocator wasn't previously set
65     @trusted bool setAllocator(RCIAllocator allocator)
66     {
67         if (_ouroborosAllocator.get.peek!(LocalAllocT) is null)
68         {
69             _ouroborosAllocator = Mutable!(MutableAlloc)(allocator,
70                     MutableAlloc(LocalAllocT(allocator)));
71             _buckets = Array!(SList!(KVPair))(allocator);
72             _numElems = Array!size_t(allocator);
73             return true;
74         }
75         return false;
76     }
77 
78     mixin template setSharedAllocator(RCISharedAllocator allocator)
79     {
80         _ouroborosAllocator = Mutable!(MutableAlloc)(allocator,
81                 MutableAlloc(SharedAllocT(allocator)));
82     }
83 
84     public @trusted auto ref getAllocator(this Q)()
85     {
86         static if (is(Q == immutable) || is(Q == const))
87         {
88             return _ouroborosAllocator.get.peek!(SharedAllocT) is null ?
89                         RCISharedAllocator(null) : allocator().parent;
90         }
91         else
92         {
93             return _ouroborosAllocator.get.peek!(LocalAllocT) is null ?
94                         RCIAllocator(null) : allocator().parent;
95         }
96     }
97 
98     this(A, this Q)(A allocator)
99     if (((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator))
100         || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator)))
101     {
102         debug(CollectionHashtable)
103         {
104             writefln("Array.ctor: begin");
105             scope(exit) writefln("Array.ctor: end");
106         }
107         static if (is(Q == immutable) || is(Q == const))
108         {
109             mixin setSharedAllocator!(allocator);
110         }
111         else
112         {
113             setAllocator(allocator);
114         }
115     }
116 
117     this(U, this Q)(U assocArr)
118     if (is(U == Value[Key], Value : V, Key : K))
119     {
120         static if (is(Q == immutable) || is(Q == const))
121         {
122             this(processAllocator, assocArr);
123         }
124         else
125         {
126             this(theAllocator, assocArr);
127         }
128     }
129 
130     this(A, U, this Q)(A allocator, U assocArr)
131     if ((((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator))
132          || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator)))
133          && is(U == Value[Key], Value : V, Key : K))
134     {
135         debug(CollectionHashtable)
136         {
137             writefln("Hashtable.ctor: begin");
138             scope(exit) writefln("Hashtable.ctor: end");
139         }
140         static if (is(Q == immutable) || is(Q == const))
141         {
142             // Build a mutable hashtable on the stack and pass ownership to this
143 
144             // TODO: This, as is, will fail big time
145             auto tmp = Hashtable!(K, V)(allocator, assocArr);
146             _buckets = cast(typeof(_buckets))(tmp._buckets);
147             _numElems = cast(typeof(_numElems))(tmp._numElems);
148 
149             mixin setSharedAllocator!(allocator);
150             //auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));
151             //_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();
152         }
153         else
154         {
155             setAllocator(allocator);
156             insert(assocArr);
157         }
158     }
159 
160     size_t insert(U)(U assocArr)
161     if (is(U == Value[Key], Value : V, Key : K))
162     {
163         debug(CollectionHashtable)
164         {
165             writefln("Hashtable.insert: begin");
166             scope(exit) writefln("Hashtable.insert: end");
167         }
168 
169         setAllocator(theAllocator);
170         if (_buckets.empty)
171         {
172             auto reqCap = requiredCapacity(assocArr.length);
173             _buckets.reserve(reqCap);
174             _buckets.forceLength(reqCap);
175             _numElems.reserve(1);
176             _numElems.forceLength(1);
177         }
178         foreach(k, v; assocArr)
179         {
180             size_t pos = k.hashOf & (_buckets.length - 1);
181             if (_buckets[pos].empty)
182             {
183                 _buckets[pos] = SList!(KVPair)(_buckets.getAllocator(), KVPair(k, v));
184             }
185             else
186             {
187                 _buckets[pos].insert(0, KVPair(k, v));
188             }
189         }
190         _numElems[0] += assocArr.length;
191         return assocArr.length;
192     }
193 
194     private size_t requiredCapacity(size_t numElems)
195     {
196         static enum size_t maxPow2 = cast(size_t)(1) << (size_t.sizeof * 8 - 1);
197         while (numElems & (numElems - 1))
198         {
199             numElems &= (numElems - 1);
200         }
201         return numElems < maxPow2 ? 2 * numElems : maxPow2;
202     }
203 
204     /// Returns number of key-value pairs
205     size_t length() const
206     {
207         return _numElems.empty ? 0 : _numElems[0];
208     }
209 
210     /// Returns number of buckets
211     size_t size() const
212     {
213         return _buckets.length;
214     }
215 
216     bool empty(this _)()
217     {
218         return _buckets.empty || _numElems.empty || _numElems[0] == 0;
219     }
220 
221     ref auto front(this _)()
222     {
223         debug(CollectionHashtable)
224         {
225             writefln("Hashtable.front: begin");
226             scope(exit) writefln("Hashtable.front: end");
227         }
228         auto tmpBuckets = _buckets;
229         while ((!tmpBuckets.empty) && tmpBuckets.front.empty)
230         {
231             tmpBuckets.popFront;
232         }
233         assert(!tmpBuckets.empty, "Hashtable.front: Hashtable is empty");
234         return tmpBuckets.front.front;
235     }
236 
237     void popFront()
238     {
239         debug(CollectionHashtable)
240         {
241             writefln("Hashtable.popFront: begin");
242             scope(exit) writefln("Hashtable.popFront: end");
243         }
244         if (!_buckets.isUnique)
245         {
246             _buckets = _buckets.dup();
247             _numElems = _numElems.dup();
248         }
249         while ((!_buckets.empty) && _buckets.front.empty)
250         {
251             _buckets.popFront;
252         }
253         assert(!_buckets.empty, "Hashtable.front: Hashtable is empty");
254         _buckets.front.popFront;
255         --_numElems[0];
256         // Find the next non-empty bucketList
257         if (_buckets.front.empty)
258         {
259             while ((!_buckets.empty) && _buckets.front.empty)
260             {
261                 _buckets.popFront;
262             }
263         }
264     }
265 
266     private const
267     void getKeyValues(string kv, BuckQual, ListQual, ArrQual)(BuckQual b, ListQual l, ref ArrQual values)
268     {
269         if (b.empty)
270         {
271             return;
272         }
273         else if (l.empty)
274         {
275             auto bTail = b.tail;
276             if (!bTail.empty)
277             {
278                 getKeyValues!kv(bTail, bTail.front, values);
279             }
280         }
281         else
282         {
283             static if (kv.length)
284             {
285                 mixin("values ~= l.front." ~ kv ~ ";");
286             }
287             else
288             {
289                 mixin("values ~= l.front;");
290             }
291             getKeyValues!kv(b, l.tail, values);
292         }
293     }
294 
295     Array!K keys(this _)()
296     {
297         debug(CollectionHashtable)
298         {
299             writefln("Hashtable.keys: begin");
300             scope(exit) writefln("Hashtable.keys: end");
301         }
302 
303         Array!K keys;
304         auto tmp = _buckets;
305         if (!_buckets.empty)
306         //if (!tmp.empty)
307         {
308             //getKeyValues!("key")(tmp, tmp.front, keys);
309             getKeyValues!("key")(_buckets, _buckets.front, keys);
310         }
311         return keys;
312     }
313 
314     Array!V values(this _)()
315     {
316         debug(CollectionHashtable)
317         {
318             writefln("Hashtable.values: begin");
319             scope(exit) writefln("Hashtable.values: end");
320         }
321 
322         Array!V values;
323         if (!_buckets.empty)
324         {
325             getKeyValues!("value")(_buckets, _buckets.front, values);
326         }
327 
328         return values;
329     }
330 
331 
332     Array!(Tuple!(K, "key", V, "value")) keyValuePairs(this _)()
333     {
334         debug(CollectionHashtable)
335         {
336             writefln("Hashtable.keyValuePairs: begin");
337             scope(exit) writefln("Hashtable.keyValuePairs: end");
338         }
339 
340         Array!KVPair pairs;
341         if (!_buckets.empty)
342         {
343             getKeyValues!("")(_buckets, _buckets.front, pairs);
344         }
345 
346         return pairs;
347     }
348 
349     private const
350     Nullable!V getValue(ListQual)(ListQual list, ref K key)
351     {
352         if (list.empty)
353         {
354             return Nullable!V.init;
355         }
356         else if (list.front.key == key)
357         {
358             return Nullable!V(list.front.value);
359         }
360         else
361         {
362             return getValue(list.tail, key);
363         }
364     }
365 
366     V get(this _)(K key, V nullValue)
367     {
368         debug(CollectionHashtable)
369         {
370             writefln("Hashtable.get: begin");
371             scope(exit) writefln("Hashtable.get: end");
372         }
373 
374         size_t pos = key.hashOf & (_buckets.length - 1);
375         auto result = getValue(_buckets[pos], key);
376         if (!result.isNull)
377         {
378             return result.get;
379         }
380         return nullValue;
381     }
382 
383     Nullable!V opIndex(this _)(K key)
384     {
385         debug(CollectionHashtable)
386         {
387             writefln("Hashtable.opIndex: begin");
388             scope(exit) writefln("Hashtable.opIndex: end");
389         }
390 
391         size_t pos = key.hashOf & (_buckets.length - 1);
392         return getValue(_buckets[pos], key);
393     }
394 
395     Nullable!V opIndexUnary(string op)(K key)
396     {
397         debug(CollectionHashtable)
398         {
399             writefln("Hashtable.opIndexUnary!" ~ op ~ ": begin");
400             scope(exit) writefln("Hashtable.opIndexUnary!" ~ op ~ ": end");
401         }
402 
403         size_t pos = key.hashOf & (_buckets.length - 1);
404         foreach(ref pair; _buckets[pos])
405         {
406             if (pair.key == key)
407             {
408                 return Nullable!V(mixin(op ~ "pair.value"));
409             }
410         }
411         return Nullable!V.init;
412     }
413 
414     Nullable!V opIndexAssign(U)(U val, K key)
415     if (isImplicitlyConvertible!(U, V))
416     {
417         debug(CollectionHashtable)
418         {
419             writefln("Hashtable.opIndexAssign: begin");
420             scope(exit) writefln("Hashtable.opIndexAssign: end");
421         }
422 
423         size_t pos = key.hashOf & (_buckets.length - 1);
424         foreach(ref pair; _buckets[pos])
425         {
426             if (pair.key == key)
427             {
428                 return Nullable!V(pair.value);
429             }
430         }
431         return Nullable!V.init;
432     }
433 
434     Nullable!V opIndexOpAssign(string op, U)(U val, K key)
435     if (isImplicitlyConvertible!(U, V))
436     {
437         debug(CollectionHashtable)
438         {
439             writefln("Hashtable.opIndexOpAssign: begin");
440             scope(exit) writefln("Hashtable.opIndexOpAssign: end");
441         }
442 
443         size_t pos = key.hashOf & (_buckets.length - 1);
444         foreach(ref pair; _buckets[pos])
445         {
446             if (pair.key == key)
447             {
448                 return Nullable!V(mixin("pair.value" ~ op ~ "= val"));
449             }
450         }
451         return Nullable!V.init;
452     }
453 
454 
455     auto ref opBinary(string op, U)(auto ref U rhs)
456         if (op == "~" &&
457             (is (U == typeof(this))
458              || is (U : T)
459              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
460             ));
461 
462     auto ref opAssign()(auto ref typeof(this) rhs)
463     {
464         debug(CollectionHashtable)
465         {
466             writefln("Hashtable.opAssign: begin");
467             scope(exit) writefln("Hashtable.opAssign: end");
468         }
469 
470         _buckets = rhs._buckets;
471         _numElems = rhs._numElems;
472         _ouroborosAllocator = rhs._ouroborosAllocator;
473         return this;
474     }
475 
476     void remove(K key);
477 
478     void clear()
479     {
480         debug(CollectionHashtable)
481         {
482             writefln("Hashtable.clear: begin");
483             scope(exit) writefln("Hashtable.clear: end");
484         }
485         _buckets = Array!(SList!(KVPair))(getAllocator());
486         _numElems = Array!size_t(getAllocator());
487     }
488 
489     void rehash();
490 }
491 
492 @trusted unittest
493 {
494     import std.algorithm.comparison : equal;
495 
496     auto h = Hashtable!(int, int)([1 : 10]);
497 
498     assert(*h._buckets.prefCount(h._buckets._support) == 0);
499     assert(!h._buckets[0].empty && (*h._buckets[0].prefCount(h._buckets[0]._head) == 0));
500 
501     assert(equal(h.keys(), [1]));
502     assert(equal(h.values(), [10]));
503     assert(h.get(1, -1) == 10);
504     assert(h.get(200, -1) == -1);
505     assert(--h[1] == 9);
506     assert(h.get(1, -1) == 9);
507 
508     assert(*h._buckets.prefCount(h._buckets._support) == 0);
509     assert(!h._buckets[0].empty && (*h._buckets[0].prefCount(h._buckets[0]._head) == 0));
510 }
511 
512 @trusted unittest
513 {
514     import std.typecons : Tuple;
515 
516     auto h2 = Hashtable!(int, int)();
517     assert(h2.length == 0);
518     h2.insert([1 : 10]);
519     assert(h2.length == 1);
520     assert(h2._buckets[0].front == Tuple!(int, int)(1, 10));
521     h2.clear();
522     assert(h2.length == 0);
523     assert(h2.empty);
524 }
525 
526 @trusted unittest
527 {
528     import std.algorithm.comparison : equal;
529     import std.typecons : Tuple;
530 
531     auto h3 = immutable Hashtable!(int, int)([1 : 10]);
532     assert(h3._buckets[0].front == Tuple!(int, int)(1, 10));
533     assert(h3.get(1, -1) == 10);
534     assert(equal(h3.values(), Array!int([10])));
535     assert(equal(h3.keyValuePairs(), Array!(Tuple!(int, int))(Tuple!(int, int)(1, 10))));
536 }