| ... | @@ -99,6 +99,16 @@ pub fn ArrayHashMap( | ... | @@ -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 | pub fn deinit(self: *Self) void { | 112 | pub fn deinit(self: *Self) void { |
| 103 | self.unmanaged.deinit(self.allocator); | 113 | self.unmanaged.deinit(self.allocator); |
| 104 | self.* = undefined; | 114 | self.* = undefined; |
| ... | @@ -214,9 +224,19 @@ pub fn ArrayHashMap( | ... | @@ -214,9 +224,19 @@ pub fn ArrayHashMap( |
| 214 | } | 224 | } |
| 215 | | 225 | |
| 216 | /// If there is an `Entry` with a matching key, it is deleted from | 226 | /// If there is an `Entry` with a matching key, it is deleted from |
| 217 | /// the hash map, and then returned from this function. | 227 | /// the hash map, and then returned from this function. The entry is |
| 218 | pub fn remove(self: *Self, key: K) ?Entry { | 228 | /// removed from the underlying array by swapping it with the last |
| 219 | return self.unmanaged.remove(key); | 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 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | 242 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, |
| ... | @@ -233,6 +253,29 @@ pub fn ArrayHashMap( | ... | @@ -233,6 +253,29 @@ pub fn ArrayHashMap( |
| 233 | var other = try self.unmanaged.clone(self.allocator); | 253 | var other = try self.unmanaged.clone(self.allocator); |
| 234 | return other.promote(self.allocator); | 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,6 +329,7 @@ pub fn ArrayHashMapUnmanaged( |
| 286 | pub const GetOrPutResult = struct { | 329 | pub const GetOrPutResult = struct { |
| 287 | entry: *Entry, | 330 | entry: *Entry, |
| 288 | found_existing: bool, | 331 | found_existing: bool, |
| | 332 | index: usize, |
| 289 | }; | 333 | }; |
| 290 | | 334 | |
| 291 | pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash); | 335 | pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash); |
| ... | @@ -294,6 +338,12 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -294,6 +338,12 @@ pub fn ArrayHashMapUnmanaged( |
| 294 | | 338 | |
| 295 | const linear_scan_max = 8; | 339 | const linear_scan_max = 8; |
| 296 | | 340 | |
| | 341 | const RemovalType = enum { |
| | 342 | swap, |
| | 343 | ordered, |
| | 344 | index_only, |
| | 345 | }; |
| | 346 | |
| 297 | pub fn promote(self: Self, allocator: *Allocator) Managed { | 347 | pub fn promote(self: Self, allocator: *Allocator) Managed { |
| 298 | return .{ | 348 | return .{ |
| 299 | .unmanaged = self, | 349 | .unmanaged = self, |
| ... | @@ -301,6 +351,15 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -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 | pub fn deinit(self: *Self, allocator: *Allocator) void { | 363 | pub fn deinit(self: *Self, allocator: *Allocator) void { |
| 305 | self.entries.deinit(allocator); | 364 | self.entries.deinit(allocator); |
| 306 | if (self.index_header) |header| { | 365 | if (self.index_header) |header| { |
| ... | @@ -343,9 +402,11 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -343,9 +402,11 @@ pub fn ArrayHashMapUnmanaged( |
| 343 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { | 402 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { |
| 344 | self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { | 403 | self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { |
| 345 | // "If key exists this function cannot fail." | 404 | // "If key exists this function cannot fail." |
| | 405 | const index = self.getIndex(key) orelse return err; |
| 346 | return GetOrPutResult{ | 406 | return GetOrPutResult{ |
| 347 | .entry = self.getEntry(key) orelse return err, | 407 | .entry = &self.entries.items[index], |
| 348 | .found_existing = true, | 408 | .found_existing = true, |
| | 409 | .index = index, |
| 349 | }; | 410 | }; |
| 350 | }; | 411 | }; |
| 351 | return self.getOrPutAssumeCapacity(key); | 412 | return self.getOrPutAssumeCapacity(key); |
| ... | @@ -362,11 +423,12 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -362,11 +423,12 @@ pub fn ArrayHashMapUnmanaged( |
| 362 | const header = self.index_header orelse { | 423 | const header = self.index_header orelse { |
| 363 | // Linear scan. | 424 | // Linear scan. |
| 364 | const h = if (store_hash) hash(key) else {}; | 425 | const h = if (store_hash) hash(key) else {}; |
| 365 | for (self.entries.items) |*item| { | 426 | for (self.entries.items) |*item, i| { |
| 366 | if (item.hash == h and eql(key, item.key)) { | 427 | if (item.hash == h and eql(key, item.key)) { |
| 367 | return GetOrPutResult{ | 428 | return GetOrPutResult{ |
| 368 | .entry = item, | 429 | .entry = item, |
| 369 | .found_existing = true, | 430 | .found_existing = true, |
| | 431 | .index = i, |
| 370 | }; | 432 | }; |
| 371 | } | 433 | } |
| 372 | } | 434 | } |
| ... | @@ -379,6 +441,7 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -379,6 +441,7 @@ pub fn ArrayHashMapUnmanaged( |
| 379 | return GetOrPutResult{ | 441 | return GetOrPutResult{ |
| 380 | .entry = new_entry, | 442 | .entry = new_entry, |
| 381 | .found_existing = false, | 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,30 +587,25 @@ pub fn ArrayHashMapUnmanaged( |
| 524 | } | 587 | } |
| 525 | | 588 | |
| 526 | /// If there is an `Entry` with a matching key, it is deleted from | 589 | /// If there is an `Entry` with a matching key, it is deleted from |
| 527 | /// the hash map, and then returned from this function. | 590 | /// the hash map, and then returned from this function. The entry is |
| 528 | pub fn remove(self: *Self, key: K) ?Entry { | 591 | /// removed from the underlying array by swapping it with the last |
| 529 | const header = self.index_header orelse { | 592 | /// element. |
| 530 | // Linear scan. | 593 | pub fn swapRemove(self: *Self, key: K) ?Entry { |
| 531 | const h = if (store_hash) hash(key) else {}; | 594 | return self.removeInternal(key, .swap); |
| 532 | for (self.entries.items) |item, i| { | 595 | } |
| 533 | if (item.hash == h and eql(key, item.key)) { | 596 | |
| 534 | return self.entries.swapRemove(i); | 597 | /// If there is an `Entry` with a matching key, it is deleted from |
| 535 | } | 598 | /// the hash map, and then returned from this function. The entry is |
| 536 | } | 599 | /// removed from the underlying array by shifting all elements forward |
| 537 | return null; | 600 | /// thereby maintaining the current ordering. |
| 538 | }; | 601 | pub fn orderedRemove(self: *Self, key: K) ?Entry { |
| 539 | switch (header.capacityIndexType()) { | 602 | return self.removeInternal(key, .ordered); |
| 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 | } | | |
| 545 | } | 603 | } |
| 546 | | 604 | |
| 547 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | 605 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, |
| 548 | /// and discards it. | 606 | /// and discards it. |
| 549 | pub fn removeAssertDiscard(self: *Self, key: K) void { | 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 | pub fn items(self: Self) []Entry { | 611 | pub fn items(self: Self) []Entry { |
| ... | @@ -566,9 +624,85 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -566,9 +624,85 @@ pub fn ArrayHashMapUnmanaged( |
| 566 | return other; | 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 | const indexes = header.indexes(I); | 704 | const indexes = header.indexes(I); |
| 571 | const h = hash(key); | 705 | const h = if (store_hash) key_hash else hash(key); |
| 572 | const start_index = header.constrainIndex(h); | 706 | const start_index = header.constrainIndex(h); |
| 573 | var roll_over: usize = 0; | 707 | var roll_over: usize = 0; |
| 574 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | 708 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { |
| ... | @@ -583,11 +717,26 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -583,11 +717,26 @@ pub fn ArrayHashMapUnmanaged( |
| 583 | if (!hash_match or !eql(key, entry.key)) | 717 | if (!hash_match or !eql(key, entry.key)) |
| 584 | continue; | 718 | continue; |
| 585 | | 719 | |
| 586 | const removed_entry = self.entries.swapRemove(index.entry_index); | 720 | var removed_entry: ?Entry = undefined; |
| 587 | if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { | 721 | switch (removal_type) { |
| 588 | // Because of the swap remove, now we need to update the index that was | 722 | .swap => { |
| 589 | // pointing to the last entry and is now pointing to this removed item slot. | 723 | removed_entry = self.entries.swapRemove(index.entry_index); |
| 590 | self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); | 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 | // Now we have to shift over the following indexes. | 742 | // Now we have to shift over the following indexes. |
| ... | @@ -658,6 +807,7 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -658,6 +807,7 @@ pub fn ArrayHashMapUnmanaged( |
| 658 | return .{ | 807 | return .{ |
| 659 | .found_existing = false, | 808 | .found_existing = false, |
| 660 | .entry = new_entry, | 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,6 +819,7 @@ pub fn ArrayHashMapUnmanaged( |
| 669 | return .{ | 819 | return .{ |
| 670 | .found_existing = true, | 820 | .found_existing = true, |
| 671 | .entry = entry, | 821 | .entry = entry, |
| | 822 | .index = index.entry_index, |
| 672 | }; | 823 | }; |
| 673 | } | 824 | } |
| 674 | if (index.distance_from_start_index < distance_from_start_index) { | 825 | if (index.distance_from_start_index < distance_from_start_index) { |
| ... | @@ -710,6 +861,7 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -710,6 +861,7 @@ pub fn ArrayHashMapUnmanaged( |
| 710 | return .{ | 861 | return .{ |
| 711 | .found_existing = false, | 862 | .found_existing = false, |
| 712 | .entry = new_entry, | 863 | .entry = new_entry, |
| | 864 | .index = self.entries.items.len - 1, |
| 713 | }; | 865 | }; |
| 714 | } | 866 | } |
| 715 | if (next_index.distance_from_start_index < distance_from_start_index) { | 867 | if (next_index.distance_from_start_index < distance_from_start_index) { |
| ... | @@ -901,11 +1053,13 @@ test "basic hash map usage" { | ... | @@ -901,11 +1053,13 @@ test "basic hash map usage" { |
| 901 | const gop1 = try map.getOrPut(5); | 1053 | const gop1 = try map.getOrPut(5); |
| 902 | testing.expect(gop1.found_existing == true); | 1054 | testing.expect(gop1.found_existing == true); |
| 903 | testing.expect(gop1.entry.value == 55); | 1055 | testing.expect(gop1.entry.value == 55); |
| | 1056 | testing.expect(gop1.index == 4); |
| 904 | gop1.entry.value = 77; | 1057 | gop1.entry.value = 77; |
| 905 | testing.expect(map.getEntry(5).?.value == 77); | 1058 | testing.expect(map.getEntry(5).?.value == 77); |
| 906 | | 1059 | |
| 907 | const gop2 = try map.getOrPut(99); | 1060 | const gop2 = try map.getOrPut(99); |
| 908 | testing.expect(gop2.found_existing == false); | 1061 | testing.expect(gop2.found_existing == false); |
| | 1062 | testing.expect(gop2.index == 5); |
| 909 | gop2.entry.value = 42; | 1063 | gop2.entry.value = 42; |
| 910 | testing.expect(map.getEntry(99).?.value == 42); | 1064 | testing.expect(map.getEntry(99).?.value == 42); |
| 911 | | 1065 | |
| ... | @@ -919,13 +1073,32 @@ test "basic hash map usage" { | ... | @@ -919,13 +1073,32 @@ test "basic hash map usage" { |
| 919 | testing.expect(map.getEntry(2).?.value == 22); | 1073 | testing.expect(map.getEntry(2).?.value == 22); |
| 920 | testing.expect(map.get(2).? == 22); | 1074 | testing.expect(map.get(2).? == 22); |
| 921 | | 1075 | |
| 922 | const rmv1 = map.remove(2); | 1076 | const rmv1 = map.swapRemove(2); |
| 923 | testing.expect(rmv1.?.key == 2); | 1077 | testing.expect(rmv1.?.key == 2); |
| 924 | testing.expect(rmv1.?.value == 22); | 1078 | testing.expect(rmv1.?.value == 22); |
| 925 | testing.expect(map.remove(2) == null); | 1079 | testing.expect(map.swapRemove(2) == null); |
| 926 | testing.expect(map.getEntry(2) == null); | 1080 | testing.expect(map.getEntry(2) == null); |
| 927 | testing.expect(map.get(2) == null); | 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 | map.removeAssertDiscard(3); | 1102 | map.removeAssertDiscard(3); |
| 930 | } | 1103 | } |
| 931 | | 1104 | |
| ... | @@ -1019,6 +1192,132 @@ test "clone" { | ... | @@ -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 | pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { | 1321 | pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { |
| 1023 | return struct { | 1322 | return struct { |
| 1024 | fn hash(key: K) u32 { | 1323 | fn hash(key: K) u32 { |