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