1 /*******************************************************************************
2 
3     Template for a class implementing a mapping from hashes to a user-specified
4     type.
5 
6     The interface of the class has been kept deliberately simple, purely
7     handling the management of the mapping. The handling of the mapping values
8     is left entirely up to the user -- all methods simply return a pointer to
9     the mapping value which the user can do what they like with. (This is an
10     intentional design decision, in order to reduce the complexity of the
11     template.)
12 
13     The HashMap is designed as a replacement for ocean.core.ArrayMap. It has
14     several advantages:
15         1. Memory safety. As the ArrayMap's buckets are implemented as dynamic
16            arrays, each bucket will theoretically grow continually in size over
17            extended periods of use. Even when clear()ed, the buffers allocated
18            for the buckets will not reduce in size. The HashMap, on the other
19            hand, uses a pool of elements, meaning that the memory allocated for
20            each bucket is truly variable.
21         2. Code simplicity via removing optional advanced features such as
22            thread safety and value array copying.
23         3. Extensibility. Functionality is split into several modules, including
24            a base class for easier reuse of components.
25 
26     A usage example with various types stored in mappings is below in the
27     unit tests.
28 
29     Copyright:
30         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
31         All rights reserved.
32 
33     License:
34         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
35         Alternatively, this file may be distributed under the terms of the Tango
36         3-Clause BSD License (see LICENSE_BSD.txt for details).
37 
38 *******************************************************************************/
39 
40 module ocean.util.container.map.HashMap;
41 
42 import ocean.util.container.map.Map;
43 
44 version (UnitTestVerbose) import ocean.io.Stdout;
45 
46 
47 
48 /*******************************************************************************
49 
50     HashMap class template. Manages a mapping from hash_t to the specified type.
51 
52     Params:
53         V = type to store in values of map
54 
55 *******************************************************************************/
56 
57 public class HashMap ( V ) : Map!(V, hash_t)
58 {
59     /***************************************************************************
60 
61         Constructor.
62 
63         Params:
64             n = expected number of elements in mapping
65             load_factor = ratio of n to the number of internal buckets. The
66                 desired (approximate) number of elements per bucket. For
67                 example, 0.5 sets the number of buckets to double n; for 2 the
68                 number of buckets is the half of n. load_factor must be greater
69                 than 0. The load factor is basically a trade-off between memory
70                 usage (number of buckets) and search time (number of elements
71                 per bucket).
72 
73     ***************************************************************************/
74 
75     public this ( size_t n, float load_factor = 0.75 )
76     {
77         super(n, load_factor);
78     }
79 
80     /***************************************************************************
81 
82         Constructor.
83 
84         Params:
85             allocator = custom bucket elements allocator
86             n = expected number of elements in mapping
87             load_factor = ratio of n to the number of internal buckets. The
88                 desired (approximate) number of elements per bucket. For
89                 example, 0.5 sets the number of buckets to double n; for 2 the
90                 number of buckets is the half of n. load_factor must be greater
91                 than 0. The load factor is basically a trade-off between memory
92                 usage (number of buckets) and search time (number of elements
93                 per bucket).
94 
95     ***************************************************************************/
96 
97     public this ( IAllocator allocator, size_t n, float load_factor = 0.75 )
98     {
99         super(allocator, n, load_factor);
100     }
101 
102     /***************************************************************************
103 
104         Calculates the hash value from key. Uses the identity since key is
105         expected to be a suitable hash value.
106 
107         This method has to accept `const(hash_t)` because it overrides templated
108         base `toHash (K) (in K key)` method and DMD demands exact qualifier
109         match for arguments.
110 
111         Params:
112             key = key to hash
113 
114         Returns:
115             the hash value that corresponds to key, which is key itself.
116 
117     ***************************************************************************/
118 
119     public override hash_t toHash ( in hash_t key )
120     {
121         return key;
122     }
123 
124     /***************************************************************************
125 
126         HashMap unittest.
127 
128     ***************************************************************************/
129 
130     version (unittest)
131     {
132         import ocean.core.Test;
133         import ocean.meta.traits.Basic : isFloatingPointType;
134         import ocean.math.IEEE : isNaN;
135     }
136 
137     unittest
138     {
139         version ( UnitTestVerbose )
140         {
141             Stdout.formatln("{} unittest ---------------",
142                 typeof(this).stringof);
143             scope ( success ) Stdout.formatln("{} unittest ---------------",
144                typeof(this).stringof);
145         }
146 
147         scope map = new typeof(this)(10);
148 
149         version ( UnitTestVerbose ) void printState ( )
150         {
151             Stdout.formatln("  ::  len={}, load={}, max_load={}",
152                 map.length, map.bucket_info.load, map.bucket_info.max_load);
153         }
154 
155         bool lengthIs ( size_t expected )
156         {
157             test!("==")(map.length, expected);
158 
159             int c;
160             foreach ( k, v; map )
161             {
162                 c++;
163             }
164             return c == expected;
165         }
166 
167         void put ( hash_t key, bool should_exist )
168         {
169             auto len = map.length;
170 
171             test!("==")(((key in map) !is null), should_exist);
172 
173             auto e = map.put(key);
174 
175             *e = V.init;
176 
177             version ( UnitTestVerbose )
178             {
179                 Stdout.format("put {}: {}", key, e);
180                 printState();
181             }
182 
183             test!("!is")((key in map), null);
184 
185             static if (is (V U : U[]) && !is (V == V[]))
186             {
187                 // work around DMD bug 7752
188 
189                 V v_init;
190 
191                 test!("==")(*map.get(key), v_init);
192             }
193             else static if ( is ( V == class ) )
194             {
195                 test!("is")(*map.get(key), V.init);
196             }
197             else static if ( isFloatingPointType!(V) )
198             {
199                 // Specialised test for floating point types, where
200                 // V.init != V.init
201                 test(isNaN(*map.get(key))); // Value does not equal previously set value
202             }
203             else
204             {
205                 test!("is")(*map.get(key), V.init); // Value does not equal previously set value
206             }
207 
208             test(lengthIs(len + (should_exist ? 0 : 1)),
209                    "Length different from foreach-counted elements!");
210         }
211 
212         void remove ( hash_t key, bool should_exist )
213         {
214             auto len = map.length;
215             auto pool_len = map.bucket_info.num_buckets;
216 
217             test!("==")(((key in map) !is null), should_exist);
218 
219             auto e = map.remove(key);
220             version ( UnitTestVerbose )
221             {
222                 Stdout.format("remove {}: {}", key, e);
223                 printState();
224             }
225 
226             test(!(key in map));
227             test(lengthIs(len - (should_exist ? 1 : 0)));
228             test!("==")(pool_len, map.bucket_info.num_buckets);
229         }
230 
231         void clear ( )
232         {
233             auto pool_len = map.bucket_info.num_buckets;
234 
235             map.clear();
236             version ( UnitTestVerbose )
237             {
238                 Stdout.format("clear");
239                 printState();
240             }
241 
242             test(lengthIs(0));
243 
244             test!("==")(pool_len, map.bucket_info.num_buckets);
245         }
246 
247         uint[hash_t] expected_keys;
248 
249         void checkContent ( )
250         {
251             foreach (key, val; map)
252             {
253                 uint* n = key in expected_keys;
254 
255                 test!("!is")(n, null);
256 
257                 test(!*n, "duplicate key");
258 
259                 (*n)++;
260             }
261 
262             foreach (n; expected_keys)
263             {
264                 test!("==")(n, 1);
265             }
266         }
267 
268         put(4711, false);   // put
269         put(4711, true);    // double put
270         put(23, false);     // put
271         put(12, false);     // put
272 
273         expected_keys[4711] = 0;
274         expected_keys[23]   = 0;
275         expected_keys[12]   = 0;
276 
277         checkContent();
278 
279         remove(23, true);   // remove
280         remove(23, false);  // double remove
281 
282         expected_keys[4711] = 0;
283         expected_keys[12]   = 0;
284 
285         expected_keys.remove(23);
286 
287         checkContent();
288 
289         put(23, false);     // put
290         put(23, true);      // double put
291 
292         expected_keys[4711] = 0;
293         expected_keys[12]   = 0;
294         expected_keys[23]   = 0;
295 
296         checkContent();
297 
298         clear();
299         foreach (key, val; map)
300         {
301             test(false);
302         }
303 
304         put(4711, false);   // put
305         put(11, false);     // put
306         put(11, true);      // double put
307         put(12, false);     // put
308 
309         expected_keys[4711] = 0;
310         expected_keys[12]   = 0;
311         expected_keys[11]   = 0;
312         expected_keys.remove(23);
313 
314         checkContent();
315 
316         remove(23, false);  // remove
317         put(23, false);     // put
318         put(23, true);      // double put
319 
320         expected_keys[4711] = 0;
321         expected_keys[12]   = 0;
322         expected_keys[11]   = 0;
323         expected_keys[23]   = 0;
324 
325         checkContent();
326 
327         clear();
328         foreach (key, val; map)
329         {
330             test(false);
331         }
332 
333         map.put(1);
334         map.put(2);
335         map.put(3);
336         map.put(4);
337         map.put(5);
338         map.put(6);
339         map.put(7);
340         map.put(8);
341         map.put(9);
342 
343 
344         void testPartial ( typeof(this).Iterator it )
345         {
346             foreach (i, ref k, ref v; it )
347             {
348                 test!("==")( i, 0); // Didn't interrupt when expected 1
349                 if ( i % 3 == 0 ) break;
350             }
351 
352             foreach (i, ref k, ref v; it )
353             {
354                 test( i >= 1 && i <= 3, "Didn't interrupt when expected 2" );
355                 if ( i % 3 == 0 ) break;
356             }
357 
358             foreach (i, ref k, ref v; it )
359             {
360                 test( i >= 4 && i <= 6, "Didn't interrupt when expected 3" );
361                 if ( i % 3 == 0 ) break;
362             }
363 
364             foreach (i, ref k, ref v; it )
365             {
366                 test( i >= 7 && i <= 9, "Didn't interrupt when expected 4" );
367                 if ( i % 3 == 0 ) break;
368             }
369         }
370 
371         auto not_looping_it = map..new InterruptibleIterator;
372 
373         testPartial(not_looping_it);
374 
375         // Should be finished
376         test( not_looping_it.finished() );
377 
378         // Should not run again
379         foreach (i, ref k, ref v; not_looping_it )
380         {
381             test(false, "Ran iteration even though it should be finished");
382         }
383 
384         not_looping_it.reset();
385 
386         test( !not_looping_it.finished() );
387 
388         // After manual reset, should loop again
389         testPartial(not_looping_it);
390         test( not_looping_it.finished() );
391 
392 //            foreach (i, bucket; map.buckets)
393 //            {
394 //                Stdout.formatln("Bucket {,2}: {,2} elements:", i, bucket.length);
395 //                foreach ( element; bucket )
396 //                {
397 //                    Stdout.formatln("  {,2}->{,2}", element.key,
398 //                        *(cast(V*)element.val.ptr));
399 //                }
400 //            }
401     }
402 
403 }
404 
405 unittest
406 {
407     // Instantiate a couple of hashmaps to actually run the unittests in the
408     // class
409     HashMap!(int) hi;
410     HashMap!(char[]) hs;
411     HashMap!(float) hf;
412     HashMap!(Object) ho;
413 }
414 
415 ///
416 unittest
417 {
418     // Unittest that serves as a usage example
419     static immutable expected_number_of_elements = 1_000;
420 
421     // Mapping from hash_t -> int
422     auto map = new HashMap!(int)(expected_number_of_elements);
423 
424     hash_t hash = 232323;
425 
426     // Add a mapping
427     *(map.put(hash)) = 12;
428 
429     // Look up a mapping and obtain a pointer to the value if found or null
430     // if not found;
431     int* val = hash in map;
432 
433     bool exists = val !is null;
434 
435     // Remove a mapping
436     map.remove(hash);
437 
438     // Clear the map
439     map.clear();
440 
441     // Mapping from hash_t -> char[]
442     auto map2 = new HashMap!(char[])(expected_number_of_elements);
443 
444     // Add a mapping
445     char[]* val2 = map2.put(hash);
446 
447     (*val2).length = "hello".length;
448     (*val2)[]      = "hello";
449 
450     // Mapping from hash_t -> struct
451     struct MyStruct
452     {
453         int x;
454         float y;
455     }
456 
457     auto map3 = new HashMap!(MyStruct)(expected_number_of_elements);
458 
459     // Add a mapping, put() never returns null
460     with (*map3.put(hash))
461     {
462         x = 12;
463         y = 23.23;
464     }
465 }