1 ///
2 module cachetools.containers.orderedhashmap;
3 
4 private import std.experimental.allocator.mallocator : Mallocator;
5 private import std.experimental.allocator.gc_allocator;
6 private import std.typecons;
7 private import std.traits;
8 
9 import cachetools.internal;
10 import cachetools.containers.hashmap;
11 import cachetools.containers.lists;
12 
13 ///
14 struct OrderedHashMap(K, V, Allocator = Mallocator, bool GCRangesAllowed = true)
15 {
16 
17     private
18     {
19         alias StoredKeyType = StoredType!K;
20         alias StoredValueType = StoredType!V;
21         alias KeysType = CompressedList!(K, Allocator, GCRangesAllowed);
22         alias HashesType = HashMap!(K, HashMapElement, Allocator, GCRangesAllowed);
23 
24         struct HashMapElement
25         {
26             StoredValueType value;
27             KeysType.NodePointer kptr;
28         }
29 
30         KeysType __keys;
31         HashesType __hashes;
32     }
33     this(this) {
34         import std.algorithm.mutation;
35         KeysType keys;
36         HashesType hashes;
37         foreach(k; __keys) {
38             auto p = keys.insertBack(k);
39             if (auto vp = k in __hashes) {
40                 hashes.put(k, HashMapElement(vp.value, p));
41             }
42         }
43         __keys.clear;
44         __hashes.clear;
45         swap(__keys,keys);
46         swap(__hashes, hashes);
47     }
48     void opAssign(ref typeof(this) rhs) {
49         if ( rhs is this ) return;
50         foreach (k; rhs.__keys) {
51             auto p = __keys.insertBack(k);
52             if (auto vp = k in rhs.__hashes) {
53                 __hashes.put(k, HashMapElement(vp.value, p));
54             }
55         }
56     }
57     string toString() {
58         import std.algorithm, std.array, std.format;
59 
60         auto pairs = byPair;
61         return "[%s]".format(pairs.map!(p => "%s:%s".format(p.key, p.value)).array.join(", "));
62     }
63 
64     ///
65     auto put(K k, V v)
66     {
67         auto hashesptr = k in __hashes;
68         if (hashesptr is null)
69         {
70             // append to list and store in hashes
71             auto keysptr = __keys.insertBack(k);
72             __hashes.put(k, HashMapElement(v, keysptr));
73             return Nullable!V();
74         }
75         else
76         {
77             auto o = hashesptr.value;
78             hashesptr.value = v;
79             return nullable(o);
80         }
81     }
82     ///
83     /// map[key]
84     /// Attention: you can't use this method in @nogc code.
85     /// Usual aa[key] method.
86     /// Throws exception if key not found
87     /// Returns: value for given key
88     ///
89     ref V opIndex(in K k) @safe
90     {
91         V* v = k in this;
92         if (v !is null)
93         {
94             return *v;
95         }
96         throw new KeyNotFound();
97     }
98 
99     ///
100     /// map[k] = v;
101     ///
102     void opIndexAssign(V v, K k) @safe
103     {
104         put(k, v);
105     }
106     ///
107     bool remove(K k) @safe
108     {
109         auto hashesptr = k in __hashes;
110         if (hashesptr is null)
111         {
112             return false;
113         }
114         auto keysptr = hashesptr.kptr;
115         () @trusted { __keys.remove(keysptr); }();
116         __hashes.remove(k);
117         return true;
118     }
119     ///
120     void clear() @safe
121     {
122         __hashes.clear();
123         __keys.clear();
124     }
125 
126     /// get numter of keys in table
127     auto length() const pure nothrow @nogc @safe
128     {
129         return __keys.length;
130     }
131 
132     /// key in table
133     /// Returns: pointer to stored value (if key in table) or null 
134     ///
135     V* opBinaryRight(string op)(in K k) @safe if (op == "in")
136     {
137         auto hashesptr = k in __hashes;
138         if (hashesptr)
139         {
140             return &hashesptr.value;
141         }
142         return null;
143     }
144 
145     ///
146     /// get
147     /// Returns: value from hash, or defaultValue if key not found (see also getOrAdd).
148     /// defaultValue can be callable.
149     ///
150     V get(T)(K k, T defaultValue) @safe
151     {
152         auto v = k in __hashes;
153         if (v)
154         {
155             return v.value;
156         }
157         static if (isAssignable!(V, T))
158         {
159             return defaultValue;
160         }
161         else static if (isCallable!T && isAssignable!(V, ReturnType!T))
162         {
163             return defaultValue();
164         }
165         else
166         {
167             static assert(0, "You must call 'get' with default value of HashMap 'value' type, or with callable, returning HashMap 'value'");
168         }
169     }
170     ///
171     V getOrAdd(T)(K k, T defaultValue) @safe
172     {
173         auto v = k in __hashes;
174         if (v)
175         {
176             return v.value;
177         }
178         static if (isAssignable!(V, T))
179         {
180             put(k, defaultValue);
181             return defaultValue;
182         }
183         else static if (isCallable!T && isAssignable!(V, ReturnType!T))
184         {
185             auto value = defaultValue();
186             return put(k, value);
187             return value;
188         }
189         else
190         {
191             static assert(0, "what?");
192         }
193     }
194 
195     ///
196     bool addIfMissed(T)(K k, T value) @safe {
197         auto v = k in __hashes;
198         if (v) {
199             return false;
200         }
201         static if (isAssignable!(V, T)) {
202             put(k, value);
203             return true;
204         }
205         else static if (isCallable!T && isAssignable!(V, ReturnType!T)) {
206             put(k, value());
207             return true;
208         }
209         else {
210             static assert(0, "what?");
211         }
212     }
213 
214     /// iterator by keys
215     auto byKey() pure @safe @nogc
216     {
217         return __keys.range();
218     }
219     /// iterator by value
220     auto byValue() pure @safe @nogc
221     {
222         struct _range
223         {
224             private KeysType.Range _keysIter;
225             private HashesType* _hashes;
226             V front() @safe
227             {
228                 auto p = _keysIter.front in *_hashes;
229                 return p.value;
230             }
231 
232             bool empty() @safe
233             {
234                 return _keysIter.empty();
235             }
236 
237             void popFront() @safe
238             {
239                 _keysIter.popFront;
240             }
241         }
242 
243         return _range(__keys.range, &__hashes);
244     }
245 
246     ///
247     auto byPair() pure @safe @nogc
248     {
249         struct _range
250         {
251             private KeysType.Range _keysIter;
252             private HashesType* _hashes;
253             auto front() @safe
254             {
255                 auto p = _keysIter.front in *_hashes;
256                 return Tuple!(K, "key", V, "value")(_keysIter.front, p.value);
257             }
258 
259             bool empty() @safe
260             {
261                 return _keysIter.empty();
262             }
263 
264             void popFront() @safe
265             {
266                 _keysIter.popFront;
267             }
268         }
269 
270         return _range(__keys.range, &__hashes);
271     }
272 }
273 
274 unittest {
275     import std.experimental.logger;
276     globalLogLevel = LogLevel.info;
277     info("Testing OrderedHashMap");
278 }
279 
280 @safe @nogc unittest
281 {
282     import std.algorithm;
283     import std.range;
284     import std.stdio;
285     OrderedHashMap!(int, int) dict;
286     dict.put(1, 1);
287     int* v = 1 in dict;
288     assert(v !is null && *v == 1);
289     assert(dict.remove(1));
290     assert(1 !in dict);
291     assert(!dict.remove(2));
292     assert(dict.length == 0);
293     iota(100).each!(a => dict.put(a, a));
294     assert(dict.length == 100);
295     assert(equal(dict.byKey, iota(100)));
296     assert(equal(dict.byValue, iota(100)));
297     assert(dict.getOrAdd(100, 100) == 100);
298 }
299 
300 @safe @nogc unittest {
301     import std.algorithm;
302     import std.range;
303 
304     OrderedHashMap!(int, int) dict0, dict1;
305     iota(100).each!(a => dict0.put(a, a));
306     dict1 = dict0;
307     assert(0 in dict1);
308     dict1.remove(0);
309     assert(equal(dict1.byKey, iota(1,100)));
310 }
311 
312 @safe unittest {
313     class C {
314         hash_t c;
315         override hash_t toHash() const @safe {
316             return c;
317         }
318 
319         bool opEquals(const C other) const @safe {
320             return c == other.c;
321         }
322 
323         this(hash_t i) {
324             c = i;
325         }
326         // void opCast(const C from) {
327         //     c = from.c;
328         // }
329     }
330 
331     OrderedHashMap!(C, int) map;
332     int* f(const C c) {
333         auto v = map[c];
334         // can't do this with classes because put require key assignment which can't convert const object to mutable
335         //map.put(c, 2);
336         return c in map;
337     }
338 
339     C c = new C(1);
340     map[c] = 1;
341     f(c);
342 }