| ... | @@ -887,17 +887,25 @@ pub fn Custom( | ... | @@ -887,17 +887,25 @@ pub fn Custom( |
| 887 | } | 887 | } |
| 888 | | 888 | |
| 889 | /// Modify an entry's key without reordering any entries. | 889 | /// Modify an entry's key without reordering any entries. |
| 890 | pub fn setKey(self: *Self, gpa: Allocator, index: usize, new_key: K) Oom!void { | 890 | pub fn setKey(self: *Self, index: usize, new_key: K) void { |
| 891 | if (@sizeOf(ByIndexContext) != 0) | 891 | if (@sizeOf(ByIndexContext) != 0) |
| 892 | @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call setKeyContext instead."); | 892 | @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call setKeyContext instead."); |
| 893 | return setKeyContext(self, gpa, index, new_key, undefined); | 893 | return setKeyContext(self, index, new_key, undefined); |
| 894 | } | 894 | } |
| 895 | | 895 | |
| 896 | pub fn setKeyContext(self: *Self, gpa: Allocator, index: usize, new_key: K, ctx: Context) Oom!void { | 896 | pub fn setKeyContext(self: *Self, index: usize, new_key: K, ctx: Context) void { |
| 897 | const key_ptr = &self.entries.items(.key)[index]; | 897 | if (self.index_header) |header| { |
| 898 | key_ptr.* = new_key; | 898 | self.removeFromIndexByIndex(index, if (store_hash) {} else ctx, header); |
| 899 | if (store_hash) self.entries.items(.hash)[index] = checkedHash(ctx, key_ptr.*); | 899 | |
| 900 | try rebuildIndex(self, gpa, undefined); | 900 | self.entries.items(.key)[index] = new_key; |
| | 901 | const h = checkedHash(ctx, new_key); |
| | 902 | if (store_hash) self.entries.items(.hash)[index] = h; |
| | 903 | |
| | 904 | insertEntryIntoNewHeader(header, h, index); |
| | 905 | } else { |
| | 906 | self.entries.items(.key)[index] = new_key; |
| | 907 | if (store_hash) self.entries.items(.hash)[index] = checkedHash(ctx, new_key); |
| | 908 | } |
| 901 | } | 909 | } |
| 902 | | 910 | |
| 903 | fn rebuildIndex(self: *Self, gpa: Allocator, ctx: Context) Oom!void { | 911 | fn rebuildIndex(self: *Self, gpa: Allocator, ctx: Context) Oom!void { |
| ... | @@ -1420,39 +1428,50 @@ pub fn Custom( | ... | @@ -1420,39 +1428,50 @@ pub fn Custom( |
| 1420 | fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, ctx: ByIndexContext, header: *IndexHeader, comptime I: type) void { | 1428 | fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, ctx: ByIndexContext, header: *IndexHeader, comptime I: type) void { |
| 1421 | const slice = self.entries.slice(); | 1429 | const slice = self.entries.slice(); |
| 1422 | const items = if (store_hash) slice.items(.hash) else slice.items(.key); | 1430 | const items = if (store_hash) slice.items(.hash) else slice.items(.key); |
| 1423 | const indexes = header.indexes(I); | | |
| 1424 | | 1431 | |
| 1425 | entry_loop: for (items, 0..) |key, i| { | 1432 | for (items, 0..) |hash_or_key, i| { |
| 1426 | const h = if (store_hash) key else checkedHash(ctx, key); | 1433 | const h = if (store_hash) hash_or_key else checkedHash(ctx, hash_or_key); |
| 1427 | const start_index = safeTruncate(usize, h); | 1434 | insertEntryIntoNewHeaderGeneric(header, h, i, I); |
| 1428 | const end_index = start_index +% indexes.len; | 1435 | } |
| 1429 | var index = start_index; | 1436 | } |
| 1430 | var entry_index = @as(I, @intCast(i)); | 1437 | |
| 1431 | var distance_from_start_index: I = 0; | 1438 | fn insertEntryIntoNewHeader(header: *IndexHeader, h: u32, i: usize) void { |
| 1432 | while (index != end_index) : ({ | 1439 | switch (header.capacityIndexType()) { |
| 1433 | index +%= 1; | 1440 | .u8 => insertEntryIntoNewHeaderGeneric(header, h, i, u8), |
| 1434 | distance_from_start_index += 1; | 1441 | .u16 => insertEntryIntoNewHeaderGeneric(header, h, i, u16), |
| 1435 | }) { | 1442 | .u32 => insertEntryIntoNewHeaderGeneric(header, h, i, u32), |
| 1436 | const slot = header.constrainIndex(index); | 1443 | } |
| 1437 | const next_index = indexes[slot]; | 1444 | } |
| 1438 | if (next_index.isEmpty()) { | 1445 | fn insertEntryIntoNewHeaderGeneric(header: *IndexHeader, h: u32, i: usize, comptime I: type) void { |
| 1439 | indexes[slot] = .{ | 1446 | const indexes = header.indexes(I); |
| 1440 | .distance_from_start_index = distance_from_start_index, | 1447 | const start_index = safeTruncate(usize, h); |
| 1441 | .entry_index = entry_index, | 1448 | const end_index = start_index +% indexes.len; |
| 1442 | }; | 1449 | var index = start_index; |
| 1443 | continue :entry_loop; | 1450 | var entry_index: I = @intCast(i); |
| 1444 | } | 1451 | var distance_from_start_index: I = 0; |
| 1445 | if (next_index.distance_from_start_index < distance_from_start_index) { | 1452 | while (index != end_index) : ({ |
| 1446 | indexes[slot] = .{ | 1453 | index +%= 1; |
| 1447 | .distance_from_start_index = distance_from_start_index, | 1454 | distance_from_start_index += 1; |
| 1448 | .entry_index = entry_index, | 1455 | }) { |
| 1449 | }; | 1456 | const slot = header.constrainIndex(index); |
| 1450 | distance_from_start_index = next_index.distance_from_start_index; | 1457 | const next_index = indexes[slot]; |
| 1451 | entry_index = next_index.entry_index; | 1458 | if (next_index.isEmpty()) { |
| 1452 | } | 1459 | indexes[slot] = .{ |
| | 1460 | .distance_from_start_index = distance_from_start_index, |
| | 1461 | .entry_index = entry_index, |
| | 1462 | }; |
| | 1463 | return; |
| | 1464 | } |
| | 1465 | if (next_index.distance_from_start_index < distance_from_start_index) { |
| | 1466 | indexes[slot] = .{ |
| | 1467 | .distance_from_start_index = distance_from_start_index, |
| | 1468 | .entry_index = entry_index, |
| | 1469 | }; |
| | 1470 | distance_from_start_index = next_index.distance_from_start_index; |
| | 1471 | entry_index = next_index.entry_index; |
| 1453 | } | 1472 | } |
| 1454 | unreachable; | | |
| 1455 | } | 1473 | } |
| | 1474 | unreachable; |
| 1456 | } | 1475 | } |
| 1457 | | 1476 | |
| 1458 | fn checkedHash(ctx: anytype, key: anytype) u32 { | 1477 | fn checkedHash(ctx: anytype, key: anytype) u32 { |
| ... | @@ -2118,7 +2137,7 @@ test "setKey storehash true" { | ... | @@ -2118,7 +2137,7 @@ test "setKey storehash true" { |
| 2118 | try map.put(gpa, 12, 34); | 2137 | try map.put(gpa, 12, 34); |
| 2119 | try map.put(gpa, 56, 78); | 2138 | try map.put(gpa, 56, 78); |
| 2120 | | 2139 | |
| 2121 | try map.setKey(gpa, 0, 42); | 2140 | map.setKey(0, 42); |
| 2122 | try testing.expectEqual(2, map.count()); | 2141 | try testing.expectEqual(2, map.count()); |
| 2123 | try testing.expectEqual(false, map.contains(12)); | 2142 | try testing.expectEqual(false, map.contains(12)); |
| 2124 | try testing.expectEqual(34, map.get(42)); | 2143 | try testing.expectEqual(34, map.get(42)); |
| ... | @@ -2134,13 +2153,49 @@ test "setKey storehash false" { | ... | @@ -2134,13 +2153,49 @@ test "setKey storehash false" { |
| 2134 | try map.put(gpa, 12, 34); | 2153 | try map.put(gpa, 12, 34); |
| 2135 | try map.put(gpa, 56, 78); | 2154 | try map.put(gpa, 56, 78); |
| 2136 | | 2155 | |
| 2137 | try map.setKey(gpa, 0, 42); | 2156 | map.setKey(0, 42); |
| 2138 | try testing.expectEqual(2, map.count()); | 2157 | try testing.expectEqual(2, map.count()); |
| 2139 | try testing.expectEqual(false, map.contains(12)); | 2158 | try testing.expectEqual(false, map.contains(12)); |
| 2140 | try testing.expectEqual(34, map.get(42)); | 2159 | try testing.expectEqual(34, map.get(42)); |
| 2141 | try testing.expectEqual(78, map.get(56)); | 2160 | try testing.expectEqual(78, map.get(56)); |
| 2142 | } | 2161 | } |
| 2143 | | 2162 | |
| | 2163 | test "setKey storehash false with index" { |
| | 2164 | const gpa = std.testing.allocator; |
| | 2165 | |
| | 2166 | const T = ArrayHashMap(usize, usize, AutoContext(usize), false); |
| | 2167 | |
| | 2168 | var map: T = .empty; |
| | 2169 | defer map.deinit(gpa); |
| | 2170 | |
| | 2171 | for (0..T.linear_scan_max + 1) |i| try map.put(gpa, i, i); |
| | 2172 | |
| | 2173 | map.setKey(0, 42); |
| | 2174 | try testing.expectEqual(T.linear_scan_max + 1, map.count()); |
| | 2175 | try testing.expectEqual(false, map.contains(0)); |
| | 2176 | try testing.expectEqual(0, map.get(42)); |
| | 2177 | |
| | 2178 | for (1..T.linear_scan_max + 1) |i| try testing.expectEqual(i, map.get(i)); |
| | 2179 | } |
| | 2180 | |
| | 2181 | test "setKey storehash true with index" { |
| | 2182 | const gpa = std.testing.allocator; |
| | 2183 | |
| | 2184 | const T = ArrayHashMap(usize, usize, AutoContext(usize), false); |
| | 2185 | |
| | 2186 | var map: ArrayHashMap(usize, usize, AutoContext(usize), true) = .empty; |
| | 2187 | defer map.deinit(gpa); |
| | 2188 | |
| | 2189 | for (0..T.linear_scan_max + 1) |i| try map.put(gpa, i, i); |
| | 2190 | |
| | 2191 | map.setKey(0, 42); |
| | 2192 | try testing.expectEqual(T.linear_scan_max + 1, map.count()); |
| | 2193 | try testing.expectEqual(false, map.contains(0)); |
| | 2194 | try testing.expectEqual(0, map.get(42)); |
| | 2195 | |
| | 2196 | for (1..T.linear_scan_max + 1) |i| try testing.expectEqual(i, map.get(i)); |
| | 2197 | } |
| | 2198 | |
| 2144 | pub fn getHashPtrAddrFn(comptime K: type, comptime Context: type) (fn (Context, K) u32) { | 2199 | pub fn getHashPtrAddrFn(comptime K: type, comptime Context: type) (fn (Context, K) u32) { |
| 2145 | return struct { | 2200 | return struct { |
| 2146 | fn hash(ctx: Context, key: K) u32 { | 2201 | fn hash(ctx: Context, key: K) u32 { |