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 moduleocean.util.container.map.HashMap;
41 42 importocean.util.container.map.Map;
43 44 version (UnitTestVerbose) importocean.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 publicclassHashMap ( 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 publicthis ( size_tn, floatload_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 publicthis ( IAllocatorallocator, size_tn, floatload_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 publicoverridehash_ttoHash ( inhash_tkey )
120 {
121 returnkey;
122 }
123 124 /***************************************************************************
125 126 HashMap unittest.
127 128 ***************************************************************************/129 130 version (unittest)
131 {
132 importocean.core.Test;
133 importocean.meta.traits.Basic : isFloatingPointType;
134 importocean.math.IEEE : isNaN;
135 }
136 137 unittest138 {
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 scopemap = newtypeof(this)(10);
148 149 version ( UnitTestVerbose ) voidprintState ( )
150 {
151 Stdout.formatln(" :: len={}, load={}, max_load={}",
152 map.length, map.bucket_info.load, map.bucket_info.max_load);
153 }
154 155 boollengthIs ( size_texpected )
156 {
157 test!("==")(map.length, expected);
158 159 intc;
160 foreach ( k, v; map )
161 {
162 c++;
163 }
164 returnc == expected;
165 }
166 167 voidput ( hash_tkey, boolshould_exist )
168 {
169 autolen = map.length;
170 171 test!("==")(((keyinmap) !isnull), should_exist);
172 173 autoe = 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")((keyinmap), null);
184 185 staticif (is (VU : U[]) && !is (V == V[]))
186 {
187 // work around DMD bug 7752188 189 Vv_init;
190 191 test!("==")(*map.get(key), v_init);
192 }
193 elsestaticif ( is ( V == class ) )
194 {
195 test!("is")(*map.get(key), V.init);
196 }
197 elsestaticif ( isFloatingPointType!(V) )
198 {
199 // Specialised test for floating point types, where200 // V.init != V.init201 test(isNaN(*map.get(key))); // Value does not equal previously set value202 }
203 else204 {
205 test!("is")(*map.get(key), V.init); // Value does not equal previously set value206 }
207 208 test(lengthIs(len + (should_exist ? 0 : 1)),
209 "Length different from foreach-counted elements!");
210 }
211 212 voidremove ( hash_tkey, boolshould_exist )
213 {
214 autolen = map.length;
215 autopool_len = map.bucket_info.num_buckets;
216 217 test!("==")(((keyinmap) !isnull), should_exist);
218 219 autoe = map.remove(key);
220 version ( UnitTestVerbose )
221 {
222 Stdout.format("remove {}: {}", key, e);
223 printState();
224 }
225 226 test(!(keyinmap));
227 test(lengthIs(len - (should_exist ? 1 : 0)));
228 test!("==")(pool_len, map.bucket_info.num_buckets);
229 }
230 231 voidclear ( )
232 {
233 autopool_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 voidcheckContent ( )
250 {
251 foreach (key, val; map)
252 {
253 uint* n = keyinexpected_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); // put269 put(4711, true); // double put270 put(23, false); // put271 put(12, false); // put272 273 expected_keys[4711] = 0;
274 expected_keys[23] = 0;
275 expected_keys[12] = 0;
276 277 checkContent();
278 279 remove(23, true); // remove280 remove(23, false); // double remove281 282 expected_keys[4711] = 0;
283 expected_keys[12] = 0;
284 285 expected_keys.remove(23);
286 287 checkContent();
288 289 put(23, false); // put290 put(23, true); // double put291 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); // put305 put(11, false); // put306 put(11, true); // double put307 put(12, false); // put308 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); // remove317 put(23, false); // put318 put(23, true); // double put319 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 voidtestPartial ( typeof(this).Iteratorit )
345 {
346 foreach (i, refk, refv; it )
347 {
348 test!("==")( i, 0); // Didn't interrupt when expected 1349 if ( i % 3 == 0 ) break;
350 }
351 352 foreach (i, refk, refv; 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, refk, refv; 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, refk, refv; it )
365 {
366 test( i >= 7 && i <= 9, "Didn't interrupt when expected 4" );
367 if ( i % 3 == 0 ) break;
368 }
369 }
370 371 autonot_looping_it = map..newInterruptibleIterator;
372 373 testPartial(not_looping_it);
374 375 // Should be finished376 test( not_looping_it.finished() );
377 378 // Should not run again379 foreach (i, refk, refv; 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 again389 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 unittest406 {
407 // Instantiate a couple of hashmaps to actually run the unittests in the408 // class409 HashMap!(int) hi;
410 HashMap!(char[]) hs;
411 HashMap!(float) hf;
412 HashMap!(Object) ho;
413 }
414 415 ///416 unittest417 {
418 // Unittest that serves as a usage example419 staticimmutableexpected_number_of_elements = 1_000;
420 421 // Mapping from hash_t -> int422 automap = newHashMap!(int)(expected_number_of_elements);
423 424 hash_thash = 232323;
425 426 // Add a mapping427 *(map.put(hash)) = 12;
428 429 // Look up a mapping and obtain a pointer to the value if found or null430 // if not found;431 int* val = hashinmap;
432 433 boolexists = val !isnull;
434 435 // Remove a mapping436 map.remove(hash);
437 438 // Clear the map439 map.clear();
440 441 // Mapping from hash_t -> char[]442 automap2 = newHashMap!(char[])(expected_number_of_elements);
443 444 // Add a mapping445 char[]* val2 = map2.put(hash);
446 447 (*val2).length = "hello".length;
448 (*val2)[] = "hello";
449 450 // Mapping from hash_t -> struct451 structMyStruct452 {
453 intx;
454 floaty;
455 }
456 457 automap3 = newHashMap!(MyStruct)(expected_number_of_elements);
458 459 // Add a mapping, put() never returns null460 with (*map3.put(hash))
461 {
462 x = 12;
463 y = 23.23;
464 }
465 }