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 }