1 module dstruct.map; 2 3 import core.memory; 4 import core.exception; 5 6 import core.stdc..string: memcpy, memset; 7 8 import std.range : ElementType; 9 import std.traits : Unqual, isPointer; 10 11 import dstruct.support; 12 13 private enum EntryState { 14 empty = 0, 15 occupied = 1, 16 deleted = 2, 17 } 18 19 private enum SearchFor { 20 empty = 1, 21 occupied = 2, 22 deleted = 4, 23 notDeleted = empty | occupied, 24 notOccupied = empty | deleted, 25 any = empty | occupied | deleted, 26 } 27 28 private enum is64Bit = is(size_t == ulong); 29 30 // Given a size, compute the space in bytes required for a member when 31 // the member is aligned to a size_t.sizeof boundary. 32 private size_t alignedSize(size_t size) { 33 if (size % size_t.sizeof) { 34 // If there's a remainder, then our boundary is a little ahead. 35 return cast(size_t) (size / size_t.sizeof) + 1 * size_t.sizeof; 36 } 37 38 // If the size is cleanly divisible, it will fit right into place. 39 return size; 40 } 41 42 private enum isHashIdentical(T) = 43 is(T : uint) 44 | is(T : char) 45 | is(T : wchar) 46 | is(T : dchar) 47 | isPointer!T 48 | (is64Bit && is(T : ulong)); 49 50 /** 51 * An item from a map. 52 * 53 * The keys and the values in the map are references into the map itself. 54 */ 55 struct Entry(K, V) { 56 private: 57 K _key; 58 V _value; 59 EntryState _state = EntryState.empty; 60 61 static if (!isHashIdentical!K) { 62 size_t _hash; 63 } 64 public: 65 /** 66 * A key from the map. 67 */ 68 @nogc @safe pure nothrow 69 @property ref inout(K) key() inout { 70 return _key; 71 } 72 73 /** 74 * A value from the map. 75 */ 76 @nogc @safe pure nothrow 77 @property ref inout(V) value() inout { 78 return _value; 79 } 80 } 81 82 // TODO: Test different entry sizes. 83 84 static if(__VERSION__ < 2066) { 85 private alias SafeGetHashType = size_t delegate(const(void*)) pure nothrow; 86 } else { 87 // Use mixin to hide UDA errors from old D compilers, it will never 88 // execute the mixin, so it won't report an error in syntax. 89 mixin("private alias SafeGetHashType = size_t delegate(const(void*)) @nogc pure nothrow;"); 90 } 91 92 @nogc @trusted pure nothrow 93 private size_t computeHash(K)(ref K key) if (!isHashIdentical!K) { 94 // Cast so we can keep our function qualifiers. 95 return (cast(SafeGetHashType) &(typeid(K).getHash))(&key); 96 } 97 98 @nogc @trusted pure nothrow 99 private size_t castHash(K)(K key) if (isHashIdentical!K) { 100 return cast(size_t) key; 101 } 102 103 private enum size_t minimumBucketListSize = 8; 104 105 @nogc @safe pure nothrow 106 private size_t newBucketListSize(size_t currentLength) { 107 return currentLength * 2; 108 } 109 110 @trusted 111 private size_t bucketListSearch(SearchFor searchFor, K, V) 112 (ref const(Entry!(K, V)[]) bucketList, const(K) key) 113 if (isHashIdentical!K) { 114 size_t index = castHash(key) & (bucketList.length - 1); 115 116 foreach(j; 1 .. bucketList.length) { 117 static if (searchFor == SearchFor.notOccupied) { 118 if (bucketList[index]._state != EntryState.occupied) { 119 return index; 120 } 121 122 if (bucketList[index]._key == key) { 123 return index; 124 } 125 } else { 126 static if (searchFor & SearchFor.empty) { 127 if (bucketList[index]._state == EntryState.empty) { 128 return index; 129 } 130 } 131 132 static if (searchFor & SearchFor.deleted) { 133 if (bucketList[index]._state == EntryState.deleted 134 && bucketList[index]._key == key) { 135 return index; 136 } 137 } 138 139 static if (searchFor & SearchFor.occupied) { 140 if (bucketList[index]._state == EntryState.occupied 141 && bucketList[index]._key == key) { 142 return index; 143 } 144 } 145 } 146 147 index = (index + j) & (bucketList.length - 1); 148 } 149 150 assert(false, "Slot not found!"); 151 } 152 153 @trusted 154 private size_t bucketListSearch(SearchFor searchFor, K, V) 155 (ref const(Entry!(K, V)[]) bucketList, size_t hash, const(K) key) 156 if (!isHashIdentical!K) { 157 size_t index = hash & (bucketList.length - 1); 158 159 foreach(j; 1 .. bucketList.length) { 160 static if (searchFor == SearchFor.notOccupied) { 161 if (bucketList[index]._state != EntryState.occupied) { 162 return index; 163 } 164 165 if (bucketList[index]._hash == hash 166 && bucketList[index]._key == key) { 167 return index; 168 } 169 } else { 170 static if (searchFor & SearchFor.empty) { 171 if (bucketList[index]._state == EntryState.empty) { 172 return index; 173 } 174 } 175 176 static if (searchFor & SearchFor.deleted) { 177 if (bucketList[index]._state == EntryState.deleted 178 && bucketList[index]._hash == hash 179 && bucketList[index]._key == key) { 180 return index; 181 } 182 183 } 184 185 static if (searchFor & SearchFor.occupied) { 186 if (bucketList[index]._state == EntryState.occupied 187 && bucketList[index]._hash == hash 188 && bucketList[index]._key == key) { 189 return index; 190 } 191 } 192 } 193 194 index = (index + j) & (bucketList.length - 1); 195 } 196 197 assert(false, "Slot not found!"); 198 } 199 200 // Add an entry into the bucket list. 201 // memcpy is used here because some types have immutable members which cannot 202 // be changed, so we have to force them into the array this way. 203 @nogc @trusted pure nothrow 204 private void setEntry(K, V)(ref Entry!(K, V)[] bucketList, 205 size_t index, auto ref K key, auto ref V value) { 206 enum valueOffset = alignedSize(K.sizeof); 207 208 // Copy the key and value into the entry. 209 memcpy(cast(void*) &bucketList[index], &key, K.sizeof); 210 memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof); 211 212 bucketList[index]._state = EntryState.occupied; 213 } 214 215 @nogc @trusted pure nothrow 216 private void setEntry(K, V)(ref Entry!(K, V)[] bucketList, 217 size_t index, size_t hash, auto ref K key, auto ref V value) { 218 enum valueOffset = alignedSize(K.sizeof); 219 220 // Copy the key and value into the entry. 221 memcpy(cast(void*) &bucketList[index], &key, K.sizeof); 222 memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof); 223 224 bucketList[index]._hash = hash; 225 bucketList[index]._state = EntryState.occupied; 226 } 227 228 // Update just the value for an entry. 229 @nogc @trusted pure nothrow 230 private void updateEntryValue(K, V)(ref Entry!(K, V)[] bucketList, 231 size_t index, auto ref V value) { 232 enum valueOffset = alignedSize(K.sizeof); 233 234 memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof); 235 } 236 237 @nogc @trusted pure nothrow 238 private void zeroEntryValue(K, V)(ref Entry!(K, V)[] bucketList, 239 size_t index) { 240 enum valueOffset = alignedSize(K.sizeof); 241 242 memset(cast(void*) &bucketList[index] + valueOffset, 0, V.sizeof); 243 } 244 245 @nogc @trusted pure nothrow 246 private bool thresholdPassed(size_t length, size_t bucketCount) { 247 return length * 2 >= bucketCount; 248 } 249 250 /** 251 * This struct implements a hashmap type, much like the standard associative 252 * array type. 253 * 254 * This map should be almost totally usable in @safe pure nothrow functions. 255 * 256 * An empty map will be a valid object, and will not result in any allocations. 257 */ 258 struct HashMap(K, V) 259 if(isAssignmentCopyable!(Unqual!K) && isAssignmentCopyable!(Unqual!V)) { 260 alias ThisType = typeof(this); 261 262 private Entry!(K, V)[] _bucketList; 263 private size_t _length; 264 265 /** 266 * Construct a hashmap reserving a minimum of :minimumSize: space 267 * for the bucket list. The actual space allocated may be some number 268 * larger than the requested size, but it will be enough to fit 269 * as many items as requested without another allocation. 270 * 271 * Params: 272 * minimumSize = The minimum size for the hashmap. 273 */ 274 @safe pure nothrow 275 this(size_t minimumSize) { 276 if (minimumSize == 0) { 277 // 0 is a special case. 278 return; 279 } 280 281 if (minimumSize <= minimumBucketListSize / 2) { 282 _bucketList = new Entry!(K, V)[](minimumBucketListSize); 283 } else { 284 // Find the next largest power of two which will fit this size. 285 size_t size = 8; 286 287 while (thresholdPassed(minimumSize, size)) { 288 size *= 2; 289 } 290 291 _bucketList = new Entry!(K, V)[](newBucketListSize(size)); 292 } 293 } 294 295 @trusted 296 private void copyToBucketList(ref Entry!(K, V)[] newBucketList) const { 297 foreach(ref entry; _bucketList) { 298 if (entry._state != EntryState.occupied) { 299 // Skip holes in the container. 300 continue; 301 } 302 303 static if (isHashIdentical!K) { 304 size_t index = 305 bucketListSearch!(SearchFor.empty, K, V) 306 (newBucketList, cast(K) entry._key); 307 308 newBucketList.setEntry( 309 index, 310 cast(K) entry._key, 311 cast(V) entry._value 312 ); 313 } else { 314 size_t index = 315 bucketListSearch!(SearchFor.empty, K, V) 316 (newBucketList, entry._hash, cast(K) entry._key); 317 318 newBucketList.setEntry( 319 index, 320 entry._hash, 321 cast(K) entry._key, 322 cast(V) entry._value 323 ); 324 } 325 } 326 } 327 328 @trusted 329 private void resize(size_t newBucketListLength) in { 330 assert(newBucketListLength > _bucketList.length); 331 } body { 332 auto newBucketList = new Entry!(K, V)[](newBucketListLength); 333 334 copyToBucketList(newBucketList); 335 336 _bucketList = newBucketList; 337 } 338 339 /** 340 * Set a value in the map. 341 * 342 * Params: 343 * key = The key in the map. 344 * value = A value to set in the map. 345 */ 346 void opIndexAssign(V value, K key) { 347 static if (!isHashIdentical!K) { 348 size_t hash = computeHash(key); 349 } 350 351 if (_bucketList.length == 0) { 352 // 0 length is a special case. 353 _length = 1; 354 resize(minimumBucketListSize); 355 356 static if (isHashIdentical!K) { 357 size_t index = castHash(key) & (_bucketList.length - 1); 358 359 _bucketList.setEntry(index, key, value); 360 } else { 361 size_t index = hash & (_bucketList.length - 1); 362 363 _bucketList.setEntry(index, hash, key, value); 364 } 365 366 return; 367 } 368 369 static if (isHashIdentical!K) { 370 size_t index = 371 bucketListSearch!(SearchFor.notDeleted, K, V) 372 (_bucketList, key); 373 } else { 374 size_t index = 375 bucketListSearch!(SearchFor.notDeleted, K, V) 376 (_bucketList, hash, key); 377 } 378 379 if (_bucketList[index]._state != EntryState.occupied) { 380 // This slot is not occupied, so insert the entry here. 381 static if (isHashIdentical!K) { 382 _bucketList.setEntry(index, key, value); 383 } else { 384 _bucketList.setEntry(index, hash, key, value); 385 } 386 387 ++_length; 388 389 if (thresholdPassed(_length, _bucketList.length)) { 390 // Resize the bucketList, as it passed the threshold. 391 resize(newBucketListSize(_bucketList.length)); 392 } 393 } else { 394 // We have this key already, so update the value. 395 _bucketList.updateEntryValue(index, value); 396 } 397 } 398 399 /** 400 * Implement the 'in' operator for a map. 401 * 402 * The in operator on a map will return a pointer to a value, which will 403 * be null when no corresponding value is set for a given key. 404 * 405 * Params: 406 * key = The key in the map. 407 * 408 * Returns: 409 * A pointer to a value, a null pointer if a value is not set. 410 */ 411 inout(V)* opBinaryRight(string op)(K key) inout if (op == "in") { 412 static if (isHashIdentical!K) { 413 size_t index = 414 bucketListSearch!(SearchFor.notDeleted, K, V) 415 (_bucketList, key); 416 } else { 417 size_t index = 418 bucketListSearch!(SearchFor.notDeleted, K, V) 419 (_bucketList, computeHash(key), key); 420 } 421 422 if (_bucketList[index]._state == EntryState.empty) { 423 return null; 424 } 425 426 return &(_bucketList[index]._value); 427 } 428 429 /** 430 * Retrieve a value from the map. 431 * 432 * If a value is not set for the given key, a RangeError will be thrown. 433 * 434 * Params: 435 * key = The key in the map. 436 * 437 * Returns: 438 * A value from the map. 439 */ 440 ref inout(V) opIndex(K key) inout { 441 static if (isHashIdentical!K) { 442 size_t index = 443 bucketListSearch!(SearchFor.notDeleted, K, V) 444 (_bucketList, key); 445 } else { 446 size_t index = 447 bucketListSearch!(SearchFor.notDeleted, K, V) 448 (_bucketList, computeHash(key), key); 449 } 450 451 assert( 452 _bucketList[index]._state != EntryState.empty, 453 "Key not found in HashMap!" 454 ); 455 456 return _bucketList[index]._value; 457 } 458 459 /** 460 * Get a value from the map, or return the given default value, which 461 * is lazy-evaluated. 462 * 463 * Params: 464 * key = The key in the map. 465 * def = A lazy default value. 466 * 467 * Returns: 468 * A value from the map, or the default value. 469 */ 470 V get(V2)(K key, lazy V2 def) const if(is(V2 : V)) { 471 static if (isHashIdentical!K) { 472 size_t index = 473 bucketListSearch!(SearchFor.notDeleted, K, V) 474 (_bucketList, key); 475 } else { 476 size_t index = 477 bucketListSearch!(SearchFor.notDeleted, K, V) 478 (_bucketList, computeHash(key), key); 479 } 480 481 if (_bucketList[index]._state == EntryState.empty) { 482 return def(); 483 } 484 485 return _bucketList[index]._value; 486 } 487 488 /** 489 * Get a value from the map, or return V.init if a value is not set for 490 * a given key. 491 * 492 * Params: 493 * key = The key in the map. 494 * 495 * Returns: 496 * A value from the map, or the default value. 497 */ 498 inout(V) get(K key) inout { 499 static if (isHashIdentical!K) { 500 size_t index = 501 bucketListSearch!(SearchFor.notDeleted, K, V) 502 (_bucketList, key); 503 } else { 504 size_t index = 505 bucketListSearch!(SearchFor.notDeleted, K, V) 506 (_bucketList, computeHash(key), key); 507 } 508 509 510 if (_bucketList[index]._state == EntryState.empty) { 511 return V.init; 512 } 513 514 return _bucketList[index]._value; 515 } 516 517 /** 518 * Get or create a value from/in the map. 519 * 520 * Given a key, and a lazy evaluated default value, 521 * attempt to retrieve a value from the map. If a value for the given 522 * key is not set, set the provided default value in the map and 523 * return that. 524 * 525 * The value will be returned by reference. 526 * 527 * Params: 528 * key = The key in the map. 529 * def = A lazy default value. 530 * 531 * Returns: 532 * A reference to the value in the map. 533 */ 534 ref V setDefault(V2)(K key, lazy V2 value) if (is(V2 : V)) { 535 static if (!isHashIdentical!K) { 536 size_t hash = computeHash(key); 537 } 538 539 if (_bucketList.length == 0) { 540 // 0 length is a special case. 541 _length = 1; 542 resize(minimumBucketListSize); 543 544 static if (isHashIdentical!K) { 545 size_t index = castHash(key) & (_bucketList.length - 1); 546 547 _bucketList.setEntry(index, key, V.init); 548 } else { 549 size_t index = hash & (_bucketList.length - 1); 550 551 _bucketList.setEntry(index, hash, key, V.init); 552 } 553 554 return _bucketList[index].value; 555 } 556 557 static if (isHashIdentical!K) { 558 size_t index = 559 bucketListSearch!(SearchFor.notDeleted, K, V) 560 (_bucketList, key); 561 } else { 562 size_t index = 563 bucketListSearch!(SearchFor.notDeleted, K, V) 564 (_bucketList, hash, key); 565 } 566 567 if (_bucketList[index]._state == EntryState.empty) { 568 // The entry is empty, so we can insert the value here. 569 static if (isHashIdentical!K) { 570 _bucketList.setEntry(index, key, value()); 571 } else { 572 _bucketList.setEntry(index, hash, key, value()); 573 } 574 575 ++_length; 576 577 if (thresholdPassed(_length, _bucketList.length)) { 578 // Resize the bucketList, as it passed the threshold. 579 resize(newBucketListSize(_bucketList.length)); 580 581 // Update the index, it has now changed. 582 static if (isHashIdentical!K) { 583 index = bucketListSearch!(SearchFor.notDeleted, K, V) 584 (_bucketList, key); 585 } else { 586 index = bucketListSearch!(SearchFor.notDeleted, K, V) 587 (_bucketList, hash, key); 588 } 589 } 590 } 591 592 // Return a reference to the value. 593 return _bucketList[index]._value; 594 } 595 596 /** 597 * Get or create a value from/in a hashmap. 598 * 599 * Given a key attempt to retrieve a value from the hashmap. 600 * If a value for the given key is not set, set the value in 601 * the associative array to the default value for the value's type. 602 * 603 * The value will be returned by reference. 604 * 605 * Params: 606 * key = The key in the map. 607 * 608 * Returns: 609 * A reference to the value in the map. 610 */ 611 ref V setDefault(K key) { 612 static if (!isHashIdentical!K) { 613 size_t hash = computeHash(key); 614 } 615 616 if (_bucketList.length == 0) { 617 // 0 length is a special case. 618 _length = 1; 619 resize(minimumBucketListSize); 620 621 static if (isHashIdentical!K) { 622 size_t index = castHash(key) & (_bucketList.length - 1); 623 624 _bucketList.setEntry(index, key, V.init); 625 } else { 626 size_t index = hash & (_bucketList.length - 1); 627 628 _bucketList.setEntry(index, hash, key, V.init); 629 } 630 631 return _bucketList[index].value; 632 } 633 634 static if (isHashIdentical!K) { 635 size_t index = 636 bucketListSearch!(SearchFor.notDeleted, K, V) 637 (_bucketList, key); 638 } else { 639 size_t index = 640 bucketListSearch!(SearchFor.notDeleted, K, V) 641 (_bucketList, hash, key); 642 } 643 644 if (_bucketList[index]._state == EntryState.empty) { 645 // The entry is empty, so we can insert the value here. 646 static if (isHashIdentical!K) { 647 _bucketList.setEntry(index, key, V.init); 648 } else { 649 _bucketList.setEntry(index, hash, key, V.init); 650 } 651 652 ++_length; 653 654 if (thresholdPassed(_length, _bucketList.length)) { 655 // Resize the bucketList, as it passed the threshold. 656 resize(newBucketListSize(_bucketList.length)); 657 658 // Update the index, it has now changed. 659 static if (isHashIdentical!K) { 660 index = bucketListSearch!(SearchFor.notDeleted, K, V) 661 (_bucketList, key); 662 } else { 663 index = bucketListSearch!(SearchFor.notDeleted, K, V) 664 (_bucketList, hash, key); 665 } 666 } 667 } 668 669 // Return a reference to the value. 670 return _bucketList[index]._value; 671 } 672 673 /** 674 * Remove a entry from the map if it is set, given a key. 675 * 676 * Params: 677 * key = The key in the map. 678 * 679 * Returns: 680 * true if a value was removed, otherwise false. 681 */ 682 bool remove(K key) { 683 static if (isHashIdentical!K) { 684 size_t index = 685 bucketListSearch!(SearchFor.any, K, V) 686 (_bucketList, key); 687 } else { 688 size_t index = 689 bucketListSearch!(SearchFor.any, K, V) 690 (_bucketList, computeHash(key), key); 691 } 692 693 if (_bucketList[index]._state == EntryState.occupied) { 694 --_length; 695 696 // Zero the value and mark the slot as 'deleted', which is 697 // treated often the same as 'empty', only we can skip over 698 // deleted values to search for more values. 699 _bucketList.zeroEntryValue(index); 700 _bucketList[index]._state = EntryState.deleted; 701 702 return true; 703 } 704 705 return false; 706 } 707 708 /** 709 * The length of the map. 710 * 711 * Returns: The number of entries in the map, in constant time. 712 */ 713 @nogc @safe pure nothrow 714 @property size_t length() const { 715 return _length; 716 } 717 718 /** 719 * Returns: True if this map is empty. 720 */ 721 @nogc @safe pure nothrow 722 @property bool empty() const { 723 return _length == 0; 724 } 725 726 /** 727 * Implement boolean conversion for a map. 728 * 729 * Returns: True if this set is not empty. 730 */ 731 @nogc @safe pure nothrow 732 bool opCast(T: bool)() const { 733 return !empty; 734 } 735 736 static if(isDupable!K && isDupable!V) { 737 /** 738 * Copy an existing map into a new mutable map. 739 * 740 * Returns: 741 * The fresh copy of the map. 742 */ 743 @safe pure nothrow 744 HashMap!(K, V) dup() const { 745 if (_length == 0) { 746 // Just return nothing special for length 0. 747 return HashMap!(K, V).init; 748 } 749 750 // Create a new map large enough to fit all of our values. 751 auto newMap = HashMap!(K, V)(_length); 752 newMap._length = _length; 753 754 copyToBucketList(newMap._bucketList); 755 756 return newMap; 757 } 758 } 759 760 761 /** 762 * Test if two maps are equal. 763 * 764 * Params: 765 * otherMap = Another map. 766 * 767 * Returns: 768 * true only if the maps are equal in length, keys, and values. 769 */ 770 bool opEquals(ref const(HashMap!(K, V)) otherMap) const { 771 if (_length != otherMap._length) { 772 return false; 773 } 774 775 foreach(ref entry; _bucketList) { 776 if (entry._state != EntryState.occupied) { 777 // Skip holes in the container. 778 continue; 779 } 780 781 static if (isHashIdentical!K) { 782 size_t index = 783 bucketListSearch!(SearchFor.notDeleted, K, V) 784 (otherMap._bucketList, entry._key); 785 } else { 786 size_t index = 787 bucketListSearch!(SearchFor.notDeleted, K, V) 788 (otherMap._bucketList, entry._hash, entry._key); 789 } 790 791 if (otherMap._bucketList[index]._state == EntryState.empty) { 792 return false; 793 } 794 } 795 796 return true; 797 } 798 799 /// ditto 800 bool opEquals(const(HashMap!(K, V)) otherMap) const { 801 return opEquals(otherMap); 802 } 803 } 804 805 template HashMapKeyType(T) { 806 alias HashMapKeyType = typeof(ElementType!(typeof(T._bucketList))._key); 807 } 808 809 template HashMapValueType(T) { 810 alias HashMapValueType = typeof(ElementType!(typeof(T._bucketList))._value); 811 } 812 813 // Test that is is not possible to create a map with a key or a value type 814 // which cannot be copy-assigned. 815 unittest { 816 struct NonCopyable { @disable this(this); } 817 818 assert(!__traits(compiles, HashMap!(NonCopyable, int))); 819 assert(__traits(compiles, HashMap!(NonCopyable*, int))); 820 assert(!__traits(compiles, HashMap!(int, NonCopyable))); 821 assert(__traits(compiles, HashMap!(int, NonCopyable*))); 822 } 823 824 // Check setting values, retrieval, removal, and lengths. 825 unittest { 826 HashMap!(int, string) map; 827 828 map[1] = "a"; 829 map[2] = "b"; 830 map[3] = "c"; 831 832 map[1] = "x"; 833 map[2] = "y"; 834 map[3] = "z"; 835 836 assert(map.length == 3); 837 838 assert(map[1] == "x"); 839 assert(map[2] == "y"); 840 assert(map[3] == "z"); 841 842 assert(map.remove(3)); 843 assert(map.remove(2)); 844 assert(map.remove(1)); 845 846 assert(!map.remove(1)); 847 848 assert(map.length == 0); 849 } 850 851 unittest { 852 HashMap!(int, string) map; 853 854 map[1] = "a"; 855 map[2] = "b"; 856 map[3] = "c"; 857 map[4] = "d"; 858 map[5] = "e"; 859 map[6] = "f"; 860 map[7] = "g"; 861 map[8] = "h"; 862 map[9] = "i"; 863 864 map[1] = "x"; 865 map[2] = "y"; 866 map[3] = "z"; 867 868 assert(map.length == 9); 869 870 assert(map[1] == "x"); 871 assert(map[2] == "y"); 872 assert(map[3] == "z"); 873 assert(map[4] == "d"); 874 assert(map[5] == "e"); 875 assert(map[6] == "f"); 876 assert(map[7] == "g"); 877 assert(map[8] == "h"); 878 assert(map[9] == "i"); 879 880 assert(map.remove(3)); 881 assert(map.remove(2)); 882 assert(map.remove(1)); 883 884 assert(!map.remove(1)); 885 886 assert(map.length == 6); 887 } 888 889 // Test the map with heavy collisions. 890 unittest { 891 struct BadHashObject { 892 int value; 893 894 this(int value) { 895 this.value = value; 896 } 897 898 @safe nothrow 899 size_t toHash() const { 900 return 0; 901 } 902 903 @nogc @safe nothrow pure 904 bool opEquals(ref const BadHashObject other) const { 905 return value == other.value; 906 } 907 } 908 909 HashMap!(BadHashObject, string) map; 910 enum size_t mapSize = 100; 911 912 foreach(num; 0 .. mapSize) { 913 map[BadHashObject(cast(int) num)] = "a"; 914 } 915 916 assert(map.length == mapSize); 917 } 918 919 // Test preallocated maps; 920 unittest { 921 auto map = HashMap!(int, string)(3); 922 923 assert(map._bucketList.length == minimumBucketListSize); 924 } 925 926 // Test the 'in' operator. 927 unittest { 928 // We'll test that our attributes work. 929 @safe pure nothrow 930 void runTest() { 931 HashMap!(int, string) map; 932 933 map[1] = "a"; 934 map[2] = "b"; 935 map[3] = "c"; 936 937 // Test that @nogc works. 938 @nogc @safe pure nothrow 939 void runNoGCPart(typeof(map) map) { 940 assert((4 in map) is null); 941 942 assert(*(1 in map) == "a"); 943 assert(*(2 in map) == "b"); 944 assert(*(3 in map) == "c"); 945 } 946 947 runNoGCPart(map); 948 } 949 950 runTest(); 951 } 952 953 // Test the map with a weird type which makes assignment harder. 954 unittest { 955 struct WeirdType { 956 // The alignment could cause memory issues so we'll check for that. 957 align(1): 958 // This immutable member means we need to use memset above to set 959 // keys or values. 960 immutable(byte)* foo = null; 961 size_t x = 3; 962 963 @nogc @safe pure nothrow 964 this(int value) { 965 x = value; 966 } 967 968 @nogc @safe pure nothrow 969 size_t toHash() const { 970 return x; 971 } 972 973 @nogc @safe pure nothrow 974 bool opEquals(ref const(WeirdType) other) const { 975 return foo == other.foo && x == other.x; 976 } 977 978 @nogc @safe pure nothrow 979 bool opEquals(const(WeirdType) other) const { 980 return opEquals(other); 981 } 982 } 983 984 @safe pure nothrow 985 void runTest() { 986 HashMap!(WeirdType, string) map; 987 988 map[WeirdType(10)] = "a"; 989 990 @nogc @safe pure nothrow 991 void runNoGCPart(typeof(map) map) { 992 assert(map[WeirdType(10)] == "a"); 993 } 994 995 runNoGCPart(map); 996 } 997 998 runTest(); 999 } 1000 1001 // Test get with default init 1002 unittest { 1003 @safe pure nothrow 1004 void runTest() { 1005 HashMap!(int, string) map; 1006 1007 map[1] = "a"; 1008 1009 @nogc @safe pure nothrow 1010 void runNoGCPart(typeof(map) map) { 1011 assert(map.get(1) == "a"); 1012 assert(map.get(2) is null); 1013 } 1014 runNoGCPart(map); 1015 } 1016 1017 runTest(); 1018 1019 } 1020 1021 // Test length, empty, and cast(bool) 1022 unittest { 1023 @safe pure nothrow 1024 void runTest() { 1025 HashMap!(int, string) map; 1026 1027 @nogc @safe pure nothrow 1028 void runNoGCPart1(typeof(map) map) { 1029 assert(map.length == 0); 1030 assert(map.empty); 1031 1032 if (map) { 1033 assert(false, "cast(bool) failed for an empty map"); 1034 } 1035 } 1036 1037 @nogc @safe pure nothrow 1038 void runNoGCPart2(typeof(map) map) { 1039 assert(map.length == 1); 1040 assert(!map.empty); 1041 1042 if (!map) { 1043 assert(false, "cast(bool) failed for an non-empty map"); 1044 } 1045 } 1046 1047 @nogc @safe pure nothrow 1048 void runNoGCPart3(typeof(map) map) { 1049 assert(map.length == 0); 1050 assert(map.empty); 1051 1052 if (map) { 1053 assert(false, "cast(bool) failed for an empty map"); 1054 } 1055 } 1056 1057 runNoGCPart1(map); 1058 map[1] = "a"; 1059 runNoGCPart2(map); 1060 map.remove(1); 1061 runNoGCPart3(map); 1062 } 1063 1064 runTest(); 1065 } 1066 1067 // BUG: The lazy argument here cannot be made to be nothrow, @nogc, etc. 1068 // Test get with a given default. 1069 unittest { 1070 HashMap!(int, string) map; 1071 1072 map[1] = "a"; 1073 1074 assert(map.get(1, "b") == "a"); 1075 assert(map.get(2, "b") == "b"); 1076 } 1077 1078 // Test opEquals 1079 unittest { 1080 @safe pure nothrow 1081 void runTest() { 1082 HashMap!(string, string) leftMap; 1083 HashMap!(string, string) rightMap; 1084 1085 // Give the left one a bit more, and take away from it. 1086 leftMap["a"] = "1"; 1087 leftMap["b"] = "2"; 1088 leftMap["c"] = "3"; 1089 leftMap["d"] = "4"; 1090 leftMap["e"] = "5"; 1091 leftMap["f"] = "6"; 1092 leftMap["g"] = "7"; 1093 leftMap["h"] = "8"; 1094 leftMap["i"] = "9"; 1095 leftMap["j"] = "10"; 1096 1097 rightMap["a"] = "1"; 1098 rightMap["b"] = "2"; 1099 rightMap["c"] = "3"; 1100 1101 @nogc @safe pure nothrow 1102 void runNoGCPart(typeof(leftMap) leftMap, typeof(rightMap) rightMap) { 1103 // Remove the extra keys 1104 leftMap.remove("d"); 1105 leftMap.remove("e"); 1106 leftMap.remove("f"); 1107 leftMap.remove("g"); 1108 leftMap.remove("h"); 1109 leftMap.remove("i"); 1110 leftMap.remove("j"); 1111 1112 // Now the two maps should have different bucketLists, but they 1113 // should still be considered equal. 1114 assert(leftMap == rightMap); 1115 } 1116 1117 runNoGCPart(leftMap, rightMap); 1118 } 1119 1120 runTest(); 1121 } 1122 1123 // Test setDefault with default init 1124 unittest { 1125 @safe pure nothrow 1126 void runTest() { 1127 HashMap!(int, string) map; 1128 1129 map[1] = "a"; 1130 1131 // setDefault for basic types with no explicit default ought to 1132 // be nothrow. 1133 assert(map.setDefault(1) == "a"); 1134 assert(map.setDefault(2) is null); 1135 1136 assert(map.length == 2); 1137 1138 assert(map[2] is null); 1139 } 1140 1141 runTest(); 1142 } 1143 1144 // Test setDefault with a given value. 1145 unittest { 1146 HashMap!(int, string) map; 1147 1148 map[1] = "a"; 1149 1150 assert(map.setDefault(1, "b") == "a"); 1151 assert(map.setDefault(2, "b") == "b"); 1152 1153 assert(map.length == 2); 1154 1155 assert(map[2] == "b"); 1156 } 1157 1158 // Test setDefault with a given value which can be implicitly converted. 1159 unittest { 1160 HashMap!(int, long) map; 1161 1162 map[1] = 2; 1163 1164 assert(map.setDefault(1, 3) == 2); 1165 1166 int x = 4; 1167 1168 assert(map.setDefault(2, x) == 4); 1169 1170 assert(map.length == 2); 1171 1172 assert(map[2] == 4); 1173 } 1174 1175 /** 1176 * A range through a series of items in the map. 1177 */ 1178 struct KeyValueRange(K, V) { 1179 private: 1180 Entry!(K, V)[] _bucketList = null; 1181 public: 1182 @nogc @safe pure nothrow 1183 this(Entry!(K, V)[] bucketList) { 1184 foreach(index, ref entry; bucketList) { 1185 if (entry._state == EntryState.occupied) { 1186 // Use a slice of the bucketList starting here. 1187 _bucketList = bucketList[index .. $]; 1188 1189 return; 1190 } 1191 } 1192 } 1193 1194 @nogc @trusted pure nothrow 1195 this(const(Entry!(K, V)[]) bucketList) { 1196 this(cast(Entry!(K, V)[]) bucketList); 1197 } 1198 1199 @nogc @safe pure nothrow 1200 inout(typeof(this)) save() inout { 1201 return this; 1202 } 1203 1204 @nogc @safe pure nothrow 1205 @property 1206 bool empty() const { 1207 // We can check that the bucketList is empty to check if this range is 1208 // empty, because we will clear it after we pop the last item. 1209 return _bucketList.length == 0; 1210 } 1211 1212 @nogc @safe pure nothrow 1213 @property 1214 ref inout(Entry!(K, V)) front() inout in { 1215 assert(!empty()); 1216 } body { 1217 return _bucketList[0]; 1218 } 1219 1220 @nogc @safe pure nothrow 1221 void popFront() in { 1222 assert(!empty()); 1223 } body { 1224 foreach(index; 1 .. _bucketList.length) { 1225 if (_bucketList[index]._state == EntryState.occupied) { 1226 // Use a slice of the bucketList starting here. 1227 _bucketList = _bucketList[index .. $]; 1228 1229 return; 1230 } 1231 } 1232 1233 // Clear the bucketList if we hit the end. 1234 _bucketList = null; 1235 } 1236 } 1237 1238 /** 1239 * Produce a range through the items of a map. (A key-value pair) 1240 * 1241 * Params: 1242 * map = A map. 1243 * Returns: 1244 * A range running through the items in the map. 1245 */ 1246 @nogc @safe pure nothrow 1247 auto byKeyValue(K, V)(auto ref HashMap!(K, V) map) { 1248 if (map.length == 0) { 1249 // Empty ranges should not have to traverse the bucketList at all. 1250 return KeyValueRange!(K, V).init; 1251 } 1252 1253 return KeyValueRange!(K, V)(map._bucketList); 1254 } 1255 1256 /// ditto 1257 @nogc @trusted pure nothrow 1258 auto byKeyValue(K, V)(auto ref const(HashMap!(K, V)) map) { 1259 alias RealK = HashMapKeyType!(typeof(map)); 1260 alias RealV = HashMapValueType!(typeof(map)); 1261 1262 if (map.length == 0) { 1263 return KeyValueRange!(RealK, RealV).init; 1264 } 1265 1266 return KeyValueRange!(RealK, RealV)( 1267 cast(Entry!(RealK, RealV)[]) 1268 map._bucketList 1269 ); 1270 } 1271 1272 /// ditto 1273 @nogc @trusted pure nothrow 1274 auto byKeyValue(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1275 alias RealK = HashMapKeyType!(typeof(map)); 1276 alias RealV = HashMapValueType!(typeof(map)); 1277 1278 if (map.length == 0) { 1279 return KeyValueRange!(RealK, RealV).init; 1280 } 1281 1282 return KeyValueRange!(RealK, RealV)( 1283 cast(Entry!(RealK, RealV)[]) 1284 map._bucketList 1285 ); 1286 } 1287 1288 unittest { 1289 HashMap!(int, string) map; 1290 1291 map[1] = "a"; 1292 map[2] = "b"; 1293 map[3] = "c"; 1294 1295 int[] keyList; 1296 string[] valueList; 1297 1298 foreach(item; map.byKeyValue()) { 1299 keyList ~= item.key; 1300 valueList ~= item.value; 1301 } 1302 1303 // From the way the bucketLists are distributed, we know we'll get this back. 1304 assert(keyList == [1, 2, 3]); 1305 assert(valueList == ["a", "b", "c"]); 1306 } 1307 1308 unittest { 1309 HashMap!(string, string) mmap; 1310 const(HashMap!(string, string)) cmap; 1311 immutable(HashMap!(string, string)) imap; 1312 1313 auto mItems = mmap.byKeyValue(); 1314 auto cItems = cmap.byKeyValue(); 1315 auto iItems = imap.byKeyValue(); 1316 1317 assert(is(typeof(mItems.front.key) == string)); 1318 assert(is(typeof(cItems.front.key) == const(string))); 1319 assert(is(typeof(iItems.front.key) == immutable(string))); 1320 assert(is(typeof(mItems.front.value) == string)); 1321 assert(is(typeof(cItems.front.value) == const(string))); 1322 assert(is(typeof(iItems.front.value) == immutable(string))); 1323 } 1324 1325 // Test that the ranges can be created from r-values. 1326 unittest { 1327 auto func() { 1328 HashMap!(int, string) map; 1329 1330 map[1] = "a"; 1331 map[2] = "b"; 1332 map[3] = "c"; 1333 1334 return map; 1335 } 1336 1337 auto keyRange = func().byKey(); 1338 auto valueRange = func().byValue(); 1339 auto itemRange = func().byKeyValue(); 1340 } 1341 1342 /** 1343 * This is a range which runs through a series of keys in map. 1344 */ 1345 struct KeyRange(K, V) { 1346 private: 1347 KeyValueRange!(K, V) _keyValueRange; 1348 public: 1349 @nogc @safe pure nothrow 1350 private this()(auto ref Entry!(K, V)[] bucketList) { 1351 _keyValueRange = KeyValueRange!(K, V)(bucketList); 1352 } 1353 1354 /// 1355 @nogc @safe pure nothrow 1356 inout(typeof(this)) save() inout { 1357 return this; 1358 } 1359 1360 /// 1361 @nogc @safe pure nothrow 1362 @property 1363 bool empty() const { 1364 return _keyValueRange.empty; 1365 } 1366 1367 /// 1368 @nogc @trusted pure nothrow 1369 @property 1370 ref inout(K) front() inout { 1371 return _keyValueRange.front.key; 1372 } 1373 1374 /// 1375 @nogc @safe pure nothrow 1376 void popFront() { 1377 _keyValueRange.popFront(); 1378 } 1379 } 1380 1381 /** 1382 * Produce a range through the keys of a map. 1383 * 1384 * Params: 1385 * map = A map. 1386 * Returns: 1387 * A range running through the keys in the map. 1388 */ 1389 @nogc @safe pure nothrow 1390 auto byKey(K, V)(auto ref HashMap!(K, V) map) { 1391 if (map.length == 0) { 1392 return KeyRange!(K, V).init; 1393 } 1394 1395 return KeyRange!(K, V)(map._bucketList); 1396 } 1397 1398 /// ditto 1399 @nogc @trusted pure nothrow 1400 auto byKey(K, V)(auto ref const(HashMap!(K, V)) map) { 1401 alias RealK = HashMapKeyType!(typeof(map)); 1402 alias RealV = HashMapValueType!(typeof(map)); 1403 1404 if (map.length == 0) { 1405 return KeyRange!(RealK, RealV).init; 1406 } 1407 1408 return KeyRange!(RealK, RealV)( 1409 cast(Entry!(RealK, RealV)[]) 1410 map._bucketList 1411 ); 1412 } 1413 1414 /// ditto 1415 @nogc @trusted pure nothrow 1416 auto byKey(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1417 alias RealK = HashMapKeyType!(typeof(map)); 1418 alias RealV = HashMapValueType!(typeof(map)); 1419 1420 if (map.length == 0) { 1421 return KeyRange!(RealK, RealV).init; 1422 } 1423 1424 return KeyRange!(RealK, RealV)( 1425 cast(Entry!(RealK, RealV)[]) 1426 map._bucketList 1427 ); 1428 } 1429 1430 unittest { 1431 HashMap!(int, string) map; 1432 1433 map[1] = "a"; 1434 map[2] = "b"; 1435 map[3] = "c"; 1436 1437 int[] keyList; 1438 1439 foreach(ref key; map.byKey()) { 1440 keyList ~= key; 1441 } 1442 1443 // From the way the bucketLists are distributed, we know we'll get this back. 1444 assert(keyList == [1, 2, 3]); 1445 } 1446 1447 unittest { 1448 HashMap!(string, string) mmap; 1449 const(HashMap!(string, string)) cmap; 1450 immutable(HashMap!(string, string)) imap; 1451 1452 auto mKeys = mmap.byKey(); 1453 auto cKeys = cmap.byKey(); 1454 auto iKeys = imap.byKey(); 1455 1456 assert(is(typeof(mKeys.front) == string)); 1457 assert(is(typeof(cKeys.front) == const(string))); 1458 assert(is(typeof(iKeys.front) == immutable(string))); 1459 } 1460 1461 /** 1462 * This is a range which runs through a series of values in a map. 1463 */ 1464 struct ValueRange(K, V) { 1465 private: 1466 KeyValueRange!(K, V) _keyValueRange; 1467 public: 1468 @nogc @safe pure nothrow 1469 private this()(auto ref Entry!(K, V)[] bucketList) { 1470 _keyValueRange = KeyValueRange!(K, V)(bucketList); 1471 } 1472 1473 /// 1474 @nogc @safe pure nothrow 1475 inout(typeof(this)) save() inout { 1476 return this; 1477 } 1478 1479 /// 1480 @nogc @safe pure nothrow 1481 @property 1482 bool empty() const { 1483 return _keyValueRange.empty; 1484 } 1485 1486 /// 1487 @nogc @trusted pure nothrow 1488 @property 1489 ref inout(V) front() inout { 1490 return _keyValueRange.front.value; 1491 } 1492 1493 /// 1494 @nogc @safe pure nothrow 1495 void popFront() { 1496 _keyValueRange.popFront(); 1497 } 1498 } 1499 1500 /** 1501 * Produce a range through the values of a map. 1502 * 1503 * Params: 1504 * map = A map. 1505 * Returns: 1506 * A range running through the values in the map. 1507 */ 1508 @nogc @safe pure nothrow 1509 auto byValue(K, V)(auto ref HashMap!(K, V) map) { 1510 if (map.length == 0) { 1511 return ValueRange!(K, V).init; 1512 } 1513 1514 return ValueRange!(K, V)(map._bucketList); 1515 } 1516 1517 /// ditto 1518 @nogc @trusted pure nothrow 1519 auto byValue(K, V)(auto ref const(HashMap!(K, V)) map) { 1520 alias RealK = HashMapKeyType!(typeof(map)); 1521 alias RealV = HashMapValueType!(typeof(map)); 1522 1523 if (map.length == 0) { 1524 return ValueRange!(RealK, RealV).init; 1525 } 1526 1527 return ValueRange!(RealK, RealV)( 1528 cast(Entry!(RealK, RealV)[]) 1529 map._bucketList 1530 ); 1531 } 1532 1533 /// ditto 1534 @nogc @trusted pure nothrow 1535 auto byValue(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1536 alias RealK = HashMapKeyType!(typeof(map)); 1537 alias RealV = HashMapValueType!(typeof(map)); 1538 1539 if (map.length == 0) { 1540 return ValueRange!(RealK, RealV).init; 1541 } 1542 1543 return ValueRange!(RealK, RealV)( 1544 cast(Entry!(RealK, RealV)[]) 1545 map._bucketList 1546 ); 1547 } 1548 1549 unittest { 1550 HashMap!(int, string) map; 1551 1552 map[1] = "a"; 1553 map[2] = "b"; 1554 map[3] = "c"; 1555 1556 string[] valueList = []; 1557 1558 foreach(ref value; map.byValue()) { 1559 valueList ~= value; 1560 } 1561 1562 // From the way the buckets are distributed, we know we'll get this back. 1563 assert(valueList == ["a", "b", "c"]); 1564 } 1565 1566 unittest { 1567 HashMap!(string, string) mmap; 1568 const(HashMap!(string, string)) cmap; 1569 immutable(HashMap!(string, string)) imap; 1570 1571 auto mValues = mmap.byValue(); 1572 auto cValues = cmap.byValue(); 1573 auto iValues = imap.byValue(); 1574 1575 assert(is(typeof(mValues.front) == string)); 1576 assert(is(typeof(cValues.front) == const(string))); 1577 assert(is(typeof(iValues.front) == immutable(string))); 1578 } 1579 1580 unittest { 1581 const(HashMap!(int, int)) createMap() { 1582 HashMap!(int, int) map; 1583 1584 map[3] = 4; 1585 map[4] = 7; 1586 1587 return map; 1588 } 1589 1590 auto map = createMap(); 1591 auto newMap = map.dup; 1592 // Test r-values. 1593 auto thirdMap = createMap().dup(); 1594 1595 assert(map == newMap); 1596 } 1597 1598 unittest { 1599 HashMap!(int, void[0]) map; 1600 1601 auto x = map.dup; 1602 }