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 if (_bucketList.length == 0) 413 return null; 414 415 static if (isHashIdentical!K) { 416 size_t index = 417 bucketListSearch!(SearchFor.notDeleted, K, V) 418 (_bucketList, key); 419 } else { 420 size_t index = 421 bucketListSearch!(SearchFor.notDeleted, K, V) 422 (_bucketList, computeHash(key), key); 423 } 424 425 if (_bucketList[index]._state == EntryState.empty) { 426 return null; 427 } 428 429 return &(_bucketList[index]._value); 430 } 431 432 /** 433 * Retrieve a value from the map. 434 * 435 * If a value is not set for the given key, a RangeError will be thrown. 436 * 437 * Params: 438 * key = The key in the map. 439 * 440 * Returns: 441 * A value from the map. 442 */ 443 ref inout(V) opIndex(K key) inout { 444 static if (isHashIdentical!K) { 445 size_t index = 446 bucketListSearch!(SearchFor.notDeleted, K, V) 447 (_bucketList, key); 448 } else { 449 size_t index = 450 bucketListSearch!(SearchFor.notDeleted, K, V) 451 (_bucketList, computeHash(key), key); 452 } 453 454 assert( 455 _bucketList[index]._state != EntryState.empty, 456 "Key not found in HashMap!" 457 ); 458 459 return _bucketList[index]._value; 460 } 461 462 /** 463 * Get a value from the map, or return the given default value, which 464 * is lazy-evaluated. 465 * 466 * Params: 467 * key = The key in the map. 468 * def = A lazy default value. 469 * 470 * Returns: 471 * A value from the map, or the default value. 472 */ 473 V get(V2)(K key, lazy V2 def) const if(is(V2 : V)) { 474 if (_bucketList.length == 0) 475 return def; 476 477 static if (isHashIdentical!K) { 478 size_t index = 479 bucketListSearch!(SearchFor.notDeleted, K, V) 480 (_bucketList, key); 481 } else { 482 size_t index = 483 bucketListSearch!(SearchFor.notDeleted, K, V) 484 (_bucketList, computeHash(key), key); 485 } 486 487 if (_bucketList[index]._state == EntryState.empty) { 488 return def(); 489 } 490 491 return _bucketList[index]._value; 492 } 493 494 /** 495 * Get a value from the map, or return V.init if a value is not set for 496 * a given key. 497 * 498 * Params: 499 * key = The key in the map. 500 * 501 * Returns: 502 * A value from the map, or the default value. 503 */ 504 inout(V) get(K key) inout { 505 static if (isHashIdentical!K) { 506 size_t index = 507 bucketListSearch!(SearchFor.notDeleted, K, V) 508 (_bucketList, key); 509 } else { 510 size_t index = 511 bucketListSearch!(SearchFor.notDeleted, K, V) 512 (_bucketList, computeHash(key), key); 513 } 514 515 516 if (_bucketList[index]._state == EntryState.empty) { 517 return V.init; 518 } 519 520 return _bucketList[index]._value; 521 } 522 523 /** 524 * Get or create a value from/in the map. 525 * 526 * Given a key, and a lazy evaluated default value, 527 * attempt to retrieve a value from the map. If a value for the given 528 * key is not set, set the provided default value in the map and 529 * return that. 530 * 531 * The value will be returned by reference. 532 * 533 * Params: 534 * key = The key in the map. 535 * def = A lazy default value. 536 * 537 * Returns: 538 * A reference to the value in the map. 539 */ 540 ref V setDefault(V2)(K key, lazy V2 value) if (is(V2 : V)) { 541 static if (!isHashIdentical!K) { 542 size_t hash = computeHash(key); 543 } 544 545 if (_bucketList.length == 0) { 546 // 0 length is a special case. 547 _length = 1; 548 resize(minimumBucketListSize); 549 550 static if (isHashIdentical!K) { 551 size_t index = castHash(key) & (_bucketList.length - 1); 552 553 _bucketList.setEntry(index, key, V.init); 554 } else { 555 size_t index = hash & (_bucketList.length - 1); 556 557 _bucketList.setEntry(index, hash, key, V.init); 558 } 559 560 return _bucketList[index].value; 561 } 562 563 static if (isHashIdentical!K) { 564 size_t index = 565 bucketListSearch!(SearchFor.notDeleted, K, V) 566 (_bucketList, key); 567 } else { 568 size_t index = 569 bucketListSearch!(SearchFor.notDeleted, K, V) 570 (_bucketList, hash, key); 571 } 572 573 if (_bucketList[index]._state == EntryState.empty) { 574 // The entry is empty, so we can insert the value here. 575 static if (isHashIdentical!K) { 576 _bucketList.setEntry(index, key, value()); 577 } else { 578 _bucketList.setEntry(index, hash, key, value()); 579 } 580 581 ++_length; 582 583 if (thresholdPassed(_length, _bucketList.length)) { 584 // Resize the bucketList, as it passed the threshold. 585 resize(newBucketListSize(_bucketList.length)); 586 587 // Update the index, it has now changed. 588 static if (isHashIdentical!K) { 589 index = bucketListSearch!(SearchFor.notDeleted, K, V) 590 (_bucketList, key); 591 } else { 592 index = bucketListSearch!(SearchFor.notDeleted, K, V) 593 (_bucketList, hash, key); 594 } 595 } 596 } 597 598 // Return a reference to the value. 599 return _bucketList[index]._value; 600 } 601 602 /** 603 * Get or create a value from/in a hashmap. 604 * 605 * Given a key attempt to retrieve a value from the hashmap. 606 * If a value for the given key is not set, set the value in 607 * the associative array to the default value for the value's type. 608 * 609 * The value will be returned by reference. 610 * 611 * Params: 612 * key = The key in the map. 613 * 614 * Returns: 615 * A reference to the value in the map. 616 */ 617 ref V setDefault(K key) { 618 static if (!isHashIdentical!K) { 619 size_t hash = computeHash(key); 620 } 621 622 if (_bucketList.length == 0) { 623 // 0 length is a special case. 624 _length = 1; 625 resize(minimumBucketListSize); 626 627 static if (isHashIdentical!K) { 628 size_t index = castHash(key) & (_bucketList.length - 1); 629 630 _bucketList.setEntry(index, key, V.init); 631 } else { 632 size_t index = hash & (_bucketList.length - 1); 633 634 _bucketList.setEntry(index, hash, key, V.init); 635 } 636 637 return _bucketList[index].value; 638 } 639 640 static if (isHashIdentical!K) { 641 size_t index = 642 bucketListSearch!(SearchFor.notDeleted, K, V) 643 (_bucketList, key); 644 } else { 645 size_t index = 646 bucketListSearch!(SearchFor.notDeleted, K, V) 647 (_bucketList, hash, key); 648 } 649 650 if (_bucketList[index]._state == EntryState.empty) { 651 // The entry is empty, so we can insert the value here. 652 static if (isHashIdentical!K) { 653 _bucketList.setEntry(index, key, V.init); 654 } else { 655 _bucketList.setEntry(index, hash, key, V.init); 656 } 657 658 ++_length; 659 660 if (thresholdPassed(_length, _bucketList.length)) { 661 // Resize the bucketList, as it passed the threshold. 662 resize(newBucketListSize(_bucketList.length)); 663 664 // Update the index, it has now changed. 665 static if (isHashIdentical!K) { 666 index = bucketListSearch!(SearchFor.notDeleted, K, V) 667 (_bucketList, key); 668 } else { 669 index = bucketListSearch!(SearchFor.notDeleted, K, V) 670 (_bucketList, hash, key); 671 } 672 } 673 } 674 675 // Return a reference to the value. 676 return _bucketList[index]._value; 677 } 678 679 /** 680 * Remove a entry from the map if it is set, given a key. 681 * 682 * Params: 683 * key = The key in the map. 684 * 685 * Returns: 686 * true if a value was removed, otherwise false. 687 */ 688 bool remove(K key) { 689 if (_bucketList.length == 0) 690 return false; 691 692 static if (isHashIdentical!K) { 693 size_t index = 694 bucketListSearch!(SearchFor.any, K, V) 695 (_bucketList, key); 696 } else { 697 size_t index = 698 bucketListSearch!(SearchFor.any, K, V) 699 (_bucketList, computeHash(key), key); 700 } 701 702 if (_bucketList[index]._state == EntryState.occupied) { 703 --_length; 704 705 // Zero the value and mark the slot as 'deleted', which is 706 // treated often the same as 'empty', only we can skip over 707 // deleted values to search for more values. 708 _bucketList.zeroEntryValue(index); 709 _bucketList[index]._state = EntryState.deleted; 710 711 return true; 712 } 713 714 return false; 715 } 716 717 /** 718 * The length of the map. 719 * 720 * Returns: The number of entries in the map, in constant time. 721 */ 722 @nogc @safe pure nothrow 723 @property size_t length() const { 724 return _length; 725 } 726 727 /** 728 * Returns: True if this map is empty. 729 */ 730 @nogc @safe pure nothrow 731 @property bool empty() const { 732 return _length == 0; 733 } 734 735 /** 736 * Implement boolean conversion for a map. 737 * 738 * Returns: True if this set is not empty. 739 */ 740 @nogc @safe pure nothrow 741 bool opCast(T: bool)() const { 742 return !empty; 743 } 744 745 static if(isDupable!K && isDupable!V) { 746 /** 747 * Copy an existing map into a new mutable map. 748 * 749 * Returns: 750 * The fresh copy of the map. 751 */ 752 @safe pure nothrow 753 HashMap!(K, V) dup() const { 754 if (_length == 0) { 755 // Just return nothing special for length 0. 756 return HashMap!(K, V).init; 757 } 758 759 // Create a new map large enough to fit all of our values. 760 auto newMap = HashMap!(K, V)(_length); 761 newMap._length = _length; 762 763 copyToBucketList(newMap._bucketList); 764 765 return newMap; 766 } 767 } 768 769 770 /** 771 * Test if two maps are equal. 772 * 773 * Params: 774 * otherMap = Another map. 775 * 776 * Returns: 777 * true only if the maps are equal in length, keys, and values. 778 */ 779 bool opEquals(ref const(HashMap!(K, V)) otherMap) const { 780 if (_length != otherMap._length) { 781 return false; 782 } 783 784 foreach(ref entry; _bucketList) { 785 if (entry._state != EntryState.occupied) { 786 // Skip holes in the container. 787 continue; 788 } 789 790 static if (isHashIdentical!K) { 791 size_t index = 792 bucketListSearch!(SearchFor.notDeleted, K, V) 793 (otherMap._bucketList, entry._key); 794 } else { 795 size_t index = 796 bucketListSearch!(SearchFor.notDeleted, K, V) 797 (otherMap._bucketList, entry._hash, entry._key); 798 } 799 800 if (otherMap._bucketList[index]._state == EntryState.empty) { 801 return false; 802 } 803 } 804 805 return true; 806 } 807 808 /// ditto 809 bool opEquals(const(HashMap!(K, V)) otherMap) const { 810 return opEquals(otherMap); 811 } 812 } 813 814 template HashMapKeyType(T) { 815 alias HashMapKeyType = typeof(ElementType!(typeof(T._bucketList))._key); 816 } 817 818 template HashMapValueType(T) { 819 alias HashMapValueType = typeof(ElementType!(typeof(T._bucketList))._value); 820 } 821 822 // Test that is is not possible to create a map with a key or a value type 823 // which cannot be copy-assigned. 824 unittest { 825 struct NonCopyable { @disable this(this); } 826 827 assert(!__traits(compiles, HashMap!(NonCopyable, int))); 828 assert(__traits(compiles, HashMap!(NonCopyable*, int))); 829 assert(!__traits(compiles, HashMap!(int, NonCopyable))); 830 assert(__traits(compiles, HashMap!(int, NonCopyable*))); 831 } 832 833 // Check setting values, retrieval, removal, and lengths. 834 unittest { 835 HashMap!(int, string) map; 836 837 map[1] = "a"; 838 map[2] = "b"; 839 map[3] = "c"; 840 841 map[1] = "x"; 842 map[2] = "y"; 843 map[3] = "z"; 844 845 assert(map.length == 3); 846 847 assert(map[1] == "x"); 848 assert(map[2] == "y"); 849 assert(map[3] == "z"); 850 851 assert(map.remove(3)); 852 assert(map.remove(2)); 853 assert(map.remove(1)); 854 855 assert(!map.remove(1)); 856 857 assert(map.length == 0); 858 } 859 860 unittest { 861 HashMap!(int, string) map; 862 863 map[1] = "a"; 864 map[2] = "b"; 865 map[3] = "c"; 866 map[4] = "d"; 867 map[5] = "e"; 868 map[6] = "f"; 869 map[7] = "g"; 870 map[8] = "h"; 871 map[9] = "i"; 872 873 map[1] = "x"; 874 map[2] = "y"; 875 map[3] = "z"; 876 877 assert(map.length == 9); 878 879 assert(map[1] == "x"); 880 assert(map[2] == "y"); 881 assert(map[3] == "z"); 882 assert(map[4] == "d"); 883 assert(map[5] == "e"); 884 assert(map[6] == "f"); 885 assert(map[7] == "g"); 886 assert(map[8] == "h"); 887 assert(map[9] == "i"); 888 889 assert(map.remove(3)); 890 assert(map.remove(2)); 891 assert(map.remove(1)); 892 893 assert(!map.remove(1)); 894 895 assert(map.length == 6); 896 } 897 898 // Test the map with heavy collisions. 899 unittest { 900 struct BadHashObject { 901 int value; 902 903 this(int value) { 904 this.value = value; 905 } 906 907 @safe nothrow 908 size_t toHash() const { 909 return 0; 910 } 911 912 @nogc @safe nothrow pure 913 bool opEquals(ref const BadHashObject other) const { 914 return value == other.value; 915 } 916 } 917 918 HashMap!(BadHashObject, string) map; 919 enum size_t mapSize = 100; 920 921 foreach(num; 0 .. mapSize) { 922 map[BadHashObject(cast(int) num)] = "a"; 923 } 924 925 assert(map.length == mapSize); 926 } 927 928 // Test preallocated maps; 929 unittest { 930 auto map = HashMap!(int, string)(3); 931 932 assert(map._bucketList.length == minimumBucketListSize); 933 } 934 935 // Test the 'in' operator. 936 unittest { 937 // We'll test that our attributes work. 938 @safe pure nothrow 939 void runTest() { 940 HashMap!(int, string) map; 941 942 map[1] = "a"; 943 map[2] = "b"; 944 map[3] = "c"; 945 946 // Test that @nogc works. 947 @nogc @safe pure nothrow 948 void runNoGCPart(typeof(map) map) { 949 assert((4 in map) is null); 950 951 assert(*(1 in map) == "a"); 952 assert(*(2 in map) == "b"); 953 assert(*(3 in map) == "c"); 954 } 955 956 runNoGCPart(map); 957 } 958 959 runTest(); 960 } 961 962 // Test the map with a weird type which makes assignment harder. 963 unittest { 964 struct WeirdType { 965 // The alignment could cause memory issues so we'll check for that. 966 align(1): 967 // This immutable member means we need to use memset above to set 968 // keys or values. 969 immutable(byte)* foo = null; 970 size_t x = 3; 971 972 @nogc @safe pure nothrow 973 this(int value) { 974 x = value; 975 } 976 977 @nogc @safe pure nothrow 978 size_t toHash() const { 979 return x; 980 } 981 982 @nogc @safe pure nothrow 983 bool opEquals(ref const(WeirdType) other) const { 984 return foo == other.foo && x == other.x; 985 } 986 987 @nogc @safe pure nothrow 988 bool opEquals(const(WeirdType) other) const { 989 return opEquals(other); 990 } 991 } 992 993 @safe pure nothrow 994 void runTest() { 995 HashMap!(WeirdType, string) map; 996 997 map[WeirdType(10)] = "a"; 998 999 @nogc @safe pure nothrow 1000 void runNoGCPart(typeof(map) map) { 1001 assert(map[WeirdType(10)] == "a"); 1002 } 1003 1004 runNoGCPart(map); 1005 } 1006 1007 runTest(); 1008 } 1009 1010 // Test get with default init 1011 unittest { 1012 @safe pure nothrow 1013 void runTest() { 1014 HashMap!(int, string) map; 1015 1016 map[1] = "a"; 1017 1018 @nogc @safe pure nothrow 1019 void runNoGCPart(typeof(map) map) { 1020 assert(map.get(1) == "a"); 1021 assert(map.get(2) is null); 1022 } 1023 runNoGCPart(map); 1024 } 1025 1026 runTest(); 1027 1028 } 1029 1030 // Test length, empty, and cast(bool) 1031 unittest { 1032 @safe pure nothrow 1033 void runTest() { 1034 HashMap!(int, string) map; 1035 1036 @nogc @safe pure nothrow 1037 void runNoGCPart1(typeof(map) map) { 1038 assert(map.length == 0); 1039 assert(map.empty); 1040 1041 if (map) { 1042 assert(false, "cast(bool) failed for an empty map"); 1043 } 1044 } 1045 1046 @nogc @safe pure nothrow 1047 void runNoGCPart2(typeof(map) map) { 1048 assert(map.length == 1); 1049 assert(!map.empty); 1050 1051 if (!map) { 1052 assert(false, "cast(bool) failed for an non-empty map"); 1053 } 1054 } 1055 1056 @nogc @safe pure nothrow 1057 void runNoGCPart3(typeof(map) map) { 1058 assert(map.length == 0); 1059 assert(map.empty); 1060 1061 if (map) { 1062 assert(false, "cast(bool) failed for an empty map"); 1063 } 1064 } 1065 1066 runNoGCPart1(map); 1067 map[1] = "a"; 1068 runNoGCPart2(map); 1069 map.remove(1); 1070 runNoGCPart3(map); 1071 } 1072 1073 runTest(); 1074 } 1075 1076 // BUG: The lazy argument here cannot be made to be nothrow, @nogc, etc. 1077 // Test get with a given default. 1078 unittest { 1079 HashMap!(int, string) map; 1080 1081 map[1] = "a"; 1082 1083 assert(map.get(1, "b") == "a"); 1084 assert(map.get(2, "b") == "b"); 1085 } 1086 1087 // Test opEquals 1088 unittest { 1089 @safe pure nothrow 1090 void runTest() { 1091 HashMap!(string, string) leftMap; 1092 HashMap!(string, string) rightMap; 1093 1094 // Give the left one a bit more, and take away from it. 1095 leftMap["a"] = "1"; 1096 leftMap["b"] = "2"; 1097 leftMap["c"] = "3"; 1098 leftMap["d"] = "4"; 1099 leftMap["e"] = "5"; 1100 leftMap["f"] = "6"; 1101 leftMap["g"] = "7"; 1102 leftMap["h"] = "8"; 1103 leftMap["i"] = "9"; 1104 leftMap["j"] = "10"; 1105 1106 rightMap["a"] = "1"; 1107 rightMap["b"] = "2"; 1108 rightMap["c"] = "3"; 1109 1110 @nogc @safe pure nothrow 1111 void runNoGCPart(typeof(leftMap) leftMap, typeof(rightMap) rightMap) { 1112 // Remove the extra keys 1113 leftMap.remove("d"); 1114 leftMap.remove("e"); 1115 leftMap.remove("f"); 1116 leftMap.remove("g"); 1117 leftMap.remove("h"); 1118 leftMap.remove("i"); 1119 leftMap.remove("j"); 1120 1121 // Now the two maps should have different bucketLists, but they 1122 // should still be considered equal. 1123 assert(leftMap == rightMap); 1124 } 1125 1126 runNoGCPart(leftMap, rightMap); 1127 } 1128 1129 runTest(); 1130 } 1131 1132 // Test setDefault with default init 1133 unittest { 1134 @safe pure nothrow 1135 void runTest() { 1136 HashMap!(int, string) map; 1137 1138 map[1] = "a"; 1139 1140 // setDefault for basic types with no explicit default ought to 1141 // be nothrow. 1142 assert(map.setDefault(1) == "a"); 1143 assert(map.setDefault(2) is null); 1144 1145 assert(map.length == 2); 1146 1147 assert(map[2] is null); 1148 } 1149 1150 runTest(); 1151 } 1152 1153 // Test setDefault with a given value. 1154 unittest { 1155 HashMap!(int, string) map; 1156 1157 map[1] = "a"; 1158 1159 assert(map.setDefault(1, "b") == "a"); 1160 assert(map.setDefault(2, "b") == "b"); 1161 1162 assert(map.length == 2); 1163 1164 assert(map[2] == "b"); 1165 } 1166 1167 // Test setDefault with a given value which can be implicitly converted. 1168 unittest { 1169 HashMap!(int, long) map; 1170 1171 map[1] = 2; 1172 1173 assert(map.setDefault(1, 3) == 2); 1174 1175 int x = 4; 1176 1177 assert(map.setDefault(2, x) == 4); 1178 1179 assert(map.length == 2); 1180 1181 assert(map[2] == 4); 1182 } 1183 1184 // Test operations work with empty map (issue #3). 1185 unittest { 1186 HashMap!(int, string) emptyMap; 1187 assert(0 !in emptyMap); 1188 assert(!emptyMap.remove(0)); 1189 assert(emptyMap.get(0, "a") == "a"); 1190 } 1191 1192 /** 1193 * A range through a series of items in the map. 1194 */ 1195 struct KeyValueRange(K, V) { 1196 private: 1197 Entry!(K, V)[] _bucketList = null; 1198 public: 1199 @nogc @safe pure nothrow 1200 this(Entry!(K, V)[] bucketList) { 1201 foreach(index, ref entry; bucketList) { 1202 if (entry._state == EntryState.occupied) { 1203 // Use a slice of the bucketList starting here. 1204 _bucketList = bucketList[index .. $]; 1205 1206 return; 1207 } 1208 } 1209 } 1210 1211 @nogc @trusted pure nothrow 1212 this(const(Entry!(K, V)[]) bucketList) { 1213 this(cast(Entry!(K, V)[]) bucketList); 1214 } 1215 1216 @nogc @safe pure nothrow 1217 inout(typeof(this)) save() inout { 1218 return this; 1219 } 1220 1221 @nogc @safe pure nothrow 1222 @property 1223 bool empty() const { 1224 // We can check that the bucketList is empty to check if this range is 1225 // empty, because we will clear it after we pop the last item. 1226 return _bucketList.length == 0; 1227 } 1228 1229 @nogc @safe pure nothrow 1230 @property 1231 ref inout(Entry!(K, V)) front() inout in { 1232 assert(!empty()); 1233 } body { 1234 return _bucketList[0]; 1235 } 1236 1237 @nogc @safe pure nothrow 1238 void popFront() in { 1239 assert(!empty()); 1240 } body { 1241 foreach(index; 1 .. _bucketList.length) { 1242 if (_bucketList[index]._state == EntryState.occupied) { 1243 // Use a slice of the bucketList starting here. 1244 _bucketList = _bucketList[index .. $]; 1245 1246 return; 1247 } 1248 } 1249 1250 // Clear the bucketList if we hit the end. 1251 _bucketList = null; 1252 } 1253 } 1254 1255 /** 1256 * Produce a range through the items of a map. (A key-value pair) 1257 * 1258 * Params: 1259 * map = A map. 1260 * Returns: 1261 * A range running through the items in the map. 1262 */ 1263 @nogc @safe pure nothrow 1264 auto byKeyValue(K, V)(auto ref HashMap!(K, V) map) { 1265 if (map.length == 0) { 1266 // Empty ranges should not have to traverse the bucketList at all. 1267 return KeyValueRange!(K, V).init; 1268 } 1269 1270 return KeyValueRange!(K, V)(map._bucketList); 1271 } 1272 1273 /// ditto 1274 @nogc @trusted pure nothrow 1275 auto byKeyValue(K, V)(auto ref const(HashMap!(K, V)) map) { 1276 alias RealK = HashMapKeyType!(typeof(map)); 1277 alias RealV = HashMapValueType!(typeof(map)); 1278 1279 if (map.length == 0) { 1280 return KeyValueRange!(RealK, RealV).init; 1281 } 1282 1283 return KeyValueRange!(RealK, RealV)( 1284 cast(Entry!(RealK, RealV)[]) 1285 map._bucketList 1286 ); 1287 } 1288 1289 /// ditto 1290 @nogc @trusted pure nothrow 1291 auto byKeyValue(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1292 alias RealK = HashMapKeyType!(typeof(map)); 1293 alias RealV = HashMapValueType!(typeof(map)); 1294 1295 if (map.length == 0) { 1296 return KeyValueRange!(RealK, RealV).init; 1297 } 1298 1299 return KeyValueRange!(RealK, RealV)( 1300 cast(Entry!(RealK, RealV)[]) 1301 map._bucketList 1302 ); 1303 } 1304 1305 unittest { 1306 HashMap!(int, string) map; 1307 1308 map[1] = "a"; 1309 map[2] = "b"; 1310 map[3] = "c"; 1311 1312 int[] keyList; 1313 string[] valueList; 1314 1315 foreach(item; map.byKeyValue()) { 1316 keyList ~= item.key; 1317 valueList ~= item.value; 1318 } 1319 1320 // From the way the bucketLists are distributed, we know we'll get this back. 1321 assert(keyList == [1, 2, 3]); 1322 assert(valueList == ["a", "b", "c"]); 1323 } 1324 1325 unittest { 1326 HashMap!(string, string) mmap; 1327 const(HashMap!(string, string)) cmap; 1328 immutable(HashMap!(string, string)) imap; 1329 1330 auto mItems = mmap.byKeyValue(); 1331 auto cItems = cmap.byKeyValue(); 1332 auto iItems = imap.byKeyValue(); 1333 1334 assert(is(typeof(mItems.front.key) == string)); 1335 assert(is(typeof(cItems.front.key) == const(string))); 1336 assert(is(typeof(iItems.front.key) == immutable(string))); 1337 assert(is(typeof(mItems.front.value) == string)); 1338 assert(is(typeof(cItems.front.value) == const(string))); 1339 assert(is(typeof(iItems.front.value) == immutable(string))); 1340 } 1341 1342 // Test that the ranges can be created from r-values. 1343 unittest { 1344 auto func() { 1345 HashMap!(int, string) map; 1346 1347 map[1] = "a"; 1348 map[2] = "b"; 1349 map[3] = "c"; 1350 1351 return map; 1352 } 1353 1354 auto keyRange = func().byKey(); 1355 auto valueRange = func().byValue(); 1356 auto itemRange = func().byKeyValue(); 1357 } 1358 1359 /** 1360 * This is a range which runs through a series of keys in map. 1361 */ 1362 struct KeyRange(K, V) { 1363 private: 1364 KeyValueRange!(K, V) _keyValueRange; 1365 public: 1366 @nogc @safe pure nothrow 1367 private this()(auto ref Entry!(K, V)[] bucketList) { 1368 _keyValueRange = KeyValueRange!(K, V)(bucketList); 1369 } 1370 1371 /// 1372 @nogc @safe pure nothrow 1373 inout(typeof(this)) save() inout { 1374 return this; 1375 } 1376 1377 /// 1378 @nogc @safe pure nothrow 1379 @property 1380 bool empty() const { 1381 return _keyValueRange.empty; 1382 } 1383 1384 /// 1385 @nogc @trusted pure nothrow 1386 @property 1387 ref inout(K) front() inout { 1388 return _keyValueRange.front.key; 1389 } 1390 1391 /// 1392 @nogc @safe pure nothrow 1393 void popFront() { 1394 _keyValueRange.popFront(); 1395 } 1396 } 1397 1398 /** 1399 * Produce a range through the keys of a map. 1400 * 1401 * Params: 1402 * map = A map. 1403 * Returns: 1404 * A range running through the keys in the map. 1405 */ 1406 @nogc @safe pure nothrow 1407 auto byKey(K, V)(auto ref HashMap!(K, V) map) { 1408 if (map.length == 0) { 1409 return KeyRange!(K, V).init; 1410 } 1411 1412 return KeyRange!(K, V)(map._bucketList); 1413 } 1414 1415 /// ditto 1416 @nogc @trusted pure nothrow 1417 auto byKey(K, V)(auto ref const(HashMap!(K, V)) map) { 1418 alias RealK = HashMapKeyType!(typeof(map)); 1419 alias RealV = HashMapValueType!(typeof(map)); 1420 1421 if (map.length == 0) { 1422 return KeyRange!(RealK, RealV).init; 1423 } 1424 1425 return KeyRange!(RealK, RealV)( 1426 cast(Entry!(RealK, RealV)[]) 1427 map._bucketList 1428 ); 1429 } 1430 1431 /// ditto 1432 @nogc @trusted pure nothrow 1433 auto byKey(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1434 alias RealK = HashMapKeyType!(typeof(map)); 1435 alias RealV = HashMapValueType!(typeof(map)); 1436 1437 if (map.length == 0) { 1438 return KeyRange!(RealK, RealV).init; 1439 } 1440 1441 return KeyRange!(RealK, RealV)( 1442 cast(Entry!(RealK, RealV)[]) 1443 map._bucketList 1444 ); 1445 } 1446 1447 unittest { 1448 HashMap!(int, string) map; 1449 1450 map[1] = "a"; 1451 map[2] = "b"; 1452 map[3] = "c"; 1453 1454 int[] keyList; 1455 1456 foreach(ref key; map.byKey()) { 1457 keyList ~= key; 1458 } 1459 1460 // From the way the bucketLists are distributed, we know we'll get this back. 1461 assert(keyList == [1, 2, 3]); 1462 } 1463 1464 unittest { 1465 HashMap!(string, string) mmap; 1466 const(HashMap!(string, string)) cmap; 1467 immutable(HashMap!(string, string)) imap; 1468 1469 auto mKeys = mmap.byKey(); 1470 auto cKeys = cmap.byKey(); 1471 auto iKeys = imap.byKey(); 1472 1473 assert(is(typeof(mKeys.front) == string)); 1474 assert(is(typeof(cKeys.front) == const(string))); 1475 assert(is(typeof(iKeys.front) == immutable(string))); 1476 } 1477 1478 /** 1479 * This is a range which runs through a series of values in a map. 1480 */ 1481 struct ValueRange(K, V) { 1482 private: 1483 KeyValueRange!(K, V) _keyValueRange; 1484 public: 1485 @nogc @safe pure nothrow 1486 private this()(auto ref Entry!(K, V)[] bucketList) { 1487 _keyValueRange = KeyValueRange!(K, V)(bucketList); 1488 } 1489 1490 /// 1491 @nogc @safe pure nothrow 1492 inout(typeof(this)) save() inout { 1493 return this; 1494 } 1495 1496 /// 1497 @nogc @safe pure nothrow 1498 @property 1499 bool empty() const { 1500 return _keyValueRange.empty; 1501 } 1502 1503 /// 1504 @nogc @trusted pure nothrow 1505 @property 1506 ref inout(V) front() inout { 1507 return _keyValueRange.front.value; 1508 } 1509 1510 /// 1511 @nogc @safe pure nothrow 1512 void popFront() { 1513 _keyValueRange.popFront(); 1514 } 1515 } 1516 1517 /** 1518 * Produce a range through the values of a map. 1519 * 1520 * Params: 1521 * map = A map. 1522 * Returns: 1523 * A range running through the values in the map. 1524 */ 1525 @nogc @safe pure nothrow 1526 auto byValue(K, V)(auto ref HashMap!(K, V) map) { 1527 if (map.length == 0) { 1528 return ValueRange!(K, V).init; 1529 } 1530 1531 return ValueRange!(K, V)(map._bucketList); 1532 } 1533 1534 /// ditto 1535 @nogc @trusted pure nothrow 1536 auto byValue(K, V)(auto ref const(HashMap!(K, V)) map) { 1537 alias RealK = HashMapKeyType!(typeof(map)); 1538 alias RealV = HashMapValueType!(typeof(map)); 1539 1540 if (map.length == 0) { 1541 return ValueRange!(RealK, RealV).init; 1542 } 1543 1544 return ValueRange!(RealK, RealV)( 1545 cast(Entry!(RealK, RealV)[]) 1546 map._bucketList 1547 ); 1548 } 1549 1550 /// ditto 1551 @nogc @trusted pure nothrow 1552 auto byValue(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1553 alias RealK = HashMapKeyType!(typeof(map)); 1554 alias RealV = HashMapValueType!(typeof(map)); 1555 1556 if (map.length == 0) { 1557 return ValueRange!(RealK, RealV).init; 1558 } 1559 1560 return ValueRange!(RealK, RealV)( 1561 cast(Entry!(RealK, RealV)[]) 1562 map._bucketList 1563 ); 1564 } 1565 1566 unittest { 1567 HashMap!(int, string) map; 1568 1569 map[1] = "a"; 1570 map[2] = "b"; 1571 map[3] = "c"; 1572 1573 string[] valueList = []; 1574 1575 foreach(ref value; map.byValue()) { 1576 valueList ~= value; 1577 } 1578 1579 // From the way the buckets are distributed, we know we'll get this back. 1580 assert(valueList == ["a", "b", "c"]); 1581 } 1582 1583 unittest { 1584 HashMap!(string, string) mmap; 1585 const(HashMap!(string, string)) cmap; 1586 immutable(HashMap!(string, string)) imap; 1587 1588 auto mValues = mmap.byValue(); 1589 auto cValues = cmap.byValue(); 1590 auto iValues = imap.byValue(); 1591 1592 assert(is(typeof(mValues.front) == string)); 1593 assert(is(typeof(cValues.front) == const(string))); 1594 assert(is(typeof(iValues.front) == immutable(string))); 1595 } 1596 1597 unittest { 1598 const(HashMap!(int, int)) createMap() { 1599 HashMap!(int, int) map; 1600 1601 map[3] = 4; 1602 map[4] = 7; 1603 1604 return map; 1605 } 1606 1607 auto map = createMap(); 1608 auto newMap = map.dup; 1609 // Test r-values. 1610 auto thirdMap = createMap().dup(); 1611 1612 assert(map == newMap); 1613 } 1614 1615 unittest { 1616 HashMap!(int, void[0]) map; 1617 1618 auto x = map.dup; 1619 }