1 /// 2 module cachetools.containers.hashmap; 3 4 import std.traits; 5 import std.format; 6 import std.typecons; 7 8 import core.memory; 9 import core.bitop; 10 11 private import std.experimental.allocator; 12 private import std.experimental.allocator.mallocator: Mallocator; 13 private import std.experimental.allocator.gc_allocator; 14 15 private import cachetools.internal; 16 private import cachetools.hash; 17 private import cachetools.containers.lists; 18 19 class KeyNotFound: Exception 20 { 21 this(string msg = "key not found") @safe 22 { 23 super(msg); 24 } 25 } 26 27 static if (hash_t.sizeof == 8) 28 { 29 enum EMPTY_HASH = 0x00_00_00_00_00_00_00_00; 30 enum DELETED_HASH = 0x10_00_00_00_00_00_00_00; 31 enum ALLOCATED_HASH = 0x20_00_00_00_00_00_00_00; 32 enum TYPE_MASK = 0xF0_00_00_00_00_00_00_00; 33 enum HASH_MASK = 0x0F_FF_FF_FF_FF_FF_FF_FF; 34 } 35 else static if (hash_t.sizeof == 4) 36 { 37 enum EMPTY_HASH = 0x00_00_00_00; 38 enum DELETED_HASH = 0x10_00_00_00; 39 enum ALLOCATED_HASH = 0x20_00_00_00; 40 enum TYPE_MASK = 0xF0_00_00_00; 41 enum HASH_MASK = 0x0F_FF_FF_FF; 42 } 43 44 /// 45 /// Return true if it is worth to store values inline in hash table 46 /// V footprint should be small enough 47 /// 48 package bool SmallValueFootprint(V)() { 49 import std.traits; 50 static if ( 51 isNumeric!V 52 || isSomeString!V 53 || isSomeChar!V 54 || isPointer!V ) 55 { 56 return true; 57 } 58 else static if ( 59 is(V == struct) && V.sizeof <= (void*).sizeof ) 60 { 61 return true; 62 } 63 else static if ( 64 is(V == class ) && __traits(classInstanceSize, V) <= (void*).sizeof) 65 { 66 return true; 67 } 68 else 69 return false; 70 } 71 72 private bool keyEquals(K)(const K a, const K b) 73 { 74 static if ( is(K==class) ) 75 { 76 if (a is b) 77 { 78 return true; 79 } 80 if (a is null || b is null) 81 { 82 return false; 83 } 84 return a.opEquals(b); 85 } 86 else 87 { 88 return a == b; 89 } 90 } 91 @safe nothrow unittest 92 { 93 class C 94 { 95 int c; 96 this(int v) 97 { 98 c = v; 99 } 100 bool opEquals(const C other) const nothrow @safe 101 { 102 return c == other.c; 103 } 104 } 105 C a = new C(0); 106 C b = new C(1); 107 C c = a; 108 C d = new C(0); 109 assert(!keyEquals(a,b)); 110 assert(keyEquals(a,c)); 111 assert(keyEquals(a,d)); 112 assert(!keyEquals(null, a)); 113 assert(keyEquals(1,1)); 114 } 115 /* 116 package struct ChainedHashMap(K, V, Allocator = Mallocator) 117 { 118 enum initial_buckets_num = 32; 119 enum grow_factor = 2; 120 121 alias StoredKeyType = StoredType!K; 122 alias StoredValueType = StoredType!V; 123 124 private { 125 alias allocator = Allocator.instance; 126 struct _Node { 127 hash_t hash; 128 StoredKeyType key; 129 StoredValueType value; 130 _Node *next_node; 131 } 132 struct _Bucket { 133 int length; 134 _Node node; 135 } 136 _Bucket[] _buckets; 137 int _buckets_num; 138 int _mask; 139 int _allocated; 140 } 141 ~this() 142 { 143 if ( _buckets_num > 0 ) 144 { 145 foreach(ref b; _buckets) 146 { 147 if (b.length == 0) 148 { 149 continue; 150 } 151 auto n = b.node.next_node; 152 while(n) 153 { 154 auto next = n.next_node; 155 () @trusted { 156 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 157 GC.removeRange(n); 158 } 159 dispose(allocator, n); 160 }(); 161 n = next; 162 } 163 } 164 () @trusted { 165 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 166 GC.removeRange(_buckets.ptr); 167 } 168 dispose(allocator, _buckets); 169 }(); 170 } 171 } 172 173 void __copyNodeContent(_Node* source, _Bucket[] buckets) @safe 174 { 175 immutable h = source.hash & HASH_MASK; 176 immutable nbmask = buckets.length - 1; 177 immutable index = h & nbmask; 178 buckets[index].length++; 179 auto n = &buckets[index].node; 180 if ( n.hash < ALLOCATED_HASH ) 181 { 182 debug(cachetools) safe_tracef("Place key %s to bucket %d", source.key, index); 183 *n = *source; 184 n.next_node = null; 185 return; 186 } 187 // allocate and place right after the bucket 188 debug(cachetools) safe_tracef("Place key %s to chain %d", source.key, index); 189 _Node* new_node = make!(_Node)(allocator); 190 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 191 () @trusted { 192 GC.addRange(new_node, _Node.sizeof); 193 }(); 194 } 195 *new_node = *source; 196 new_node.next_node = n.next_node; 197 n.next_node = new_node; 198 } 199 void __relinkNode(_Node* source, _Bucket[] buckets) @safe 200 { 201 immutable h = source.hash & HASH_MASK; 202 immutable nbmask = buckets.length - 1; 203 immutable index = h & nbmask; 204 205 debug(cachetools) safe_tracef("Relink key %s to chain %d", source.key, index); 206 buckets[index].length++; 207 source.next_node = buckets[index].node.next_node; 208 buckets[index].node.next_node = source; 209 } 210 private void doResize(int dest) @safe 211 { 212 immutable _new_buckets_num = dest; 213 immutable _new_mask = dest - 1; 214 215 debug(cachetools) safe_tracef("resizing from %d to %s", _buckets_num, dest); 216 217 _Bucket[] _new_buckets = makeArray!(_Bucket)(allocator, _new_buckets_num); 218 219 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 220 () @trusted { 221 GC.addRange(_new_buckets.ptr, _new_buckets.length * _Bucket.sizeof); 222 }(); 223 } 224 225 int new_allocated = 0; 226 227 // iterate over entries 228 for(int i=0;i<_buckets_num;i++) 229 { 230 if (_buckets[i].length == 0) 231 { 232 continue; 233 } 234 _Bucket* b = &_buckets[i]; 235 _Node* n = b.node.next_node; 236 if ( b.node.hash >= ALLOCATED_HASH ) 237 { 238 __copyNodeContent(&b.node, _new_buckets); 239 new_allocated++; 240 } 241 while(n) 242 { 243 auto next = n.next_node; 244 __relinkNode(n, _new_buckets); 245 n = next; 246 new_allocated++; 247 } 248 } 249 250 (() @trusted { 251 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 252 GC.removeRange(_buckets.ptr); 253 } 254 dispose(allocator, _buckets); 255 })(); 256 assert(_allocated == new_allocated); 257 _buckets = _new_buckets; 258 _buckets_num = _new_buckets_num; 259 _mask = _new_mask; 260 assert(popcnt(_buckets_num) == 1, "Buckets number must be power of 2"); 261 262 debug(cachetools) safe_tracef("resizing done: new loadfactor: %s", (1.0*_allocated) / _buckets_num); 263 264 } 265 266 V* put(K k, V v) @safe 267 out 268 { 269 assert(__result !is null); 270 } 271 do { 272 if ( !_buckets_num ) { 273 _buckets_num = initial_buckets_num; 274 assert(popcnt(_buckets_num) == 1, "Buckets number must be power of 2"); 275 _mask = _buckets_num - 1; 276 _buckets = makeArray!(_Bucket)(allocator, _buckets_num); 277 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 278 () @trusted { 279 GC.addRange(_buckets.ptr, _buckets_num * _Bucket.sizeof); 280 }(); 281 } 282 } 283 284 if ( _allocated>>1 > _buckets_num ) // 4 node per bucket 285 { 286 doResize(grow_factor * _buckets_num); 287 } 288 289 debug(cachetools) safe_tracef("put k: %s, v: %s", k,v); 290 V* r; //result 291 immutable computed_hash = hash_function(k) & HASH_MASK; 292 immutable index = computed_hash & _mask; 293 debug(cachetools) safe_tracef("use index %d", index); 294 _Bucket* bucket = &_buckets[index]; 295 if ( bucket.node.hash < ALLOCATED_HASH ) 296 { 297 debug(cachetools) safe_tracef("empty slot"); 298 _Node* n = &bucket.node; 299 bucket.length++; 300 _allocated++; 301 n.hash = computed_hash | ALLOCATED_HASH; 302 n.key = k; 303 n.value = v; 304 static if ( is(V==StoredValueType) ) 305 { 306 return &n.value; 307 } 308 else 309 { 310 V* rp = () @trusted {return cast(V*)&n.value;}(); 311 return rp; 312 } 313 } 314 else 315 { 316 assert(bucket.length > 0); 317 _Node* n = &bucket.node; 318 _Node* prev; 319 320 while(n) 321 { 322 debug(cachetools) safe_tracef("Check node for key %s", n.key); 323 if ((n.hash & HASH_MASK) == computed_hash && keyEquals(n.key, k)) 324 { 325 // replace old value 326 debug(cachetools) safe_tracef("Replace value for key %s", k); 327 n.value = v; 328 static if ( is(V==StoredValueType) ) 329 { 330 return &n.value; 331 } 332 else 333 { 334 V* rp = () @trusted {return cast(V*)&n.value;}(); 335 return rp; 336 } 337 } 338 else 339 { 340 prev = n; 341 n = n.next_node; 342 } 343 } 344 debug(cachetools) safe_tracef("Create new node for key %s", k); 345 _Node* new_node = make!(_Node)(allocator); 346 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 347 () @trusted { 348 GC.addRange(new_node, _Node.sizeof); 349 }(); 350 } 351 assert(prev !is null); 352 prev.next_node = new_node; 353 new_node.key = k; 354 new_node.value = v; 355 new_node.hash = computed_hash | ALLOCATED_HASH; 356 bucket.length++; 357 _allocated++; 358 static if ( is(V==StoredValueType) ) 359 { 360 return &new_node.value; 361 } 362 else 363 { 364 V* rp = () @trusted {return cast(V*)&new_node.value;}(); 365 return rp; 366 } 367 } 368 assert(0); 369 } 370 /// 371 /// Lookup methods 372 /// 373 V* opBinaryRight(string op)(in K k) @safe if (op == "in") 374 { 375 376 if ( _buckets_num == 0 ) return null; 377 378 immutable computed_hash = hash_function(k) & HASH_MASK; 379 immutable index = computed_hash & _mask; 380 381 _Bucket* bucket = &_buckets[index]; 382 383 if ( bucket.length == 0 ) 384 { 385 return null; 386 } 387 immutable b_hash = bucket.node.hash; 388 if ((b_hash >= ALLOCATED_HASH) && (b_hash & HASH_MASK) == computed_hash && keyEquals(k, bucket.node.key)) 389 { 390 static if ( is(V==StoredValueType) ) 391 { 392 return &bucket.node.value; 393 } 394 else 395 { 396 V* r = () @trusted {return cast(V*)&bucket.node.value;}(); 397 return r; 398 } 399 } 400 _Node* n = bucket.node.next_node; 401 while(n) 402 { 403 if ((n.hash & HASH_MASK) == computed_hash && keyEquals(n.key, k)) 404 { 405 debug(cachetools) safe_tracef("Located value for key %s", k); 406 static if ( is(V==StoredValueType) ) 407 { 408 return &n.value; 409 } 410 else 411 { 412 V* r = () @trusted {return cast(V*)&n.value;}(); 413 return r; 414 } 415 } 416 else 417 { 418 n = n.next_node; 419 } 420 } 421 return null; 422 } 423 ref V getOrAdd(T)(K k, T defaultValue) @safe 424 { 425 V* v = k in this; 426 if ( v ) 427 { 428 return *v; 429 } 430 static if (isAssignable!(V, T)) 431 { 432 return *put(k, defaultValue); 433 } 434 else static if ( isCallable!T && isAssignable!(V, ReturnType!T)) 435 { 436 return *put(k, defaultValue()); 437 } 438 else 439 { 440 static assert(0, "what?"); 441 } 442 } 443 444 alias require = getOrAdd; 445 /// Attention: this can't return ref as default value can be rvalue 446 V get(T)(K k, T defaultValue) @safe 447 { 448 V* v = k in this; 449 if ( v ) 450 { 451 return *v; 452 } 453 static if (isAssignable!(V, T)) 454 { 455 return defaultValue; 456 } 457 else static if ( isCallable!T && isAssignable!(V, ReturnType!T)) 458 { 459 return defaultValue(); 460 } 461 else 462 { 463 static assert(0, "You must call 'get' with default value of HashMap 'value' type, or with callable, returning HashMap 'value'"); 464 } 465 } 466 /// 467 /// Attention: you can't use this method in @nogc code. 468 /// Usual aa[key] method. 469 /// Throws exception if key not found 470 /// 471 ref V opIndex(in K k) @safe 472 { 473 V* v = k in this; 474 if ( v !is null ) 475 { 476 return *v; 477 } 478 throw new KeyNotFound(); 479 } 480 481 /// 482 /// Modifiers 483 /// 484 void opIndexAssign(V v, K k) @safe 485 { 486 put(k, v); 487 } 488 /// 489 /// remomve key from hash, return true if actually removed 490 /// 491 bool remove(K k) @safe 492 { 493 if ( _buckets_num == 0 ) return false; 494 495 immutable computed_hash = hash_function(k) & HASH_MASK; 496 immutable index = computed_hash & _mask; 497 498 _Bucket* bucket = &_buckets[index]; 499 500 if ( bucket.length == 0 ) 501 { 502 return false; 503 } 504 immutable b_hash = bucket.node.hash; 505 if ((b_hash >= ALLOCATED_HASH) && (b_hash & HASH_MASK) == computed_hash && keyEquals(k, bucket.node.key)) 506 { 507 // remove first in chain 508 bucket.node.hash = 0; 509 bucket.length-=1; 510 _allocated-=1; 511 assert(bucket.length >= 0); 512 assert(_allocated >= 0); 513 return true; 514 } 515 _Node* n = bucket.node.next_node; 516 _Node* p = &bucket.node; 517 while(n) 518 { 519 assert(n.hash >= ALLOCATED_HASH); 520 if((n.hash & HASH_MASK) == computed_hash && keyEquals(k, n.key)) 521 { 522 // remove this node 523 p.next_node = n.next_node; 524 () @trusted { 525 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 526 GC.removeRange(n); 527 } 528 dispose(allocator, n); 529 }(); 530 bucket.length--; 531 _allocated--; 532 assert(bucket.length >= 0); 533 assert(_allocated >= 0); 534 return true; 535 } 536 else 537 { 538 p = n; 539 n = n.next_node; 540 } 541 } 542 return false; 543 } 544 auto length() const pure nothrow @nogc @safe 545 { 546 return _allocated; 547 } 548 549 auto size() const pure nothrow @nogc @safe 550 { 551 return _buckets_num; 552 } 553 554 } 555 */ 556 /// 557 struct HashMap(K, V, Allocator = Mallocator, bool GCRangesAllowed = true) { 558 559 enum initial_buckets_num = 32; 560 561 alias StoredKeyType = StoredType!K; 562 alias StoredValueType = StoredType!V; 563 564 private { 565 alias allocator = Allocator.instance; 566 567 struct _Bucket { 568 hash_t hash; 569 StoredKeyType key; 570 StoredValueType value; 571 string toString() const { 572 import std.format; 573 return "%s, hash: %0x,key: %s, value: %s".format( 574 [EMPTY_HASH:"free", DELETED_HASH:"deleted", ALLOCATED_HASH:"allocated"][cast(long )(hash & TYPE_MASK)], 575 hash, key, value); 576 } 577 } 578 579 _Bucket[] _buckets; 580 int _buckets_num; 581 int _mask; 582 int _allocated; 583 int _deleted; 584 int _empty; 585 586 int _grow_factor = 4; 587 588 } 589 590 ~this() @safe { 591 clear(); 592 } 593 invariant { 594 assert(_allocated>=0 && _deleted>=0 && _empty >= 0); 595 assert(_allocated + _deleted + _empty == _buckets_num); 596 } 597 598 /// 599 struct KeyPointer { 600 601 private _Bucket* _bucket; 602 private hash_t _hash; 603 private HashMap!(K,V,Allocator)* _map; 604 605 bool allocated() pure const nothrow @nogc { 606 return _hash >= ALLOCATED_HASH; 607 } 608 609 V get() { 610 static if ( is(V==StoredValueType) ) 611 { 612 return _bucket.value; 613 } 614 else 615 { 616 return cast(V)_bucket.value; 617 } 618 } 619 620 void set(V)(auto ref V v) 621 { 622 bool check_overload = false; 623 debug(cachetools) safe_tracef("bucket: %s", *_bucket); 624 if ( !allocated() ) 625 { 626 _map._allocated++; 627 if ( _bucket.hash == DELETED_HASH ) 628 { 629 _map._deleted--; 630 } 631 else 632 { 633 _map._empty--; 634 } 635 _hash += ALLOCATED_HASH; 636 _bucket.hash = _hash; 637 check_overload = true; 638 } 639 static if ( is(V==StoredValueType) ) 640 { 641 _bucket.value = v; 642 } 643 else 644 { 645 _bucket.value = cast(StoredValueType)v; 646 } 647 if ( check_overload && _map.tooHighLoad ) { 648 _map.doResize(_map._grow_factor * _map._buckets_num); 649 } 650 } 651 } 652 653 /// 654 public KeyPointer keyPointer(K key) @safe { 655 656 if ( !_buckets_num ) { 657 _buckets_num = _empty = initial_buckets_num; 658 assert(popcnt(_buckets_num) == 1, "Buckets number must be power of 2"); 659 _mask = _buckets_num - 1; 660 _buckets = makeArray!(_Bucket)(allocator, _buckets_num); 661 () @trusted { 662 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 663 GC.addRange(_buckets.ptr, _buckets_num * _Bucket.sizeof); 664 } 665 }(); 666 } 667 668 hash_t computed_hash = hash_function(key) & HASH_MASK; 669 immutable start_index = computed_hash & _mask; 670 immutable placement_index = findUpdateIndex(start_index, computed_hash, key); 671 assert(placement_index >= 0); 672 673 immutable allocated = _buckets[placement_index].hash >= ALLOCATED_HASH; 674 if ( !allocated ) 675 { 676 // store key inline 677 _buckets[placement_index].key = key; 678 } 679 else 680 { 681 computed_hash += ALLOCATED_HASH; 682 } 683 return KeyPointer(&_buckets[placement_index], computed_hash, &this); 684 } 685 // Find allocated bucket for given key and computed hash starting from start_index 686 // Returns: index if bucket found or hash_t.max otherwise 687 // 688 // Inherits @nogc from K opEquals() 689 // 690 private hash_t findEntryIndex(const hash_t start_index, const hash_t hash, in K key) pure const @safe 691 in 692 { 693 assert(hash < DELETED_HASH); // we look for real hash 694 assert(start_index < _buckets_num); // start position inside array 695 } 696 do { 697 hash_t index = start_index; 698 699 do { 700 immutable h = _buckets[index].hash; 701 702 debug(cachetools) safe_tracef("test entry index %d (%s) for key %s", index, _buckets[index], key); 703 704 if ( h == EMPTY_HASH ) { 705 break; 706 } 707 708 if ( h >= ALLOCATED_HASH && (h & HASH_MASK) == hash && keyEquals(_buckets[index].key, key) ) { 709 //() @nogc @trusted {debug(cachetools) tracef("test entry index %d for key %s - success", index, key);}(); 710 return index; 711 } 712 index = (index + 1) & _mask; 713 } while(index != start_index); 714 return hash_t.max; 715 } 716 717 // 718 // Find place where we can insert(DELETED or EMPTY bucket) or update existent (ALLOCATED) 719 // bucket for key k and precomputed hash starting from start_index 720 // 721 // 722 // Inherits @nogc from K opEquals() 723 // 724 private hash_t findUpdateIndex(const hash_t start_index, const hash_t computed_hash, in K key) pure const @safe 725 in 726 { 727 assert(computed_hash < DELETED_HASH); 728 assert(start_index < _buckets_num); 729 } 730 do { 731 hash_t index = start_index; 732 733 do { 734 immutable h = _buckets[index].hash; 735 736 debug(cachetools) safe_tracef("test update index %d (%s) for key %s", index, _buckets[index], key); 737 738 if ( h <= DELETED_HASH ) // empty or deleted 739 { 740 debug(cachetools) safe_tracef("test update index %d (%s) for key %s - success", index, _buckets[index], key); 741 return index; 742 } 743 assert((h & TYPE_MASK) == ALLOCATED_HASH); 744 if ( (h & HASH_MASK) == computed_hash && keyEquals(_buckets[index].key, key) ) 745 { 746 debug(cachetools) safe_tracef("test update index %d (%s) for key %s - success", index, _buckets[index], key); 747 return index; 748 } 749 index = (index + 1) & _mask; 750 } while(index != start_index); 751 return hash_t.max; 752 } 753 // 754 // Find unallocated entry in the buckets slice 755 // We use this function during resize() only. 756 // 757 private long findEmptyIndexExtended(const hash_t start_index, in ref _Bucket[] buckets, int new_mask) pure const @safe @nogc 758 in 759 { 760 assert(start_index < buckets.length); 761 } 762 do 763 { 764 hash_t index = start_index; 765 766 do { 767 immutable t = buckets[index].hash; 768 769 debug(cachetools) safe_tracef("test empty index %d (%s)", index, buckets[index]); 770 771 if ( t <= DELETED_HASH ) // empty or deleted 772 { 773 return index; 774 } 775 776 index = (index + 1) & new_mask; 777 } while(index != start_index); 778 return hash_t.max; 779 } 780 781 private bool tooMuchDeleted() pure const @safe @nogc { 782 // 783 // _deleted > _buckets_num / 8 784 // 785 return _deleted << 3 > _buckets_num; 786 } 787 788 private bool tooHighLoad() pure const @safe @nogc { 789 // 790 // _allocated/_buckets_num > 0.8 791 // 5 * allocated > 4 * buckets_num 792 // 793 return _allocated + (_allocated << 2) > _buckets_num << 2; 794 } 795 796 private void doResize(int dest) @safe { 797 immutable _new_buckets_num = dest; 798 immutable _new_mask = dest - 1; 799 _Bucket[] _new_buckets = makeArray!(_Bucket)(allocator, _new_buckets_num); 800 801 static if ( UseGCRanges!(Allocator, K, V, GCRangesAllowed) ) { 802 () @trusted 803 { 804 GC.addRange(_new_buckets.ptr, _new_buckets_num * _Bucket.sizeof); 805 }(); 806 } 807 808 // iterate over entries 809 810 debug(cachetools) safe_tracef("start resizing: old loadfactor: %s", (1.0*_allocated) / _buckets_num); 811 812 for(int i=0;i<_buckets_num;i++) { 813 immutable hash_t h = _buckets[i].hash; 814 if ( h < ALLOCATED_HASH ) { // empty or deleted 815 continue; 816 } 817 818 immutable hash_t start_index = h & _new_mask; 819 immutable new_position = findEmptyIndexExtended(start_index, _new_buckets, _new_mask); 820 821 debug(cachetools) safe_tracef("old hash: %0x, old pos: %d, new_pos: %d", h, i, new_position); 822 823 assert( new_position >= 0 ); 824 assert( _new_buckets[cast(hash_t)new_position].hash == EMPTY_HASH ); 825 826 _new_buckets[cast(hash_t)new_position] = _buckets[i]; 827 } 828 () @trusted { 829 static if ( UseGCRanges!(Allocator, K, V, GCRangesAllowed) ) { 830 GC.removeRange(_buckets.ptr); 831 } 832 dispose(allocator, _buckets.ptr); 833 }(); 834 _buckets = _new_buckets; 835 _buckets_num = _new_buckets_num; 836 _mask = _buckets_num - 1; 837 _deleted = 0; 838 _empty = _buckets_num - _allocated; 839 840 assert(popcnt(_buckets_num) == 1, "Buckets number must be power of 2"); 841 debug(cachetools) safe_tracef("resizing done: new loadfactor: %s", (1.0*_allocated) / _buckets_num); 842 } 843 844 // 845 // Lookup methods 846 // 847 848 /// key in table 849 /// Returns: pointer to stored value (if key in table) or null 850 /// 851 V* opBinaryRight(string op)(in K k) @safe if (op == "in") 852 { 853 854 if ( _buckets_num == 0 ) return null; 855 856 immutable computed_hash = hash_function(k) & HASH_MASK; 857 immutable start_index = computed_hash & _mask; 858 immutable lookup_index = findEntryIndex(start_index, computed_hash, k); 859 if ( lookup_index == hash_t.max) { 860 return null; 861 } 862 static if ( is(V==StoredValueType) ) 863 { 864 return &_buckets[lookup_index].value; 865 } 866 else 867 { 868 V* r = () @trusted {return cast(V*)&_buckets[lookup_index].value;}(); 869 return r; 870 } 871 } 872 873 /// 874 /// get value from hash or add if key is not in table. defaultValue can be callable. 875 /// Returns: ref to value (maybe added) 876 /// 877 ref V getOrAdd(T)(K k, T defaultValue) @safe 878 { 879 V* v = k in this; 880 if ( v ) 881 { 882 return *v; 883 } 884 static if (isAssignable!(V, T)) 885 { 886 return *put(k, defaultValue); 887 } 888 else static if ( isCallable!T && isAssignable!(V, ReturnType!T)) 889 { 890 return *put(k, defaultValue()); 891 } 892 else 893 { 894 static assert(0, "what?"); 895 } 896 } 897 898 /// 899 alias require = getOrAdd; 900 901 /// get current grow factor. 902 auto grow_factor() const @safe { 903 return _grow_factor; 904 } 905 906 /// set grow factor (can be between 2, 4 or 8). 907 void grow_factor(int gf) @safe { 908 if ( gf < 2 ) 909 { 910 _grow_factor = 2; 911 return; 912 } 913 if ( gf > 8 ) 914 { 915 _grow_factor = 8; 916 return; 917 } 918 // enforce new grow_factor is power of 2 919 if ( popcnt(gf) > 1 ) 920 { 921 immutable p = bsr(gf); 922 gf = 1 << (p+1); 923 } 924 _grow_factor = gf; 925 } 926 /// 927 /// get 928 /// Returns: value from hash, or defaultValue if key not found (see also getOrAdd). 929 /// defaultValue can be callable. 930 /// 931 V get(T)(K k, T defaultValue) @safe 932 { 933 V* v = k in this; 934 if ( v ) 935 { 936 return *v; 937 } 938 static if (isAssignable!(V, T)) 939 { 940 return defaultValue; 941 } 942 else static if ( isCallable!T && isAssignable!(V, ReturnType!T)) 943 { 944 return defaultValue(); 945 } 946 else 947 { 948 static assert(0, "You must call 'get' with default value of HashMap 'value' type, or with callable, returning HashMap 'value'"); 949 } 950 } 951 952 /// 953 /// map[key] 954 /// Attention: you can't use this method in @nogc code. 955 /// Usual aa[key] method. 956 /// Throws exception if key not found 957 /// Returns: value for given key 958 /// 959 ref V opIndex(in K k) @safe 960 { 961 V* v = k in this; 962 if ( v !is null ) 963 { 964 return *v; 965 } 966 throw new KeyNotFound(); 967 } 968 969 /// 970 /// map[k] = v; 971 /// 972 void opIndexAssign(V v, K k) @safe 973 { 974 put(k, v); 975 } 976 /// 977 /// put pair (k,v) into hash. 978 /// 979 /// it must be @safe, it inherits @nogc properties from K and V 980 /// It can resize table if table is overloaded or has too much deleted entries. 981 /// Returns: pointer to placed value (pointer is valid until next resize). 982 /// 983 V* put(K k, V v) @safe 984 out 985 { 986 assert(__result !is null); 987 } 988 do { 989 if ( !_buckets_num ) { 990 _buckets_num = _empty = initial_buckets_num; 991 assert(popcnt(_buckets_num) == 1, "Buckets number must be power of 2"); 992 _mask = _buckets_num - 1; 993 _buckets = makeArray!(_Bucket)(allocator, _buckets_num); 994 () @trusted { 995 static if ( UseGCRanges!(Allocator, K, V, GCRangesAllowed) ) { 996 GC.addRange(_buckets.ptr, _buckets_num * _Bucket.sizeof); 997 } 998 }(); 999 } 1000 1001 debug(cachetools) safe_tracef("put k: %s, v: %s", k,v); 1002 1003 if ( tooHighLoad ) { 1004 doResize(_grow_factor * _buckets_num); 1005 } 1006 1007 V* r; //result 1008 immutable computed_hash = hash_function(k) & HASH_MASK; 1009 immutable start_index = computed_hash & _mask; 1010 immutable placement_index = findUpdateIndex(start_index, computed_hash, k); 1011 1012 _Bucket* bucket = &_buckets[placement_index]; 1013 immutable h = bucket.hash; 1014 1015 debug(cachetools) safe_tracef("start_index: %d, placement_index: %d", start_index, placement_index); 1016 1017 if ( h < ALLOCATED_HASH ) 1018 { 1019 _allocated++; 1020 _empty--; 1021 } 1022 debug(cachetools) safe_tracef("place inline buckets[%d] '%s'='%s'", placement_index, k, v); 1023 bucket.value = v; 1024 static if ( is(V==StoredValueType) ) 1025 { 1026 r = &bucket.value; 1027 } 1028 else 1029 { 1030 () @trusted {r = cast(V*)&bucket.value;}(); 1031 } 1032 bucket.hash = computed_hash | ALLOCATED_HASH; 1033 bucket.key = k; 1034 return r; 1035 } 1036 1037 /// 1038 /// remomve key from hash. 1039 /// Returns: true if actually removed, false otherwise. 1040 /// 1041 bool remove(K k) @safe { 1042 1043 if ( tooMuchDeleted ) { 1044 // do not shrink, just compact table 1045 doResize(_buckets_num); 1046 } 1047 1048 1049 debug(cachetools) safe_tracef("remove k: %s", k); 1050 1051 immutable computed_hash = hash_function(k) & HASH_MASK; 1052 immutable start_index = computed_hash & _mask; 1053 immutable lookup_index = findEntryIndex(start_index, computed_hash, k); 1054 if ( lookup_index == hash_t.max ) 1055 { 1056 // nothing to remove 1057 return false; 1058 } 1059 1060 assert((_buckets[lookup_index].hash & TYPE_MASK) == ALLOCATED_HASH, "tried to remove non allocated bucket"); 1061 1062 _allocated--; 1063 immutable next_index = (lookup_index + 1) & _mask; 1064 // if next bucket is EMPTY, then we can convert all DELETED buckets down staring from current to EMPTY buckets 1065 if ( _buckets[next_index].hash == EMPTY_HASH ) 1066 { 1067 _empty++; 1068 _buckets[lookup_index].hash = EMPTY_HASH; 1069 auto free_index = (lookup_index - 1) & _mask; 1070 while (free_index != lookup_index) { 1071 if ( _buckets[free_index].hash != DELETED_HASH ) { 1072 break; 1073 } 1074 _buckets[free_index].hash = EMPTY_HASH; 1075 _deleted--; 1076 _empty++; 1077 free_index = (free_index - 1) & _mask; 1078 } 1079 assert(free_index != lookup_index, "table full of deleted buckets?"); 1080 } 1081 else 1082 { 1083 _buckets[lookup_index].hash = DELETED_HASH; 1084 _deleted++; 1085 } 1086 return true; 1087 } 1088 /// throw away all keys 1089 void clear() @safe 1090 { 1091 if ( _buckets_num > 0 ) 1092 { 1093 () @trusted { 1094 static if ( !is(Allocator == GCAllocator) && (UseGCRanges!K||UseGCRanges!V) ) { 1095 GC.removeRange(_buckets.ptr); 1096 } 1097 dispose(allocator, _buckets.ptr); 1098 }(); 1099 } 1100 1101 _buckets = null; 1102 _allocated = _deleted = _empty = _buckets_num = 0; 1103 } 1104 /// get numter of keys in table 1105 auto length() const pure nothrow @nogc @safe 1106 { 1107 return _allocated; 1108 } 1109 1110 /// get current buckets number 1111 auto size() const pure nothrow @nogc @safe 1112 { 1113 return _buckets_num; 1114 } 1115 1116 /// iterator by keys 1117 auto byKey() pure @safe @nogc 1118 { 1119 struct _kvRange { 1120 int _pos; 1121 ulong _buckets_num; 1122 _Bucket[] _buckets; 1123 this(_Bucket[] _b) 1124 { 1125 _buckets = _b; 1126 _buckets_num = _b.length; 1127 _pos = 0; 1128 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH ) 1129 { 1130 _pos++; 1131 } 1132 } 1133 bool empty() const pure nothrow @safe @nogc { 1134 return _pos == _buckets_num; 1135 } 1136 1137 K front() { 1138 return _buckets[_pos].key; 1139 } 1140 1141 void popFront() pure nothrow @safe @nogc { 1142 _pos++; 1143 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH ) 1144 { 1145 _pos++; 1146 } 1147 } 1148 } 1149 return _kvRange(_buckets); 1150 } 1151 1152 /// iterator by values 1153 auto byValue() pure @safe { 1154 struct _kvRange { 1155 int _pos; 1156 ulong _buckets_num; 1157 _Bucket[] _buckets; 1158 this(_Bucket[] _b) 1159 { 1160 _buckets = _b; 1161 _buckets_num = _b.length; 1162 _pos = 0; 1163 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH ) 1164 { 1165 _pos++; 1166 } 1167 } 1168 bool empty() const pure nothrow @safe @nogc { 1169 return _pos == _buckets_num; 1170 } 1171 1172 V front() { 1173 return _buckets[_pos].value; 1174 } 1175 1176 void popFront() pure nothrow @safe @nogc { 1177 _pos++; 1178 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH ) 1179 { 1180 _pos++; 1181 } 1182 } 1183 } 1184 return _kvRange(_buckets); 1185 } 1186 1187 /// iterator by key/value pairs 1188 auto byPair() pure @safe 1189 { 1190 import std.typecons; 1191 1192 struct _kvRange { 1193 int _pos; 1194 ulong _buckets_num; 1195 _Bucket[] _buckets; 1196 this(_Bucket[] _b) 1197 { 1198 _buckets = _b; 1199 _buckets_num = _b.length; 1200 _pos = 0; 1201 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH ) 1202 { 1203 _pos++; 1204 } 1205 } 1206 bool empty() const pure nothrow @safe @nogc 1207 { 1208 return _pos == _buckets_num; 1209 } 1210 auto front() @safe 1211 { 1212 return Tuple!(K, "key", V, "value")(_buckets[_pos].key, _buckets[_pos].value); 1213 } 1214 void popFront() pure nothrow @safe @nogc 1215 { 1216 _pos++; 1217 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH ) 1218 { 1219 _pos++; 1220 } 1221 } 1222 } 1223 return _kvRange(_buckets); 1224 } 1225 } 1226 1227 /// Example 1228 @safe unittest { 1229 import std.range; 1230 import std.algorithm; 1231 1232 HashMap!(string, int) counter; 1233 string[] words = ["hello", "this", "simple", "example", "should", "succeed", "or", "it", "should", "fail"]; 1234 // count words, simplest and fastest way 1235 foreach (word; words) 1236 { 1237 counter.getOrAdd(word, 0)++; 1238 } 1239 assert("world" !in counter); 1240 assert(counter["hello"] == 1); 1241 assert(counter["should"] == 2); 1242 assert(counter.length == words.length - 1); 1243 // clear counter 1244 counter.clear; 1245 assert(counter.length == 0); 1246 // more verbose way to count 1247 foreach (word; words) 1248 { 1249 auto w = word in counter; 1250 if (w) 1251 { 1252 (*w)++; 1253 } 1254 else 1255 { 1256 counter[word] = 1; 1257 } 1258 } 1259 assert("world" !in counter); 1260 assert(counter["hello"] == 1); 1261 assert(counter["should"] == 2); 1262 assert(counter.length == words.length - 1); 1263 // iterators 1264 assert(counter.byKey.count == counter.byValue.count); 1265 assert(words.all!(w => w in counter)); // all words are in table 1266 assert(counter.byValue.sum == words.length); // sum of counters must equals to number of words 1267 } 1268 // Tests 1269 @safe unittest 1270 { 1271 // test of nogc getOrAdd 1272 import std.experimental.logger; 1273 globalLogLevel = LogLevel.info; 1274 import std.meta; 1275 static foreach(T; AliasSeq!(HashMap!(int, int))) { 1276 () @nogc nothrow 1277 { 1278 T hashMap; 1279 debug(cachetools) safe_tracef("Testing %s", typeid(T)); 1280 foreach (i;0..10) { 1281 hashMap.put(i, i); 1282 } 1283 foreach (i;0..10) { 1284 hashMap.put(i, i); 1285 } 1286 foreach (i;0..10) { 1287 assert(i==*(i in hashMap)); 1288 } 1289 assert(hashMap.length == 10); 1290 hashMap.remove(0); 1291 assert(hashMap.length == 9); 1292 assert((0 in hashMap) is null); 1293 hashMap.remove(1); 1294 assert(hashMap.length == 8); 1295 assert((1 in hashMap) is null); 1296 assert(8 in hashMap); 1297 hashMap.remove(8); 1298 assert(hashMap.length == 7); 1299 assert((8 in hashMap) is null); 1300 foreach (i;0..10) { 1301 hashMap.put(i, i); 1302 } 1303 assert(hashMap.length == 10); 1304 hashMap.remove(8); 1305 hashMap.remove(1); 1306 assert(hashMap.length == 8); 1307 assert((1 in hashMap) is null); 1308 assert((8 in hashMap) is null); 1309 assert(hashMap.remove(1) == false); 1310 foreach (i;0..10) { 1311 hashMap.remove(i); 1312 } 1313 assert(hashMap.length == 0); 1314 //foreach (b; hashMap._buckets) 1315 //{ 1316 // assert(b.length == 0); 1317 // assert(b.node.hash == 0); 1318 // assert(b.node.next_node is null); 1319 //} 1320 }(); 1321 } 1322 //auto v = hashMap.getOrAdd(-1, -1); 1323 //assert(-1 in hashMap && v == -1); 1324 globalLogLevel = LogLevel.info; 1325 } 1326 1327 // test get() 1328 @safe @nogc nothrow unittest 1329 { 1330 import std.meta; 1331 static foreach(T; AliasSeq!(HashMap!(int, int))) { 1332 { 1333 T hashMap; 1334 int i = hashMap.get(1, 55); 1335 assert(i == 55); 1336 i = hashMap.get(1, () => 66); 1337 assert(i == 66); 1338 hashMap[1] = 1; 1339 i = hashMap.get(1, () => 66); 1340 assert(i == 1); 1341 } 1342 } 1343 } 1344 @safe unittest 1345 { 1346 import std.meta; 1347 import std.stdio; 1348 static foreach(T; AliasSeq!(HashMap!(int, int))) { 1349 { 1350 T hashMap; 1351 hashMap[1]=1; 1352 assert( 1 in hashMap); 1353 assert( 2 !in hashMap); 1354 1355 auto kp1 = hashMap.keyPointer(1); 1356 assert(kp1.allocated); 1357 kp1.set(11); 1358 assert(kp1.get() == 11); 1359 assert(hashMap[1] == 11); 1360 1361 auto kp2 = hashMap.keyPointer(2); 1362 assert(!kp2.allocated); 1363 kp2.set(2); 1364 assert(kp2.allocated); 1365 assert(kp2.get() == 2); 1366 assert(hashMap[2] == 2); 1367 assert(hashMap.length == 2); 1368 } 1369 } 1370 struct S 1371 { 1372 int s; 1373 } 1374 HashMap!(immutable S, int) isHashMap; 1375 immutable ss = S(1); 1376 isHashMap[ss] = 1; 1377 assert(ss in isHashMap && *(ss in isHashMap) == 1); 1378 auto kp1 = isHashMap.keyPointer(ss); 1379 assert( kp1.allocated ); 1380 assert( kp1.get() == 1); 1381 kp1.set(2); 1382 assert( isHashMap[ss] == 2); 1383 } 1384 1385 1386 // test immutable struct and class as Key type 1387 @safe unittest 1388 { 1389 import std.experimental.logger; 1390 globalLogLevel = LogLevel.info; 1391 info("Testing hash tables"); 1392 import std.meta; 1393 struct S 1394 { 1395 int s; 1396 } 1397 static foreach(T; AliasSeq!(HashMap!(immutable S, int))) { 1398 () @nogc nothrow 1399 { 1400 T hs1; 1401 immutable ss = S(1); 1402 hs1[ss] = 1; 1403 assert(ss in hs1 && *(ss in hs1) == 1); 1404 }(); 1405 } 1406 static foreach(T; AliasSeq!(HashMap!(int, immutable S))) { 1407 () @nogc nothrow 1408 { 1409 T hs2; 1410 immutable ss = S(1); 1411 hs2[1] = ss; 1412 assert(1 in hs2 && *(1 in hs2) == ss); 1413 assert(!(2 in hs2)); 1414 }(); 1415 } 1416 // class 1417 class C 1418 { 1419 int v; 1420 this(int _v) pure inout 1421 { 1422 v = _v; 1423 } 1424 bool opEquals(const C o) pure const @safe @nogc nothrow { 1425 return v == o.v; 1426 } 1427 override hash_t toHash() const @safe @nogc { 1428 return hash_function(v); 1429 } 1430 } 1431 static foreach(T; AliasSeq!(HashMap!(immutable C, int))) 1432 { 1433 { 1434 T hc1; 1435 immutable cc = new immutable C(1); 1436 hc1[cc] = 1; 1437 assert(hc1[cc] == 1); 1438 } 1439 } 1440 static foreach(T; AliasSeq!(HashMap!(int, immutable C))) 1441 { 1442 { 1443 immutable cc = new immutable C(1); 1444 T hc2; 1445 hc2[1] = cc; 1446 assert(hc2[1] is cc); 1447 } 1448 } 1449 } 1450 @safe unittest { 1451 // test class as key 1452 import std.experimental.logger; 1453 globalLogLevel = LogLevel.info; 1454 class A { 1455 int v; 1456 1457 bool opEquals(const A o) pure const @safe @nogc nothrow { 1458 return v == o.v; 1459 } 1460 override hash_t toHash() const @safe @nogc { 1461 return hash_function(v); 1462 } 1463 this(int v) 1464 { 1465 this.v = v; 1466 } 1467 override string toString() const 1468 { 1469 import std.format; 1470 return "A(%d)".format(v); 1471 } 1472 } 1473 1474 globalLogLevel = LogLevel.info; 1475 auto x = new A(1); 1476 auto y = new A(2); 1477 HashMap!(A, string) dict; 1478 dict.put(x, "x"); 1479 dict.put(y, "y"); 1480 } 1481 1482 @safe unittest { 1483 import std.experimental.logger; 1484 globalLogLevel = LogLevel.info; 1485 () @nogc nothrow { 1486 HashMap!(int, int) int2int; 1487 foreach(i; 0..15) { 1488 int2int.put(i,i); 1489 } 1490 assert(int2int.length() == 15); 1491 foreach(i; 0..15) { 1492 assert(i in int2int); 1493 } 1494 foreach(i; 0..15) { 1495 int2int.remove(i); 1496 } 1497 assert(int2int.length() == 0); 1498 }(); 1499 () @nogc nothrow { 1500 struct LargeStruct { 1501 ulong a; 1502 ulong b; 1503 } 1504 HashMap!(int, LargeStruct) int2ls; 1505 foreach(i; 1..5) { 1506 int2ls.put(i,LargeStruct(i,i)); 1507 } 1508 int2ls.put(33,LargeStruct(33,33)); // <- follow key 1, move key 2 on pos 3 1509 assert(1 in int2ls, "1 not in hash"); 1510 assert(2 in int2ls, "2 not in hash"); 1511 assert(3 in int2ls, "3 not in hash"); 1512 assert(4 in int2ls, "4 not in hash"); 1513 assert(33 in int2ls, "33 not in hash"); 1514 int2ls.remove(33); 1515 int2ls.put(2,LargeStruct(2,2)); // <- must replace key 2 on pos 3 1516 assert(2 in int2ls, "2 not in hash"); 1517 }(); 1518 } 1519 1520 @safe unittest { 1521 import std.experimental.logger; 1522 globalLogLevel = LogLevel.info; 1523 () @nogc nothrow { 1524 assert(SmallValueFootprint!int()); 1525 assert(SmallValueFootprint!double()); 1526 struct SmallStruct { 1527 ulong a; 1528 } 1529 //assert(SmallValueFootprint!SmallStruct); 1530 struct LargeStruct { 1531 ulong a; 1532 ulong b; 1533 } 1534 assert(!SmallValueFootprint!LargeStruct); 1535 class SmallClass { 1536 ulong a; 1537 } 1538 //assert(!SmallValueFootprint!SmallClass); 1539 1540 HashMap!(int, string) int2string; 1541 auto u = int2string.put(1, "one"); 1542 { 1543 auto v = 1 in int2string; 1544 assert(v !is null); 1545 assert(*v == "one"); 1546 } 1547 assert(2 !in int2string); 1548 u = int2string.put(32+1, "33"); 1549 assert(33 in int2string); 1550 assert(int2string.remove(33)); 1551 assert(!int2string.remove(33)); 1552 1553 HashMap!(int, LargeStruct) int2LagreStruct; 1554 int2LagreStruct.put(1, LargeStruct(1,2)); 1555 { 1556 auto v = 1 in int2LagreStruct; 1557 assert(v !is null); 1558 assert(*v == LargeStruct(1, 2)); 1559 } 1560 }(); 1561 1562 globalLogLevel = LogLevel.info; 1563 } 1564 1565 @safe unittest { 1566 import std.experimental.logger; 1567 import std.experimental.allocator.gc_allocator; 1568 globalLogLevel = LogLevel.info; 1569 static int i; 1570 () @safe @nogc nothrow { 1571 struct LargeStruct { 1572 ulong a; 1573 ulong b; 1574 ~this() @safe @nogc { 1575 i++; 1576 } 1577 } 1578 HashMap!(int, LargeStruct) int2LagreStruct; 1579 int2LagreStruct.put(1, LargeStruct(1,2)); 1580 }(); 1581 globalLogLevel = LogLevel.info; 1582 } 1583 1584 @safe unittest /* not nothrow as opIndex may throw */ 1585 { 1586 import std.typecons; 1587 alias K = Tuple!(int, int); 1588 alias V = int; 1589 HashMap!(K,V) h; 1590 K k0 = K(0,1); 1591 V v0 = 1; 1592 h.put(k0, v0); 1593 int *v = k0 in h; 1594 assert(v); 1595 assert(*v == 1); 1596 h[k0] = v0; 1597 assert(h[k0] == v0); 1598 } 1599 1600 @safe nothrow unittest 1601 { 1602 class c { 1603 int a; 1604 this(int a) 1605 { 1606 this.a = a; 1607 } 1608 override hash_t toHash() const pure @nogc @safe 1609 { 1610 return hash_function(a); 1611 } 1612 bool opEquals(const c other) pure const nothrow @safe @nogc 1613 { 1614 return this is other || this.a == other.a; 1615 } 1616 } 1617 alias K = c; 1618 alias V = int; 1619 K k0 = new c(0); 1620 V v0 = 1; 1621 () @nogc nothrow { 1622 HashMap!(K,V) h; 1623 h.put(k0, v0); 1624 int *v = k0 in h; 1625 assert(v); 1626 assert(*v == 1); 1627 h[k0] = 2; 1628 v = k0 in h; 1629 assert(*v == 2); 1630 }(); 1631 } 1632 1633 // Test if we can work with non-@nogc opEquals for class-key. 1634 // opEquals anyway must be non-@system. 1635 @safe nothrow unittest 1636 { 1637 class c { 1638 int a; 1639 this(int a) 1640 { 1641 this.a = a; 1642 } 1643 override hash_t toHash() const pure @safe 1644 { 1645 int[] _ = [1, 2, 3]; // this cause GC 1646 return hash_function(a); 1647 } 1648 1649 bool opEquals(const c other) const pure nothrow @safe 1650 { 1651 auto _ = [1,2,3]; // this cause GC 1652 return this is other || this.a == other.a; 1653 } 1654 } 1655 alias K = c; 1656 alias V = int; 1657 HashMap!(K,V) h; 1658 K k0 = new c(0); 1659 V v0 = 1; 1660 h.put(k0, v0); 1661 int *v = k0 in h; 1662 assert(v); 1663 assert(*v == 1); 1664 K k1 = new c(1); 1665 V v1 = 1; 1666 h.put(k0, v0); 1667 assert(!keyEquals(k0, k1)); 1668 } 1669 // 1670 // test byKey, byValue, byPair 1671 // 1672 @safe nothrow unittest 1673 { 1674 import std.algorithm; 1675 import std.array; 1676 import std.stdio; 1677 1678 HashMap!(int, string) m; 1679 m[1] = "one"; 1680 m[2] = "two"; 1681 m[10] = "ten"; 1682 assert(equal(m.byKey.array.sort, [1,2,10])); 1683 assert(equal(m.byValue.array.sort, ["one", "ten", "two"])); 1684 assert(equal( 1685 m.byPair.map!"tuple(a.key, a.value)".array.sort, 1686 [tuple(1, "one"), tuple(2, "two"), tuple(10, "ten")] 1687 )); 1688 m.remove(1); 1689 m.remove(10); 1690 assert(equal( 1691 m.byPair.map!"tuple(a.key, a.value)".array.sort, 1692 [tuple(2, "two")] 1693 )); 1694 m.remove(2); 1695 assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0); 1696 m.remove(2); 1697 assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0); 1698 } 1699 // 1700 // compare equivalence to AA 1701 // 1702 /* not @safe because of AA */ unittest { 1703 import std.random; 1704 import std.array; 1705 import std.algorithm; 1706 import std.stdio; 1707 import std.experimental.logger; 1708 1709 enum iterations = 400_000; 1710 1711 globalLogLevel = LogLevel.info; 1712 1713 HashMap!(int, int) hashMap; 1714 int[int] AA; 1715 1716 auto rnd = Random(unpredictableSeed); 1717 1718 foreach(i;0..iterations) { 1719 int k = uniform(0, iterations, rnd); 1720 hashMap.put(k, i); 1721 AA[k] = i; 1722 } 1723 assert(equal(AA.keys().sort(), hashMap.byKey().array.sort())); 1724 assert(equal(AA.values().sort(), hashMap.byValue().array.sort())); 1725 assert(AA.length == hashMap.length); 1726 } 1727 // 1728 // check remove 1729 // 1730 @safe unittest 1731 { 1732 // test removal while iterating 1733 import std.random; 1734 import std.array; 1735 import std.algorithm; 1736 import std.stdio; 1737 import std.experimental.logger; 1738 1739 enum iterations = 400_000; 1740 1741 globalLogLevel = LogLevel.info; 1742 1743 HashMap!(int, int) hashMap; 1744 1745 auto rnd = Random(unpredictableSeed); 1746 1747 foreach(i;0..iterations) { 1748 int k = uniform(0, iterations, rnd); 1749 hashMap[k] = i; 1750 } 1751 foreach(k; hashMap.byKey) 1752 { 1753 assert(hashMap.remove(k)); 1754 } 1755 assert(hashMap.length == 0); 1756 } 1757 // 1758 // test clear 1759 // 1760 @safe @nogc nothrow unittest 1761 { 1762 // test clear 1763 1764 HashMap!(int, int) hashMap; 1765 1766 foreach(i;0..100) { 1767 hashMap[i] = i; 1768 } 1769 hashMap.clear(); 1770 assert(hashMap.length == 0); 1771 hashMap[1] = 1; 1772 assert(1 in hashMap && hashMap.length == 1); 1773 } 1774 // 1775 // test getOrAdd with value 1776 // 1777 @safe @nogc nothrow unittest 1778 { 1779 // test of nogc getOrAdd 1780 1781 HashMap!(int, int) hashMap; 1782 1783 foreach(i;0..100) { 1784 hashMap[i] = i; 1785 } 1786 auto v = hashMap.getOrAdd(-1, -1); 1787 assert(-1 in hashMap && v == -1); 1788 } 1789 1790 // 1791 // test getOrAdd with callable 1792 // 1793 @safe @nogc nothrow unittest 1794 { 1795 // test of nogc getOrAdd with lazy default value 1796 1797 HashMap!(int, int) hashMap; 1798 1799 foreach(i;0..100) { 1800 hashMap[i] = i; 1801 } 1802 int v = hashMap.getOrAdd(-1, () => -1); 1803 assert(-1 in hashMap && v == -1); 1804 assert(hashMap.get(-1, 0) == -1); // key -1 is in hash, return value 1805 assert(hashMap.get(-2, 0) == 0); // key -2 not in map, return default value 1806 assert(hashMap.get(-3, () => 0) == 0); // ditto 1807 } 1808 1809 // 1810 // test getOrAdd with complex data 1811 // 1812 @safe unittest 1813 { 1814 import std.socket, std.meta; 1815 static foreach(T; AliasSeq!(HashMap!(string, Socket))) 1816 { 1817 { 1818 T socketPool; 1819 Socket s0 = socketPool.getOrAdd("http://example.com", () => new Socket(AddressFamily.INET, SocketType.STREAM)); 1820 assert(s0 !is null); 1821 assert(s0.addressFamily == AddressFamily.INET); 1822 Socket s1 = socketPool.getOrAdd("http://example.com", () => new Socket(AddressFamily.INET, SocketType.STREAM)); 1823 assert(s1 !is null); 1824 assert(s1 is s0); 1825 } 1826 } 1827 } 1828 // 1829 // test with real class (socket) 1830 // 1831 @safe unittest 1832 { 1833 import std.socket; 1834 class Connection { 1835 Socket s; 1836 bool opEquals(const Connection other) const pure @safe 1837 { 1838 return s is other.s; 1839 } 1840 override hash_t toHash() const @safe 1841 { 1842 return hash_function(s.handle); 1843 } 1844 } 1845 HashMap!(Connection, string) socketPool; 1846 } 1847 1848 @safe unittest 1849 { 1850 // test of non-@nogc getOrAdd with lazy default value 1851 import std.conv; 1852 import std.exception; 1853 import std.experimental.logger; 1854 import std.meta; 1855 1856 globalLogLevel = LogLevel.info; 1857 class C { 1858 string v; 1859 this(int _v) @safe 1860 { 1861 v = to!string(_v); 1862 } 1863 } 1864 static foreach(T; AliasSeq!(HashMap!(int, C))) 1865 { 1866 { 1867 T hashMap; 1868 1869 foreach(i;0..100) { 1870 hashMap[i] = new C(i); 1871 } 1872 C v = hashMap.getOrAdd(-1, () => new C(-1)); 1873 assert(-1 in hashMap && v.v == "-1"); 1874 assert(hashMap[-1].v == "-1"); 1875 hashMap[-1].v ~= "1"; 1876 assert(hashMap[-1].v == "-11"); 1877 assertThrown!KeyNotFound(hashMap[-2]); 1878 // check lazyness 1879 bool called; 1880 v = hashMap.getOrAdd(-1, delegate C() {called = true; return new C(0);}); 1881 assert(!called); 1882 v = hashMap.getOrAdd(-2, delegate C() {called = true; return new C(0);}); 1883 assert(called); 1884 } 1885 } 1886 } 1887 // 1888 // test if we can handle some exotic value type 1889 // 1890 @safe @nogc nothrow unittest 1891 { 1892 // test of nogc getOrAdd with lazy default value 1893 // corner case when V is callable 1894 1895 alias F = int function() @safe @nogc nothrow; 1896 1897 F one = function() 1898 { 1899 return 1; 1900 }; 1901 F two = function() 1902 { 1903 return 2; 1904 }; 1905 F three = function() 1906 { 1907 return 3; 1908 }; 1909 F four = function() 1910 { 1911 return 4; 1912 }; 1913 HashMap!(int, F) hashMap; 1914 hashMap.put(1, one); 1915 hashMap.put(2, two); 1916 auto p = 1 in hashMap; 1917 assert(p); 1918 assert((*p)() == 1); 1919 p = 2 in hashMap; 1920 assert(p); 1921 assert((*p)() == 2); 1922 auto f3 = hashMap.getOrAdd(3, () => function int() {return 3;}); // used as default() 1923 assert(f3() == 3); 1924 auto f4 = hashMap.getOrAdd(4, four); 1925 assert(f4() == 4); 1926 } 1927 1928 // test get() 1929 @safe @nogc nothrow unittest 1930 { 1931 HashMap!(int, int) hashMap; 1932 int i = hashMap.get(1, 55); 1933 assert(i == 55); 1934 i = hashMap.get(1, () => 66); 1935 assert(i == 66); 1936 hashMap[1] = 1; 1937 i = hashMap.get(1, () => 66); 1938 assert(i == 1); 1939 } 1940 // test grow_factor() 1941 unittest 1942 { 1943 import std.experimental.logger; 1944 globalLogLevel = LogLevel.info; 1945 HashMap!(int, int) hashMap; 1946 hashMap.grow_factor(3); 1947 assert(hashMap.grow_factor() == 4); 1948 hashMap.grow_factor(0); 1949 assert(hashMap.grow_factor() == 2); 1950 hashMap.grow_factor(16); 1951 assert(hashMap.grow_factor() == 8); 1952 assert(hashMap.size == 0); 1953 assert(hashMap.length == 0); 1954 auto kp = hashMap.keyPointer(1); 1955 assert(hashMap.size > 0); 1956 assert(hashMap.length == 0); 1957 kp.set(1); 1958 assert(hashMap.length == 1); 1959 foreach(i;1..16) hashMap[i]=i; 1960 hashMap.remove(1); 1961 kp = hashMap.keyPointer(1); 1962 kp.set(1); 1963 }