authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2023-09-02 17:00:20-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-09-02 17:00:20-04:00
log8b74eae9c602433f5f35d30929b455447a3ce78b
treed9b7071193554ca90466ef071a31c3e97fd49b5e
parentda56727e6a50286992b025ce8ef39bd3dc88c630
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.ArrayHashMap.reIndex also recomputes hashes (#17054)


1 files changed, 57 insertions(+), 29 deletions(-)

lib/std/array_hash_map.zig+57-29
......@@ -154,13 +154,16 @@ pub fn ArrayHashMap(
154154 return self.unmanaged.count();
155155 }
156156
157 /// Returns the backing array of keys in this map.
158 /// Modifying the map may invalidate this array.
157 /// Returns the backing array of keys in this map. Modifying the map may
158 /// invalidate this array. Modifying this array in a way that changes
159 /// key hashes or key equality puts the map into an unusable state until
160 /// `reIndex` is called.
159161 pub fn keys(self: Self) []K {
160162 return self.unmanaged.keys();
161163 }
162 /// Returns the backing array of values in this map.
163 /// Modifying the map may invalidate this array.
164 /// Returns the backing array of values in this map. Modifying the map
165 /// may invalidate this array. It is permitted to modify the values in
166 /// this array.
164167 pub fn values(self: Self) []V {
165168 return self.unmanaged.values();
166169 }
......@@ -407,8 +410,16 @@ pub fn ArrayHashMap(
407410 return result;
408411 }
409412
410 /// Rebuilds the key indexes. If the underlying entries has been modified directly, users
411 /// can call `reIndex` to update the indexes to account for these new entries.
413 /// Recomputes stored hashes and rebuilds the key indexes. If the
414 /// underlying keys have been modified directly, call this method to
415 /// recompute the denormalized metadata necessary for the operation of
416 /// the methods of this map that lookup entries by key.
417 ///
418 /// One use case for this is directly calling `entries.resize()` to grow
419 /// the underlying storage, and then setting the `keys` and `values`
420 /// directly without going through the methods of this map.
421 ///
422 /// The time complexity of this operation is O(n).
412423 pub fn reIndex(self: *Self) !void {
413424 return self.unmanaged.reIndexContext(self.allocator, self.ctx);
414425 }
......@@ -477,6 +488,7 @@ pub fn ArrayHashMapUnmanaged(
477488) type {
478489 return struct {
479490 /// It is permitted to access this field directly.
491 /// After any modification to the keys, consider calling `reIndex`.
480492 entries: DataList = .{},
481493
482494 /// When entries length is less than `linear_scan_max`, this remains `null`.
......@@ -599,13 +611,16 @@ pub fn ArrayHashMapUnmanaged(
599611 return self.entries.len;
600612 }
601613
602 /// Returns the backing array of keys in this map.
603 /// Modifying the map may invalidate this array.
614 /// Returns the backing array of keys in this map. Modifying the map may
615 /// invalidate this array. Modifying this array in a way that changes
616 /// key hashes or key equality puts the map into an unusable state until
617 /// `reIndex` is called.
604618 pub fn keys(self: Self) []K {
605619 return self.entries.items(.key);
606620 }
607 /// Returns the backing array of values in this map.
608 /// Modifying the map may invalidate this array.
621 /// Returns the backing array of values in this map. Modifying the map
622 /// may invalidate this array. It is permitted to modify the values in
623 /// this array.
609624 pub fn values(self: Self) []V {
610625 return self.entries.items(.value);
611626 }
......@@ -1175,8 +1190,16 @@ pub fn ArrayHashMapUnmanaged(
11751190 return result;
11761191 }
11771192
1178 /// Rebuilds the key indexes. If the underlying entries has been modified directly, users
1179 /// can call `reIndex` to update the indexes to account for these new entries.
1193 /// Recomputes stored hashes and rebuilds the key indexes. If the
1194 /// underlying keys have been modified directly, call this method to
1195 /// recompute the denormalized metadata necessary for the operation of
1196 /// the methods of this map that lookup entries by key.
1197 ///
1198 /// One use case for this is directly calling `entries.resize()` to grow
1199 /// the underlying storage, and then setting the `keys` and `values`
1200 /// directly without going through the methods of this map.
1201 ///
1202 /// The time complexity of this operation is O(n).
11801203 pub fn reIndex(self: *Self, allocator: Allocator) !void {
11811204 if (@sizeOf(ByIndexContext) != 0)
11821205 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call reIndexContext instead.");
......@@ -1184,14 +1207,23 @@ pub fn ArrayHashMapUnmanaged(
11841207 }
11851208
11861209 pub fn reIndexContext(self: *Self, allocator: Allocator, ctx: Context) !void {
1187 if (self.entries.capacity <= linear_scan_max) return;
1188 // We're going to rebuild the index header and replace the existing one (if any). The
1189 // indexes should sized such that they will be at most 60% full.
1190 const bit_index = try IndexHeader.findBitIndex(self.entries.capacity);
1191 const new_header = try IndexHeader.alloc(allocator, bit_index);
1192 if (self.index_header) |header| header.free(allocator);
1193 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header);
1194 self.index_header = new_header;
1210 // Recompute all hashes.
1211 if (store_hash) {
1212 for (self.keys(), self.entries.items(.hash)) |key, *hash| {
1213 const h = checkedHash(ctx, key);
1214 hash.* = h;
1215 }
1216 }
1217 // Rebuild the index.
1218 if (self.entries.capacity > linear_scan_max) {
1219 // We're going to rebuild the index header and replace the existing one (if any). The
1220 // indexes should sized such that they will be at most 60% full.
1221 const bit_index = try IndexHeader.findBitIndex(self.entries.capacity);
1222 const new_header = try IndexHeader.alloc(allocator, bit_index);
1223 if (self.index_header) |header| header.free(allocator);
1224 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header);
1225 self.index_header = new_header;
1226 }
11951227 }
11961228
11971229 /// Sorts the entries and then rebuilds the index.
......@@ -2247,16 +2279,12 @@ test "reIndex" {
22472279 // Make sure we allocated an index header.
22482280 try testing.expect(map.unmanaged.index_header != null);
22492281
2250 // Now write to the underlying array list directly.
2282 // Now write to the arrays directly.
22512283 const num_unindexed_entries = 20;
2252 const hash = getAutoHashFn(i32, void);
2253 var al = &map.unmanaged.entries;
2254 while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) {
2255 try al.append(std.testing.allocator, .{
2256 .key = i,
2257 .value = i * 10,
2258 .hash = hash({}, i),
2259 });
2284 try map.unmanaged.entries.resize(std.testing.allocator, num_indexed_entries + num_unindexed_entries);
2285 for (map.keys()[num_indexed_entries..], map.values()[num_indexed_entries..], num_indexed_entries..) |*key, *value, j| {
2286 key.* = @intCast(j);
2287 value.* = @intCast(j * 10);
22602288 }
22612289
22622290 // After reindexing, we should see everything.