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