| ... | ... | @@ -99,6 +99,16 @@ pub fn ArrayHashMap( |
| 99 | 99 | }; |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | /// `ArrayHashMap` takes ownership of the passed in array list. The array list must have |
| 103 | /// been allocated with `allocator`. |
| 104 | /// Deinitialize with `deinit`. |
| 105 | pub fn fromOwnedArrayList(allocator: *Allocator, entries: std.ArrayListUnmanaged(Entry)) !Self { |
| 106 | return Self{ |
| 107 | .unmanaged = try Unmanaged.fromOwnedArrayList(allocator, entries), |
| 108 | .allocator = allocator, |
| 109 | }; |
| 110 | } |
| 111 | |
| 102 | 112 | pub fn deinit(self: *Self) void { |
| 103 | 113 | self.unmanaged.deinit(self.allocator); |
| 104 | 114 | self.* = undefined; |
| ... | ... | @@ -214,9 +224,19 @@ pub fn ArrayHashMap( |
| 214 | 224 | } |
| 215 | 225 | |
| 216 | 226 | /// If there is an `Entry` with a matching key, it is deleted from |
| 217 | | /// the hash map, and then returned from this function. |
| 218 | | pub fn remove(self: *Self, key: K) ?Entry { |
| 219 | | return self.unmanaged.remove(key); |
| 227 | /// the hash map, and then returned from this function. The entry is |
| 228 | /// removed from the underlying array by swapping it with the last |
| 229 | /// element. |
| 230 | pub fn swapRemove(self: *Self, key: K) ?Entry { |
| 231 | return self.unmanaged.swapRemove(key); |
| 232 | } |
| 233 | |
| 234 | /// If there is an `Entry` with a matching key, it is deleted from |
| 235 | /// the hash map, and then returned from this function. The entry is |
| 236 | /// removed from the underlying array by shifting all elements forward |
| 237 | /// thereby maintaining the current ordering. |
| 238 | pub fn orderedRemove(self: *Self, key: K) ?Entry { |
| 239 | return self.unmanaged.orderedRemove(key); |
| 220 | 240 | } |
| 221 | 241 | |
| 222 | 242 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, |
| ... | ... | @@ -233,6 +253,29 @@ pub fn ArrayHashMap( |
| 233 | 253 | var other = try self.unmanaged.clone(self.allocator); |
| 234 | 254 | return other.promote(self.allocator); |
| 235 | 255 | } |
| 256 | |
| 257 | /// Rebuilds the key indexes. If the underlying entries has been modified directly, users |
| 258 | /// can call `reIndex` to update the indexes to account for these new entries. |
| 259 | pub fn reIndex(self: *Self) !void { |
| 260 | return self.unmanaged.reIndex(self.allocator); |
| 261 | } |
| 262 | |
| 263 | /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated |
| 264 | /// index entries. Keeps capacity the same. |
| 265 | pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { |
| 266 | return self.unmanaged.shrinkRetainingCapacity(new_len); |
| 267 | } |
| 268 | |
| 269 | /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated |
| 270 | /// index entries. Reduces allocated capacity. |
| 271 | pub fn shrinkAndFree(self: *Self, new_len: usize) void { |
| 272 | return self.unmanaged.shrinkAndFree(self.allocator, new_len); |
| 273 | } |
| 274 | |
| 275 | /// Removes the last inserted `Entry` in the hash map and returns it. |
| 276 | pub fn pop(self: *Self) Entry { |
| 277 | return self.unmanaged.pop(); |
| 278 | } |
| 236 | 279 | }; |
| 237 | 280 | } |
| 238 | 281 | |
| ... | ... | @@ -286,6 +329,7 @@ pub fn ArrayHashMapUnmanaged( |
| 286 | 329 | pub const GetOrPutResult = struct { |
| 287 | 330 | entry: *Entry, |
| 288 | 331 | found_existing: bool, |
| 332 | index: usize, |
| 289 | 333 | }; |
| 290 | 334 | |
| 291 | 335 | pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash); |
| ... | ... | @@ -294,6 +338,12 @@ pub fn ArrayHashMapUnmanaged( |
| 294 | 338 | |
| 295 | 339 | const linear_scan_max = 8; |
| 296 | 340 | |
| 341 | const RemovalType = enum { |
| 342 | swap, |
| 343 | ordered, |
| 344 | index_only, |
| 345 | }; |
| 346 | |
| 297 | 347 | pub fn promote(self: Self, allocator: *Allocator) Managed { |
| 298 | 348 | return .{ |
| 299 | 349 | .unmanaged = self, |
| ... | ... | @@ -301,6 +351,15 @@ pub fn ArrayHashMapUnmanaged( |
| 301 | 351 | }; |
| 302 | 352 | } |
| 303 | 353 | |
| 354 | /// `ArrayHashMapUnmanaged` takes ownership of the passed in array list. The array list must |
| 355 | /// have been allocated with `allocator`. |
| 356 | /// Deinitialize with `deinit`. |
| 357 | pub fn fromOwnedArrayList(allocator: *Allocator, entries: std.ArrayListUnmanaged(Entry)) !Self { |
| 358 | var array_hash_map = Self{ .entries = entries }; |
| 359 | try array_hash_map.reIndex(allocator); |
| 360 | return array_hash_map; |
| 361 | } |
| 362 | |
| 304 | 363 | pub fn deinit(self: *Self, allocator: *Allocator) void { |
| 305 | 364 | self.entries.deinit(allocator); |
| 306 | 365 | if (self.index_header) |header| { |
| ... | ... | @@ -323,7 +382,7 @@ pub fn ArrayHashMapUnmanaged( |
| 323 | 382 | } |
| 324 | 383 | |
| 325 | 384 | pub fn clearAndFree(self: *Self, allocator: *Allocator) void { |
| 326 | | self.entries.shrink(allocator, 0); |
| 385 | self.entries.shrinkAndFree(allocator, 0); |
| 327 | 386 | if (self.index_header) |header| { |
| 328 | 387 | header.free(allocator); |
| 329 | 388 | self.index_header = null; |
| ... | ... | @@ -343,9 +402,11 @@ pub fn ArrayHashMapUnmanaged( |
| 343 | 402 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { |
| 344 | 403 | self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { |
| 345 | 404 | // "If key exists this function cannot fail." |
| 405 | const index = self.getIndex(key) orelse return err; |
| 346 | 406 | return GetOrPutResult{ |
| 347 | | .entry = self.getEntry(key) orelse return err, |
| 407 | .entry = &self.entries.items[index], |
| 348 | 408 | .found_existing = true, |
| 409 | .index = index, |
| 349 | 410 | }; |
| 350 | 411 | }; |
| 351 | 412 | return self.getOrPutAssumeCapacity(key); |
| ... | ... | @@ -362,11 +423,12 @@ pub fn ArrayHashMapUnmanaged( |
| 362 | 423 | const header = self.index_header orelse { |
| 363 | 424 | // Linear scan. |
| 364 | 425 | const h = if (store_hash) hash(key) else {}; |
| 365 | | for (self.entries.items) |*item| { |
| 426 | for (self.entries.items) |*item, i| { |
| 366 | 427 | if (item.hash == h and eql(key, item.key)) { |
| 367 | 428 | return GetOrPutResult{ |
| 368 | 429 | .entry = item, |
| 369 | 430 | .found_existing = true, |
| 431 | .index = i, |
| 370 | 432 | }; |
| 371 | 433 | } |
| 372 | 434 | } |
| ... | ... | @@ -379,6 +441,7 @@ pub fn ArrayHashMapUnmanaged( |
| 379 | 441 | return GetOrPutResult{ |
| 380 | 442 | .entry = new_entry, |
| 381 | 443 | .found_existing = false, |
| 444 | .index = self.entries.items.len - 1, |
| 382 | 445 | }; |
| 383 | 446 | }; |
| 384 | 447 | |
| ... | ... | @@ -524,30 +587,25 @@ pub fn ArrayHashMapUnmanaged( |
| 524 | 587 | } |
| 525 | 588 | |
| 526 | 589 | /// If there is an `Entry` with a matching key, it is deleted from |
| 527 | | /// the hash map, and then returned from this function. |
| 528 | | pub fn remove(self: *Self, key: K) ?Entry { |
| 529 | | const header = self.index_header orelse { |
| 530 | | // Linear scan. |
| 531 | | const h = if (store_hash) hash(key) else {}; |
| 532 | | for (self.entries.items) |item, i| { |
| 533 | | if (item.hash == h and eql(key, item.key)) { |
| 534 | | return self.entries.swapRemove(i); |
| 535 | | } |
| 536 | | } |
| 537 | | return null; |
| 538 | | }; |
| 539 | | switch (header.capacityIndexType()) { |
| 540 | | .u8 => return self.removeInternal(key, header, u8), |
| 541 | | .u16 => return self.removeInternal(key, header, u16), |
| 542 | | .u32 => return self.removeInternal(key, header, u32), |
| 543 | | .usize => return self.removeInternal(key, header, usize), |
| 544 | | } |
| 590 | /// the hash map, and then returned from this function. The entry is |
| 591 | /// removed from the underlying array by swapping it with the last |
| 592 | /// element. |
| 593 | pub fn swapRemove(self: *Self, key: K) ?Entry { |
| 594 | return self.removeInternal(key, .swap); |
| 595 | } |
| 596 | |
| 597 | /// If there is an `Entry` with a matching key, it is deleted from |
| 598 | /// the hash map, and then returned from this function. The entry is |
| 599 | /// removed from the underlying array by shifting all elements forward |
| 600 | /// thereby maintaining the current ordering. |
| 601 | pub fn orderedRemove(self: *Self, key: K) ?Entry { |
| 602 | return self.removeInternal(key, .ordered); |
| 545 | 603 | } |
| 546 | 604 | |
| 547 | 605 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, |
| 548 | 606 | /// and discards it. |
| 549 | 607 | pub fn removeAssertDiscard(self: *Self, key: K) void { |
| 550 | | assert(self.remove(key) != null); |
| 608 | assert(self.swapRemove(key) != null); |
| 551 | 609 | } |
| 552 | 610 | |
| 553 | 611 | pub fn items(self: Self) []Entry { |
| ... | ... | @@ -566,9 +624,85 @@ pub fn ArrayHashMapUnmanaged( |
| 566 | 624 | return other; |
| 567 | 625 | } |
| 568 | 626 | |
| 569 | | fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry { |
| 627 | /// Rebuilds the key indexes. If the underlying entries has been modified directly, users |
| 628 | /// can call `reIndex` to update the indexes to account for these new entries. |
| 629 | pub fn reIndex(self: *Self, allocator: *Allocator) !void { |
| 630 | if (self.entries.capacity <= linear_scan_max) return; |
| 631 | // We're going to rebuild the index header and replace the existing one (if any). The |
| 632 | // indexes should sized such that they will be at most 60% full. |
| 633 | const needed_len = self.entries.capacity * 5 / 3; |
| 634 | const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; |
| 635 | const new_header = try IndexHeader.alloc(allocator, new_indexes_len); |
| 636 | self.insertAllEntriesIntoNewHeader(new_header); |
| 637 | if (self.index_header) |header| |
| 638 | header.free(allocator); |
| 639 | self.index_header = new_header; |
| 640 | } |
| 641 | |
| 642 | /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated |
| 643 | /// index entries. Keeps capacity the same. |
| 644 | pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { |
| 645 | // Remove index entries from the new length onwards. |
| 646 | // Explicitly choose to ONLY remove index entries and not the underlying array list |
| 647 | // entries as we're going to remove them in the subsequent shrink call. |
| 648 | var i: usize = new_len; |
| 649 | while (i < self.entries.items.len) : (i += 1) |
| 650 | _ = self.removeWithHash(self.entries.items[i].key, self.entries.items[i].hash, .index_only); |
| 651 | self.entries.shrinkRetainingCapacity(new_len); |
| 652 | } |
| 653 | |
| 654 | /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated |
| 655 | /// index entries. Reduces allocated capacity. |
| 656 | pub fn shrinkAndFree(self: *Self, allocator: *Allocator, new_len: usize) void { |
| 657 | // Remove index entries from the new length onwards. |
| 658 | // Explicitly choose to ONLY remove index entries and not the underlying array list |
| 659 | // entries as we're going to remove them in the subsequent shrink call. |
| 660 | var i: usize = new_len; |
| 661 | while (i < self.entries.items.len) : (i += 1) |
| 662 | _ = self.removeWithHash(self.entries.items[i].key, self.entries.items[i].hash, .index_only); |
| 663 | self.entries.shrinkAndFree(allocator, new_len); |
| 664 | } |
| 665 | |
| 666 | /// Removes the last inserted `Entry` in the hash map and returns it. |
| 667 | pub fn pop(self: *Self) Entry { |
| 668 | const top = self.entries.pop(); |
| 669 | _ = self.removeWithHash(top.key, top.hash, .index_only); |
| 670 | return top; |
| 671 | } |
| 672 | |
| 673 | fn removeInternal(self: *Self, key: K, comptime removal_type: RemovalType) ?Entry { |
| 674 | const key_hash = if (store_hash) hash(key) else {}; |
| 675 | return self.removeWithHash(key, key_hash, removal_type); |
| 676 | } |
| 677 | |
| 678 | fn removeWithHash(self: *Self, key: K, key_hash: Hash, comptime removal_type: RemovalType) ?Entry { |
| 679 | const header = self.index_header orelse { |
| 680 | // If we're only removing index entries and we have no index header, there's no need |
| 681 | // to continue. |
| 682 | if (removal_type == .index_only) return null; |
| 683 | // Linear scan. |
| 684 | for (self.entries.items) |item, i| { |
| 685 | if (item.hash == key_hash and eql(key, item.key)) { |
| 686 | switch (removal_type) { |
| 687 | .swap => return self.entries.swapRemove(i), |
| 688 | .ordered => return self.entries.orderedRemove(i), |
| 689 | .index_only => unreachable, |
| 690 | } |
| 691 | } |
| 692 | } |
| 693 | return null; |
| 694 | }; |
| 695 | switch (header.capacityIndexType()) { |
| 696 | .u8 => return self.removeWithIndex(key, key_hash, header, u8, removal_type), |
| 697 | .u16 => return self.removeWithIndex(key, key_hash, header, u16, removal_type), |
| 698 | .u32 => return self.removeWithIndex(key, key_hash, header, u32, removal_type), |
| 699 | .usize => return self.removeWithIndex(key, key_hash, header, usize, removal_type), |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | fn removeWithIndex(self: *Self, key: K, key_hash: Hash, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) ?Entry { |
| 570 | 704 | const indexes = header.indexes(I); |
| 571 | | const h = hash(key); |
| 705 | const h = if (store_hash) key_hash else hash(key); |
| 572 | 706 | const start_index = header.constrainIndex(h); |
| 573 | 707 | var roll_over: usize = 0; |
| 574 | 708 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { |
| ... | ... | @@ -583,11 +717,26 @@ pub fn ArrayHashMapUnmanaged( |
| 583 | 717 | if (!hash_match or !eql(key, entry.key)) |
| 584 | 718 | continue; |
| 585 | 719 | |
| 586 | | const removed_entry = self.entries.swapRemove(index.entry_index); |
| 587 | | if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { |
| 588 | | // Because of the swap remove, now we need to update the index that was |
| 589 | | // pointing to the last entry and is now pointing to this removed item slot. |
| 590 | | self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); |
| 720 | var removed_entry: ?Entry = undefined; |
| 721 | switch (removal_type) { |
| 722 | .swap => { |
| 723 | removed_entry = self.entries.swapRemove(index.entry_index); |
| 724 | if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { |
| 725 | // Because of the swap remove, now we need to update the index that was |
| 726 | // pointing to the last entry and is now pointing to this removed item slot. |
| 727 | self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); |
| 728 | } |
| 729 | }, |
| 730 | .ordered => { |
| 731 | removed_entry = self.entries.orderedRemove(index.entry_index); |
| 732 | var i: usize = index.entry_index; |
| 733 | while (i < self.entries.items.len) : (i += 1) { |
| 734 | // Because of the ordered remove, everything from the entry index onwards has |
| 735 | // been shifted forward so we'll need to update the index entries. |
| 736 | self.updateEntryIndex(header, i + 1, i, I, indexes); |
| 737 | } |
| 738 | }, |
| 739 | .index_only => removed_entry = null, |
| 591 | 740 | } |
| 592 | 741 | |
| 593 | 742 | // Now we have to shift over the following indexes. |
| ... | ... | @@ -658,6 +807,7 @@ pub fn ArrayHashMapUnmanaged( |
| 658 | 807 | return .{ |
| 659 | 808 | .found_existing = false, |
| 660 | 809 | .entry = new_entry, |
| 810 | .index = self.entries.items.len - 1, |
| 661 | 811 | }; |
| 662 | 812 | } |
| 663 | 813 | |
| ... | ... | @@ -669,6 +819,7 @@ pub fn ArrayHashMapUnmanaged( |
| 669 | 819 | return .{ |
| 670 | 820 | .found_existing = true, |
| 671 | 821 | .entry = entry, |
| 822 | .index = index.entry_index, |
| 672 | 823 | }; |
| 673 | 824 | } |
| 674 | 825 | if (index.distance_from_start_index < distance_from_start_index) { |
| ... | ... | @@ -710,6 +861,7 @@ pub fn ArrayHashMapUnmanaged( |
| 710 | 861 | return .{ |
| 711 | 862 | .found_existing = false, |
| 712 | 863 | .entry = new_entry, |
| 864 | .index = self.entries.items.len - 1, |
| 713 | 865 | }; |
| 714 | 866 | } |
| 715 | 867 | if (next_index.distance_from_start_index < distance_from_start_index) { |
| ... | ... | @@ -901,11 +1053,13 @@ test "basic hash map usage" { |
| 901 | 1053 | const gop1 = try map.getOrPut(5); |
| 902 | 1054 | testing.expect(gop1.found_existing == true); |
| 903 | 1055 | testing.expect(gop1.entry.value == 55); |
| 1056 | testing.expect(gop1.index == 4); |
| 904 | 1057 | gop1.entry.value = 77; |
| 905 | 1058 | testing.expect(map.getEntry(5).?.value == 77); |
| 906 | 1059 | |
| 907 | 1060 | const gop2 = try map.getOrPut(99); |
| 908 | 1061 | testing.expect(gop2.found_existing == false); |
| 1062 | testing.expect(gop2.index == 5); |
| 909 | 1063 | gop2.entry.value = 42; |
| 910 | 1064 | testing.expect(map.getEntry(99).?.value == 42); |
| 911 | 1065 | |
| ... | ... | @@ -919,13 +1073,32 @@ test "basic hash map usage" { |
| 919 | 1073 | testing.expect(map.getEntry(2).?.value == 22); |
| 920 | 1074 | testing.expect(map.get(2).? == 22); |
| 921 | 1075 | |
| 922 | | const rmv1 = map.remove(2); |
| 1076 | const rmv1 = map.swapRemove(2); |
| 923 | 1077 | testing.expect(rmv1.?.key == 2); |
| 924 | 1078 | testing.expect(rmv1.?.value == 22); |
| 925 | | testing.expect(map.remove(2) == null); |
| 1079 | testing.expect(map.swapRemove(2) == null); |
| 926 | 1080 | testing.expect(map.getEntry(2) == null); |
| 927 | 1081 | testing.expect(map.get(2) == null); |
| 928 | 1082 | |
| 1083 | // Since we've used `swapRemove` above, the index of this entry should remain unchanged. |
| 1084 | testing.expect(map.getIndex(100).? == 1); |
| 1085 | const gop5 = try map.getOrPut(5); |
| 1086 | testing.expect(gop5.found_existing == true); |
| 1087 | testing.expect(gop5.entry.value == 77); |
| 1088 | testing.expect(gop5.index == 4); |
| 1089 | |
| 1090 | // Whereas, if we do an `orderedRemove`, it should move the index forward one spot. |
| 1091 | const rmv2 = map.orderedRemove(100); |
| 1092 | testing.expect(rmv2.?.key == 100); |
| 1093 | testing.expect(rmv2.?.value == 41); |
| 1094 | testing.expect(map.orderedRemove(100) == null); |
| 1095 | testing.expect(map.getEntry(100) == null); |
| 1096 | testing.expect(map.get(100) == null); |
| 1097 | const gop6 = try map.getOrPut(5); |
| 1098 | testing.expect(gop6.found_existing == true); |
| 1099 | testing.expect(gop6.entry.value == 77); |
| 1100 | testing.expect(gop6.index == 3); |
| 1101 | |
| 929 | 1102 | map.removeAssertDiscard(3); |
| 930 | 1103 | } |
| 931 | 1104 | |
| ... | ... | @@ -1019,6 +1192,132 @@ test "clone" { |
| 1019 | 1192 | } |
| 1020 | 1193 | } |
| 1021 | 1194 | |
| 1195 | test "shrink" { |
| 1196 | var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); |
| 1197 | defer map.deinit(); |
| 1198 | |
| 1199 | // This test is more interesting if we insert enough entries to allocate the index header. |
| 1200 | const num_entries = 20; |
| 1201 | var i: i32 = 0; |
| 1202 | while (i < num_entries) : (i += 1) |
| 1203 | testing.expect((try map.fetchPut(i, i * 10)) == null); |
| 1204 | |
| 1205 | testing.expect(map.unmanaged.index_header != null); |
| 1206 | testing.expect(map.count() == num_entries); |
| 1207 | |
| 1208 | // Test `shrinkRetainingCapacity`. |
| 1209 | map.shrinkRetainingCapacity(17); |
| 1210 | testing.expect(map.count() == 17); |
| 1211 | testing.expect(map.capacity() == 20); |
| 1212 | i = 0; |
| 1213 | while (i < num_entries) : (i += 1) { |
| 1214 | const gop = try map.getOrPut(i); |
| 1215 | if (i < 17) { |
| 1216 | testing.expect(gop.found_existing == true); |
| 1217 | testing.expect(gop.entry.value == i * 10); |
| 1218 | } else |
| 1219 | testing.expect(gop.found_existing == false); |
| 1220 | } |
| 1221 | |
| 1222 | // Test `shrinkAndFree`. |
| 1223 | map.shrinkAndFree(15); |
| 1224 | testing.expect(map.count() == 15); |
| 1225 | testing.expect(map.capacity() == 15); |
| 1226 | i = 0; |
| 1227 | while (i < num_entries) : (i += 1) { |
| 1228 | const gop = try map.getOrPut(i); |
| 1229 | if (i < 15) { |
| 1230 | testing.expect(gop.found_existing == true); |
| 1231 | testing.expect(gop.entry.value == i * 10); |
| 1232 | } else |
| 1233 | testing.expect(gop.found_existing == false); |
| 1234 | } |
| 1235 | } |
| 1236 | |
| 1237 | test "pop" { |
| 1238 | var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); |
| 1239 | defer map.deinit(); |
| 1240 | |
| 1241 | testing.expect((try map.fetchPut(1, 11)) == null); |
| 1242 | testing.expect((try map.fetchPut(2, 22)) == null); |
| 1243 | testing.expect((try map.fetchPut(3, 33)) == null); |
| 1244 | testing.expect((try map.fetchPut(4, 44)) == null); |
| 1245 | |
| 1246 | const pop1 = map.pop(); |
| 1247 | testing.expect(pop1.key == 4 and pop1.value == 44); |
| 1248 | const pop2 = map.pop(); |
| 1249 | testing.expect(pop2.key == 3 and pop2.value == 33); |
| 1250 | const pop3 = map.pop(); |
| 1251 | testing.expect(pop3.key == 2 and pop3.value == 22); |
| 1252 | const pop4 = map.pop(); |
| 1253 | testing.expect(pop4.key == 1 and pop4.value == 11); |
| 1254 | } |
| 1255 | |
| 1256 | test "reIndex" { |
| 1257 | var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); |
| 1258 | defer map.deinit(); |
| 1259 | |
| 1260 | // Populate via the API. |
| 1261 | const num_indexed_entries = 20; |
| 1262 | var i: i32 = 0; |
| 1263 | while (i < num_indexed_entries) : (i += 1) |
| 1264 | testing.expect((try map.fetchPut(i, i * 10)) == null); |
| 1265 | |
| 1266 | // Make sure we allocated an index header. |
| 1267 | testing.expect(map.unmanaged.index_header != null); |
| 1268 | |
| 1269 | // Now write to the underlying array list directly. |
| 1270 | const num_unindexed_entries = 20; |
| 1271 | const hash = getAutoHashFn(i32); |
| 1272 | var al = &map.unmanaged.entries; |
| 1273 | while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) { |
| 1274 | try al.append(std.testing.allocator, .{ |
| 1275 | .key = i, |
| 1276 | .value = i * 10, |
| 1277 | .hash = hash(i), |
| 1278 | }); |
| 1279 | } |
| 1280 | |
| 1281 | // After reindexing, we should see everything. |
| 1282 | try map.reIndex(); |
| 1283 | i = 0; |
| 1284 | while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) { |
| 1285 | const gop = try map.getOrPut(i); |
| 1286 | testing.expect(gop.found_existing == true); |
| 1287 | testing.expect(gop.entry.value == i * 10); |
| 1288 | testing.expect(gop.index == i); |
| 1289 | } |
| 1290 | } |
| 1291 | |
| 1292 | test "fromOwnedArrayList" { |
| 1293 | comptime const array_hash_map_type = AutoArrayHashMap(i32, i32); |
| 1294 | var al = std.ArrayListUnmanaged(array_hash_map_type.Entry){}; |
| 1295 | const hash = getAutoHashFn(i32); |
| 1296 | |
| 1297 | // Populate array list. |
| 1298 | const num_entries = 20; |
| 1299 | var i: i32 = 0; |
| 1300 | while (i < num_entries) : (i += 1) { |
| 1301 | try al.append(std.testing.allocator, .{ |
| 1302 | .key = i, |
| 1303 | .value = i * 10, |
| 1304 | .hash = hash(i), |
| 1305 | }); |
| 1306 | } |
| 1307 | |
| 1308 | // Now instantiate using `fromOwnedArrayList`. |
| 1309 | var map = try array_hash_map_type.fromOwnedArrayList(std.testing.allocator, al); |
| 1310 | defer map.deinit(); |
| 1311 | |
| 1312 | i = 0; |
| 1313 | while (i < num_entries) : (i += 1) { |
| 1314 | const gop = try map.getOrPut(i); |
| 1315 | testing.expect(gop.found_existing == true); |
| 1316 | testing.expect(gop.entry.value == i * 10); |
| 1317 | testing.expect(gop.index == i); |
| 1318 | } |
| 1319 | } |
| 1320 | |
| 1022 | 1321 | pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { |
| 1023 | 1322 | return struct { |
| 1024 | 1323 | fn hash(key: K) u32 { |