1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Hash table. 7 * 8 * Copyright: Eugene Wissner 2018-2021. 9 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 10 * Mozilla Public License, v. 2.0). 11 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 12 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/hashtable.d, 13 * tanya/container/hashtable.d) 14 */ 15 module tanya.container.hashtable; 16 17 import std.algorithm.iteration; 18 import tanya.algorithm.mutation; 19 import tanya.container.array; 20 import tanya.container.entry; 21 import tanya.hash.lookup; 22 import tanya.memory.allocator; 23 import tanya.memory.lifetime; 24 import tanya.meta.trait; 25 import tanya.meta.transform; 26 import tanya.range.primitive; 27 28 /** 29 * Bidirectional range whose element type is a tuple of a key and the 30 * respective value. 31 * 32 * Params: 33 * T = Type of the internal hash storage. 34 */ 35 struct Range(T) 36 { 37 private alias KV = CopyConstness!(T, T.Bucket.KV); 38 static if (isMutable!T) 39 { 40 private alias DataRange = T.array.Range; 41 } 42 else 43 { 44 private alias DataRange = T.array.ConstRange; 45 } 46 private DataRange dataRange; 47 48 @disable this(); 49 50 private this(DataRange dataRange) 51 { 52 while (!dataRange.empty && dataRange.front.status != BucketStatus.used) 53 { 54 dataRange.popFront(); 55 } 56 while (!dataRange.empty && dataRange.back.status != BucketStatus.used) 57 { 58 dataRange.popBack(); 59 } 60 this.dataRange = dataRange; 61 } 62 63 @property Range save() 64 { 65 return this; 66 } 67 68 @property bool empty() const 69 { 70 return this.dataRange.empty(); 71 } 72 73 void popFront() 74 in 75 { 76 assert(!empty); 77 assert(this.dataRange.front.status == BucketStatus.used); 78 } 79 out 80 { 81 assert(empty || this.dataRange.back.status == BucketStatus.used); 82 } 83 do 84 { 85 do 86 { 87 this.dataRange.popFront(); 88 } 89 while (!empty && dataRange.front.status != BucketStatus.used); 90 } 91 92 void popBack() 93 in 94 { 95 assert(!empty); 96 assert(this.dataRange.back.status == BucketStatus.used); 97 } 98 out 99 { 100 assert(empty || this.dataRange.back.status == BucketStatus.used); 101 } 102 do 103 { 104 do 105 { 106 this.dataRange.popBack(); 107 } 108 while (!empty && dataRange.back.status != BucketStatus.used); 109 } 110 111 @property ref inout(KV) front() inout 112 in 113 { 114 assert(!empty); 115 assert(this.dataRange.front.status == BucketStatus.used); 116 } 117 do 118 { 119 return this.dataRange.front.kv; 120 } 121 122 @property ref inout(KV) back() inout 123 in 124 { 125 assert(!empty); 126 assert(this.dataRange.back.status == BucketStatus.used); 127 } 128 do 129 { 130 return this.dataRange.back.kv; 131 } 132 133 Range opIndex() 134 { 135 return typeof(return)(this.dataRange[]); 136 } 137 138 Range!(const T) opIndex() const 139 { 140 return typeof(return)(this.dataRange[]); 141 } 142 } 143 144 /** 145 * Bidirectional range iterating over the key of a $(D_PSYMBOL HashTable). 146 * 147 * Params: 148 * T = Type of the internal hash storage. 149 */ 150 struct ByKey(T) 151 { 152 private alias Key = CopyConstness!(T, T.Key); 153 static if (isMutable!T) 154 { 155 private alias DataRange = T.array.Range; 156 } 157 else 158 { 159 private alias DataRange = T.array.ConstRange; 160 } 161 private DataRange dataRange; 162 163 @disable this(); 164 165 private this(DataRange dataRange) 166 { 167 while (!dataRange.empty && dataRange.front.status != BucketStatus.used) 168 { 169 dataRange.popFront(); 170 } 171 while (!dataRange.empty && dataRange.back.status != BucketStatus.used) 172 { 173 dataRange.popBack(); 174 } 175 this.dataRange = dataRange; 176 } 177 178 @property ByKey save() 179 { 180 return this; 181 } 182 183 @property bool empty() const 184 { 185 return this.dataRange.empty(); 186 } 187 188 @property void popFront() 189 in 190 { 191 assert(!empty); 192 assert(this.dataRange.front.status == BucketStatus.used); 193 } 194 out 195 { 196 assert(empty || this.dataRange.back.status == BucketStatus.used); 197 } 198 do 199 { 200 do 201 { 202 this.dataRange.popFront(); 203 } 204 while (!empty && dataRange.front.status != BucketStatus.used); 205 } 206 207 @property void popBack() 208 in 209 { 210 assert(!empty); 211 assert(this.dataRange.back.status == BucketStatus.used); 212 } 213 out 214 { 215 assert(empty || this.dataRange.back.status == BucketStatus.used); 216 } 217 do 218 { 219 do 220 { 221 this.dataRange.popBack(); 222 } 223 while (!empty && dataRange.back.status != BucketStatus.used); 224 } 225 226 @property ref inout(Key) front() inout 227 in 228 { 229 assert(!empty); 230 assert(this.dataRange.front.status == BucketStatus.used); 231 } 232 do 233 { 234 return this.dataRange.front.key; 235 } 236 237 @property ref inout(Key) back() inout 238 in 239 { 240 assert(!empty); 241 assert(this.dataRange.back.status == BucketStatus.used); 242 } 243 do 244 { 245 return this.dataRange.back.key; 246 } 247 248 ByKey opIndex() 249 { 250 return typeof(return)(this.dataRange[]); 251 } 252 253 ByKey!(const T) opIndex() const 254 { 255 return typeof(return)(this.dataRange[]); 256 } 257 } 258 259 /** 260 * Bidirectional range iterating over the key of a $(D_PSYMBOL HashTable). 261 * 262 * Params: 263 * T = Type of the internal hash storage. 264 */ 265 struct ByValue(T) 266 { 267 private alias Value = CopyConstness!(T, T.Value); 268 static if (isMutable!T) 269 { 270 private alias DataRange = T.array.Range; 271 } 272 else 273 { 274 private alias DataRange = T.array.ConstRange; 275 } 276 private DataRange dataRange; 277 278 @disable this(); 279 280 private this(DataRange dataRange) 281 { 282 while (!dataRange.empty && dataRange.front.status != BucketStatus.used) 283 { 284 dataRange.popFront(); 285 } 286 while (!dataRange.empty && dataRange.back.status != BucketStatus.used) 287 { 288 dataRange.popBack(); 289 } 290 this.dataRange = dataRange; 291 } 292 293 @property ByValue save() 294 { 295 return this; 296 } 297 298 @property bool empty() const 299 { 300 return this.dataRange.empty(); 301 } 302 303 @property void popFront() 304 in 305 { 306 assert(!empty); 307 assert(this.dataRange.front.status == BucketStatus.used); 308 } 309 out 310 { 311 assert(empty || this.dataRange.back.status == BucketStatus.used); 312 } 313 do 314 { 315 do 316 { 317 this.dataRange.popFront(); 318 } 319 while (!empty && dataRange.front.status != BucketStatus.used); 320 } 321 322 @property void popBack() 323 in 324 { 325 assert(!empty); 326 assert(this.dataRange.back.status == BucketStatus.used); 327 } 328 out 329 { 330 assert(empty || this.dataRange.back.status == BucketStatus.used); 331 } 332 do 333 { 334 do 335 { 336 this.dataRange.popBack(); 337 } 338 while (!empty && dataRange.back.status != BucketStatus.used); 339 } 340 341 @property ref inout(Value) front() inout 342 in 343 { 344 assert(!empty); 345 assert(this.dataRange.front.status == BucketStatus.used); 346 } 347 do 348 { 349 return this.dataRange.front.kv.value; 350 } 351 352 @property ref inout(Value) back() inout 353 in 354 { 355 assert(!empty); 356 assert(this.dataRange.back.status == BucketStatus.used); 357 } 358 do 359 { 360 return this.dataRange.back.kv.value; 361 } 362 363 ByValue opIndex() 364 { 365 return typeof(return)(this.dataRange[]); 366 } 367 368 ByValue!(const T) opIndex() const 369 { 370 return typeof(return)(this.dataRange[]); 371 } 372 } 373 374 /** 375 * Hash table is a data structure that stores pairs of keys and values without 376 * any particular order. 377 * 378 * This $(D_PSYMBOL HashTable) is implemented using closed hashing. Hash 379 * collisions are resolved with linear probing. 380 * 381 * $(D_PARAM Key) should be hashable with $(D_PARAM hasher). $(D_PARAM hasher) 382 * is a callable that accepts an argument of type $(D_PARAM Key) and returns a 383 * hash value for it ($(D_KEYWORD size_t)). 384 * 385 * Params: 386 * Key = Key type. 387 * Value = Value type. 388 * hasher = Hash function for $(D_PARAM Key). 389 */ 390 struct HashTable(Key, Value, alias hasher = hash) 391 if (isHashFunction!(hasher, Key)) 392 { 393 private alias HashArray = .HashArray!(hasher, Key, Value); 394 private alias Buckets = HashArray.Buckets; 395 396 private HashArray data; 397 398 /// Type of the key-value pair stored in the hash table. 399 alias KeyValue = HashArray.Bucket.KV; 400 401 /// The range types for $(D_PSYMBOL HashTable). 402 alias Range = .Range!HashArray; 403 404 /// ditto 405 alias ConstRange = .Range!(const HashArray); 406 407 /// ditto 408 alias ByKey = .ByKey!(const HashArray); 409 410 /// ditto 411 alias ByValue = .ByValue!HashArray; 412 413 /// ditto 414 alias ConstByValue = .ByValue!(const HashArray); 415 416 invariant 417 { 418 assert(this.data.lengthIndex < primes.length); 419 } 420 421 /** 422 * Constructor. 423 * 424 * Params: 425 * n = Minimum number of buckets. 426 * allocator = Allocator. 427 * 428 * Precondition: $(D_INLINECODE allocator !is null). 429 */ 430 this(size_t n, shared Allocator allocator = defaultAllocator) 431 in 432 { 433 assert(allocator !is null); 434 } 435 do 436 { 437 this(allocator); 438 this.data.rehash(n); 439 } 440 441 /// 442 @nogc nothrow pure @safe unittest 443 { 444 auto hashTable = HashTable!(string, int)(5); 445 assert(hashTable.capacity == 7); 446 } 447 448 /// ditto 449 this(shared Allocator allocator) 450 in 451 { 452 assert(allocator !is null); 453 } 454 do 455 { 456 this.data = HashArray(allocator); 457 } 458 459 /** 460 * Initializes this $(D_PARAM HashTable) from another one. 461 * 462 * If $(D_PARAM init) is passed by reference, it will be copied. 463 * If $(D_PARAM init) is passed by value, it will be moved. 464 * 465 * Params: 466 * S = Source set type. 467 * init = Source set. 468 * allocator = Allocator. 469 * 470 * Precondition: $(D_INLINECODE allocator !is null). 471 */ 472 this(S)(ref S init, shared Allocator allocator = defaultAllocator) 473 if (is(Unqual!S == HashTable)) 474 in 475 { 476 assert(allocator !is null); 477 } 478 do 479 { 480 this.data = HashArray(init.data, allocator); 481 } 482 483 /// ditto 484 this(S)(S init, shared Allocator allocator = defaultAllocator) 485 if (is(S == HashTable)) 486 in 487 { 488 assert(allocator !is null); 489 } 490 do 491 { 492 this.data.move(init.data, allocator); 493 } 494 495 /** 496 * Constructs the hash table from a forward range. 497 * 498 * Params: 499 * R = Range type. 500 * range = Forward range. 501 * allocator = Allocator. 502 * 503 * Precondition: $(D_INLINECODE allocator !is null). 504 */ 505 this(R)(scope R range, shared Allocator allocator = defaultAllocator) 506 if (isForwardRange!R && is(ElementType!R == KeyValue) && !isInfinite!R) 507 in 508 { 509 assert(allocator !is null); 510 } 511 do 512 { 513 this(allocator); 514 insert(range); 515 } 516 517 /// 518 @nogc nothrow pure @safe unittest 519 { 520 alias KeyValue = HashTable!(string, int).KeyValue; 521 522 KeyValue[2] range = [KeyValue("one", 1), KeyValue("two", 2)]; 523 auto hashTable = HashTable!(string, int)(range[]); 524 525 assert(hashTable["one"] == 1); 526 assert(hashTable["two"] == 2); 527 } 528 529 /** 530 * Initializes the hash table from a static array. 531 * 532 * Params: 533 * n = Array size. 534 * array = Static array. 535 * allocator = Allocator. 536 * 537 * Precondition: $(D_INLINECODE allocator !is null). 538 */ 539 this(size_t n)(KeyValue[n] array, 540 shared Allocator allocator = defaultAllocator) 541 in 542 { 543 assert(allocator !is null); 544 } 545 do 546 { 547 this(allocator); 548 insert(array[]); 549 } 550 551 /// 552 @nogc nothrow pure @safe unittest 553 { 554 alias KeyValue = HashTable!(string, int).KeyValue; 555 auto hashTable = HashTable!(string, int)([KeyValue("one", 1), KeyValue("two", 2)]); 556 557 assert(hashTable["one"] == 1); 558 assert(hashTable["two"] == 2); 559 } 560 561 /** 562 * Assigns another hash table. 563 * 564 * If $(D_PARAM that) is passed by reference, it will be copied. 565 * If $(D_PARAM that) is passed by value, it will be moved. 566 * 567 * Params: 568 * S = Content type. 569 * that = The value should be assigned. 570 * 571 * Returns: $(D_KEYWORD this). 572 */ 573 ref typeof(this) opAssign(S)(ref S that) 574 if (is(Unqual!S == HashTable)) 575 { 576 this.data = that.data; 577 return this; 578 } 579 580 /// ditto 581 ref typeof(this) opAssign(S)(S that) @trusted 582 if (is(S == HashTable)) 583 { 584 this.data.swap(that.data); 585 return this; 586 } 587 588 /** 589 * Returns: Used allocator. 590 * 591 * Postcondition: $(D_INLINECODE allocator !is null) 592 */ 593 @property shared(Allocator) allocator() const 594 out (allocator) 595 { 596 assert(allocator !is null); 597 } 598 do 599 { 600 return this.data.array.allocator; 601 } 602 603 /** 604 * Maximum amount of elements this $(D_PSYMBOL HashTable) can hold without 605 * resizing and rehashing. Note that it doesn't mean that the 606 * $(D_PSYMBOL Set) will hold $(I exactly) $(D_PSYMBOL capacity) elements. 607 * $(D_PSYMBOL capacity) tells the size of the container under a best-case 608 * distribution of elements. 609 * 610 * Returns: $(D_PSYMBOL HashTable) capacity. 611 */ 612 @property size_t capacity() const 613 { 614 return this.data.capacity; 615 } 616 617 /// 618 @nogc nothrow pure @safe unittest 619 { 620 HashTable!(string, int) hashTable; 621 assert(hashTable.capacity == 0); 622 623 hashTable["eight"] = 8; 624 assert(hashTable.capacity == 3); 625 } 626 627 /** 628 * Returns the number of elements in the container. 629 * 630 * Returns: The number of elements in the container. 631 */ 632 @property size_t length() const 633 { 634 return this.data.length; 635 } 636 637 /// 638 @nogc nothrow pure @safe unittest 639 { 640 HashTable!(string, int) hashTable; 641 assert(hashTable.length == 0); 642 643 hashTable["eight"] = 8; 644 assert(hashTable.length == 1); 645 } 646 647 /** 648 * Tells whether the container contains any elements. 649 * 650 * Returns: Whether the container is empty. 651 */ 652 @property bool empty() const 653 { 654 return length == 0; 655 } 656 657 /// 658 @nogc nothrow pure @safe unittest 659 { 660 HashTable!(string, int) hashTable; 661 assert(hashTable.empty); 662 hashTable["five"] = 5; 663 assert(!hashTable.empty); 664 } 665 666 /** 667 * Removes all elements. 668 */ 669 void clear() 670 { 671 this.data.clear(); 672 } 673 674 /// 675 @nogc nothrow pure @safe unittest 676 { 677 HashTable!(string, int) hashTable; 678 hashTable["five"] = 5; 679 assert(!hashTable.empty); 680 hashTable.clear(); 681 assert(hashTable.empty); 682 } 683 684 /** 685 * Returns current bucket count in the container. 686 * 687 * Bucket count equals to the number of the elements can be saved in the 688 * container in the best case scenario for key distribution, i.d. every key 689 * has a unique hash value. In a worse case the bucket count can be less 690 * than the number of elements stored in the container. 691 * 692 * Returns: Current bucket count. 693 * 694 * See_Also: $(D_PSYMBOL rehash). 695 */ 696 @property size_t bucketCount() const 697 { 698 return this.data.bucketCount; 699 } 700 701 /// The maximum number of buckets the container can have. 702 enum size_t maxBucketCount = primes[$ - 1]; 703 704 /** 705 * Inserts a new value at $(D_PARAM key) or reassigns the element if 706 * $(D_PARAM key) already exists in the hash table. 707 * 708 * Params: 709 * key = The key to insert the value at. 710 * value = The value to be inserted. 711 * 712 * Returns: Just inserted element. 713 */ 714 ref Value opIndexAssign()(auto ref Value value, auto ref Key key) 715 { 716 auto e = ((ref v) @trusted => &this.data.insert(v))(key); 717 if (e.status != BucketStatus.used) 718 { 719 static if (__traits(isRef, key)) 720 { 721 e.key = key; 722 } 723 else 724 { 725 e.moveKey(key); 726 } 727 } 728 static if (__traits(isRef, value)) 729 { 730 return e.kv.value = value; 731 } 732 else 733 { 734 return e.kv.value = move(value); 735 } 736 } 737 738 /// 739 @nogc nothrow pure @safe unittest 740 { 741 HashTable!(string, int) hashTable; 742 assert("Pachycephalosaurus" !in hashTable); 743 744 hashTable["Pachycephalosaurus"] = 6; 745 assert(hashTable.length == 1); 746 assert("Pachycephalosaurus" in hashTable); 747 748 hashTable["Pachycephalosaurus"] = 6; 749 assert(hashTable.length == 1); 750 assert("Pachycephalosaurus" in hashTable); 751 } 752 753 /** 754 * Inserts a new element in the hash table. 755 * 756 * If the element with the same key was already in the table, it reassigns 757 * it with the new value, but $(D_PSYMBOL insert) returns `0`. Otherwise 758 * `1` is returned. 759 * 760 * Params: 761 * keyValue = Key/value pair. 762 * 763 * Returns: The number of the inserted elements with a unique key. 764 */ 765 size_t insert()(ref KeyValue keyValue) 766 { 767 auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key); 768 size_t inserted; 769 if (e.status != BucketStatus.used) 770 { 771 e.key = keyValue.key; 772 inserted = 1; 773 } 774 e.kv.value = keyValue.value; 775 return inserted; 776 } 777 778 /// ditto 779 size_t insert()(KeyValue keyValue) 780 { 781 auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key); 782 size_t inserted; 783 if (e.status != BucketStatus.used) 784 { 785 e.moveKey(keyValue.key); 786 inserted = 1; 787 } 788 move(keyValue.value, e.kv.value); 789 return inserted; 790 } 791 792 /// 793 @nogc nothrow pure @safe unittest 794 { 795 HashTable!(string, int) hashTable; 796 797 assert(hashTable.insert(hashTable.KeyValue("number", 1)) == 1); 798 assert(hashTable["number"] == 1); 799 assert(hashTable.insert(hashTable.KeyValue("number", 2)) == 0); 800 assert(hashTable["number"] == 2); 801 } 802 803 /** 804 * Inserts a forward range of key/value pairs into the hash table. 805 * 806 * If some of the elements in the $(D_PARAM range) have the same key, they 807 * are reassigned but are not counted as inserted elements. So the value 808 * returned by this function will be less than the range length. 809 * 810 * Params: 811 * R = Range type. 812 * range = Forward range. 813 * 814 * Returns: The number of the inserted elements with a unique key. 815 */ 816 size_t insert(R)(scope R range) 817 if (isForwardRange!R && is(ElementType!R == KeyValue) && !isInfinite!R) 818 { 819 return fold!((acc, x) => acc + insert(x))(range, size_t.init); 820 } 821 822 /// 823 @nogc nothrow pure @safe unittest 824 { 825 HashTable!(string, int) hashTable; 826 827 hashTable.KeyValue[2] range = [ 828 hashTable.KeyValue("one", 1), 829 hashTable.KeyValue("two", 2), 830 ]; 831 832 assert(hashTable.insert(range[]) == 2); 833 assert(hashTable["one"] == 1); 834 assert(hashTable["two"] == 2); 835 } 836 837 /** 838 * Find the element with the key $(D_PARAM key). 839 * 840 * Params: 841 * T = Type comparable with the key type, used for the lookup. 842 * key = The key to be find. 843 * 844 * Returns: The value associated with $(D_PARAM key). 845 * 846 * Precondition: Element with $(D_PARAM key) is in this hash table. 847 */ 848 ref Value opIndex(T)(auto ref const T key) 849 if (ifTestable!(T, a => Key.init == a)) 850 { 851 const code = this.data.locateBucket(key); 852 853 for (auto range = this.data.array[code .. $]; !range.empty; range.popFront()) 854 { 855 if (key == range.front.key) 856 { 857 return range.front.kv.value; 858 } 859 } 860 assert(false, "Range violation"); 861 } 862 863 /// 864 @nogc nothrow pure @safe unittest 865 { 866 HashTable!(string, int) hashTable; 867 hashTable["Triceratops"] = 7; 868 assert(hashTable["Triceratops"] == 7); 869 } 870 871 /** 872 * Removes the element with the key $(D_PARAM key). 873 * 874 * The method returns the number of elements removed. Since 875 * the hash table contains only unique keys, $(D_PARAM remove) always 876 * returns `1` if an element with the $(D_PARAM key) was found, `0` 877 * otherwise. 878 * 879 * Params: 880 * key = The key to be removed. 881 * 882 * Returns: Number of the removed elements. 883 */ 884 size_t remove(Key key) 885 { 886 return this.data.remove(key); 887 } 888 889 /// 890 @nogc nothrow pure @safe unittest 891 { 892 HashTable!(string, int) hashTable; 893 hashTable["Euoplocephalus"] = 6; 894 895 assert("Euoplocephalus" in hashTable); 896 assert(hashTable.remove("Euoplocephalus") == 1); 897 assert(hashTable.remove("Euoplocephalus") == 0); 898 assert("Euoplocephalus" !in hashTable); 899 } 900 901 /** 902 * Looks for $(D_PARAM key) in this hash table. 903 * 904 * Params: 905 * T = Type comparable with the key type, used for the lookup. 906 * key = The key to look for. 907 * 908 * Returns: $(D_KEYWORD true) if $(D_PARAM key) exists in the hash table, 909 * $(D_KEYWORD false) otherwise. 910 */ 911 bool opBinaryRight(string op : "in", T)(auto ref const T key) const 912 if (ifTestable!(T, a => Key.init == a)) 913 { 914 return key in this.data; 915 } 916 917 /// 918 @nogc nothrow pure @safe unittest 919 { 920 HashTable!(string, int) hashTable; 921 922 assert("Shantungosaurus" !in hashTable); 923 hashTable["Shantungosaurus"] = 15; 924 assert("Shantungosaurus" in hashTable); 925 926 assert("Ceratopsia" !in hashTable); 927 } 928 929 /** 930 * Sets the number of buckets in the container to at least $(D_PARAM n) 931 * and rearranges all the elements according to their hash values. 932 * 933 * If $(D_PARAM n) is greater than the current $(D_PSYMBOL bucketCount) 934 * and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is 935 * forced. 936 * 937 * If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount), 938 * $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets. 939 * 940 * If $(D_PARAM n) is less than or equal to the current 941 * $(D_PSYMBOL bucketCount), the function may have no effect. 942 * 943 * Rehashing is automatically performed whenever the container needs space 944 * to insert new elements. 945 * 946 * Params: 947 * n = Minimum number of buckets. 948 */ 949 void rehash(size_t n) 950 { 951 this.data.rehash(n); 952 } 953 954 /** 955 * Returns a bidirectional range whose element type is a tuple of a key and 956 * the respective value. 957 * 958 * Returns: A bidirectional range that iterates over the container. 959 */ 960 Range opIndex() 961 { 962 return typeof(return)(this.data.array[]); 963 } 964 965 /// ditto 966 ConstRange opIndex() const 967 { 968 return typeof(return)(this.data.array[]); 969 } 970 971 /// 972 @nogc nothrow pure @safe unittest 973 { 974 HashTable!(string, int) hashTable; 975 assert(hashTable[].empty); 976 977 hashTable["Iguanodon"] = 9; 978 assert(!hashTable[].empty); 979 assert(hashTable[].front == hashTable.KeyValue("Iguanodon", 9)); 980 assert(hashTable[].back == hashTable.KeyValue("Iguanodon", 9)); 981 } 982 983 /** 984 * Returns a bidirectional range that iterats over the keys of this 985 * $(D_PSYMBOL HashTable). 986 * 987 * This function always returns a $(D_KEYWORD const) range, since changing 988 * a key of a hash table would probably change its hash value and require 989 * rehashing. 990 * 991 * Returns: $(D_KEYWORD const) bidirectional range that iterates over the 992 * keys of the container. 993 * 994 * See_Also: $(D_PSYMBOL byValue). 995 */ 996 ByKey byKey() const 997 { 998 return typeof(return)(this.data.array[]); 999 } 1000 1001 /// 1002 @nogc nothrow pure @safe unittest 1003 { 1004 HashTable!(string, int) hashTable; 1005 hashTable["one"] = 1; 1006 hashTable["two"] = 2; 1007 1008 auto byKey = hashTable.byKey(); 1009 assert(!byKey.empty); 1010 1011 assert(byKey.front == "one" || byKey.front == "two"); 1012 assert(byKey.back == "one" || byKey.back == "two"); 1013 assert(byKey.front != byKey.back); 1014 1015 byKey.popFront(); 1016 assert(byKey.front == byKey.back); 1017 1018 byKey.popBack(); 1019 assert(byKey.empty); 1020 } 1021 1022 /** 1023 * Returns a bidirectional range that iterats over the values of this 1024 * $(D_PSYMBOL HashTable). 1025 * 1026 * Returns: A bidirectional range that iterates over the values of the 1027 * container. 1028 * 1029 * See_Also: $(D_PSYMBOL byKey). 1030 */ 1031 ByValue byValue() 1032 { 1033 return typeof(return)(this.data.array[]); 1034 } 1035 1036 /// ditto 1037 ConstByValue byValue() const 1038 { 1039 return typeof(return)(this.data.array[]); 1040 } 1041 1042 /// 1043 @nogc nothrow pure @safe unittest 1044 { 1045 HashTable!(string, int) hashTable; 1046 hashTable["one"] = 1; 1047 hashTable["two"] = 2; 1048 1049 auto byValue = hashTable.byValue(); 1050 assert(!byValue.empty); 1051 1052 assert(byValue.front == 1 || byValue.front == 2); 1053 assert(byValue.back == 1 || byValue.back == 2); 1054 assert(byValue.front != byValue.back); 1055 1056 byValue.popFront(); 1057 assert(byValue.front == byValue.back); 1058 1059 byValue.popBack(); 1060 assert(byValue.empty); 1061 } 1062 } 1063 1064 @nogc nothrow pure @safe unittest 1065 { 1066 auto dinos = HashTable!(string, int)(17); 1067 assert(dinos.empty); 1068 1069 dinos["Ornithominus"] = 4; 1070 dinos["Tyrannosaurus"] = 12; 1071 dinos["Deinonychus"] = 3; 1072 dinos["Stegosaurus"] = 6; 1073 dinos["Brachiosaurus"] = 25; 1074 1075 assert(dinos.length == 5); 1076 assert(dinos["Ornithominus"] == 4); 1077 assert(dinos["Stegosaurus"] == 6); 1078 assert(dinos["Deinonychus"] == 3); 1079 assert(dinos["Tyrannosaurus"] == 12); 1080 assert(dinos["Brachiosaurus"] == 25); 1081 1082 dinos.clear(); 1083 assert(dinos.empty); 1084 }