| ... | @@ -154,13 +154,16 @@ pub fn ArrayHashMap( | ... | @@ -154,13 +154,16 @@ pub fn ArrayHashMap( |
| 154 | return self.unmanaged.count(); | 154 | return self.unmanaged.count(); |
| 155 | } | 155 | } |
| 156 | | 156 | |
| 157 | /// Returns the backing array of keys in this map. | 157 | /// Returns the backing array of keys in this map. Modifying the map may |
| 158 | /// Modifying the map may invalidate this array. | 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. |
| 159 | pub fn keys(self: Self) []K { | 161 | pub fn keys(self: Self) []K { |
| 160 | return self.unmanaged.keys(); | 162 | return self.unmanaged.keys(); |
| 161 | } | 163 | } |
| 162 | /// Returns the backing array of values in this map. | 164 | /// Returns the backing array of values in this map. Modifying the map |
| 163 | /// Modifying the map may invalidate this array. | 165 | /// may invalidate this array. It is permitted to modify the values in |
| | 166 | /// this array. |
| 164 | pub fn values(self: Self) []V { | 167 | pub fn values(self: Self) []V { |
| 165 | return self.unmanaged.values(); | 168 | return self.unmanaged.values(); |
| 166 | } | 169 | } |
| ... | @@ -407,8 +410,16 @@ pub fn ArrayHashMap( | ... | @@ -407,8 +410,16 @@ pub fn ArrayHashMap( |
| 407 | return result; | 410 | return result; |
| 408 | } | 411 | } |
| 409 | | 412 | |
| 410 | /// Rebuilds the key indexes. If the underlying entries has been modified directly, users | 413 | /// Recomputes stored hashes and rebuilds the key indexes. If the |
| 411 | /// can call `reIndex` to update the indexes to account for these new entries. | 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). |
| 412 | pub fn reIndex(self: *Self) !void { | 423 | pub fn reIndex(self: *Self) !void { |
| 413 | return self.unmanaged.reIndexContext(self.allocator, self.ctx); | 424 | return self.unmanaged.reIndexContext(self.allocator, self.ctx); |
| 414 | } | 425 | } |
| ... | @@ -477,6 +488,7 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -477,6 +488,7 @@ pub fn ArrayHashMapUnmanaged( |
| 477 | ) type { | 488 | ) type { |
| 478 | return struct { | 489 | return struct { |
| 479 | /// It is permitted to access this field directly. | 490 | /// It is permitted to access this field directly. |
| | 491 | /// After any modification to the keys, consider calling `reIndex`. |
| 480 | entries: DataList = .{}, | 492 | entries: DataList = .{}, |
| 481 | | 493 | |
| 482 | /// When entries length is less than `linear_scan_max`, this remains `null`. | 494 | /// When entries length is less than `linear_scan_max`, this remains `null`. |
| ... | @@ -599,13 +611,16 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -599,13 +611,16 @@ pub fn ArrayHashMapUnmanaged( |
| 599 | return self.entries.len; | 611 | return self.entries.len; |
| 600 | } | 612 | } |
| 601 | | 613 | |
| 602 | /// Returns the backing array of keys in this map. | 614 | /// Returns the backing array of keys in this map. Modifying the map may |
| 603 | /// Modifying the map may invalidate this array. | 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. |
| 604 | pub fn keys(self: Self) []K { | 618 | pub fn keys(self: Self) []K { |
| 605 | return self.entries.items(.key); | 619 | return self.entries.items(.key); |
| 606 | } | 620 | } |
| 607 | /// Returns the backing array of values in this map. | 621 | /// Returns the backing array of values in this map. Modifying the map |
| 608 | /// Modifying the map may invalidate this array. | 622 | /// may invalidate this array. It is permitted to modify the values in |
| | 623 | /// this array. |
| 609 | pub fn values(self: Self) []V { | 624 | pub fn values(self: Self) []V { |
| 610 | return self.entries.items(.value); | 625 | return self.entries.items(.value); |
| 611 | } | 626 | } |
| ... | @@ -1175,8 +1190,16 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1175,8 +1190,16 @@ pub fn ArrayHashMapUnmanaged( |
| 1175 | return result; | 1190 | return result; |
| 1176 | } | 1191 | } |
| 1177 | | 1192 | |
| 1178 | /// Rebuilds the key indexes. If the underlying entries has been modified directly, users | 1193 | /// Recomputes stored hashes and rebuilds the key indexes. If the |
| 1179 | /// can call `reIndex` to update the indexes to account for these new entries. | 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). |
| 1180 | pub fn reIndex(self: *Self, allocator: Allocator) !void { | 1203 | pub fn reIndex(self: *Self, allocator: Allocator) !void { |
| 1181 | if (@sizeOf(ByIndexContext) != 0) | 1204 | if (@sizeOf(ByIndexContext) != 0) |
| 1182 | @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call reIndexContext instead."); | 1205 | @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call reIndexContext instead."); |
| ... | @@ -1184,14 +1207,23 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1184,14 +1207,23 @@ pub fn ArrayHashMapUnmanaged( |
| 1184 | } | 1207 | } |
| 1185 | | 1208 | |
| 1186 | pub fn reIndexContext(self: *Self, allocator: Allocator, ctx: Context) !void { | 1209 | pub fn reIndexContext(self: *Self, allocator: Allocator, ctx: Context) !void { |
| 1187 | if (self.entries.capacity <= linear_scan_max) return; | 1210 | // Recompute all hashes. |
| 1188 | // We're going to rebuild the index header and replace the existing one (if any). The | 1211 | if (store_hash) { |
| 1189 | // indexes should sized such that they will be at most 60% full. | 1212 | for (self.keys(), self.entries.items(.hash)) |key, *hash| { |
| 1190 | const bit_index = try IndexHeader.findBitIndex(self.entries.capacity); | 1213 | const h = checkedHash(ctx, key); |
| 1191 | const new_header = try IndexHeader.alloc(allocator, bit_index); | 1214 | hash.* = h; |
| 1192 | if (self.index_header) |header| header.free(allocator); | 1215 | } |
| 1193 | self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header); | 1216 | } |
| 1194 | self.index_header = new_header; | 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 | } |
| 1195 | } | 1227 | } |
| 1196 | | 1228 | |
| 1197 | /// Sorts the entries and then rebuilds the index. | 1229 | /// Sorts the entries and then rebuilds the index. |
| ... | @@ -2247,16 +2279,12 @@ test "reIndex" { | ... | @@ -2247,16 +2279,12 @@ test "reIndex" { |
| 2247 | // Make sure we allocated an index header. | 2279 | // Make sure we allocated an index header. |
| 2248 | try testing.expect(map.unmanaged.index_header != null); | 2280 | try testing.expect(map.unmanaged.index_header != null); |
| 2249 | | 2281 | |
| 2250 | // Now write to the underlying array list directly. | 2282 | // Now write to the arrays directly. |
| 2251 | const num_unindexed_entries = 20; | 2283 | const num_unindexed_entries = 20; |
| 2252 | const hash = getAutoHashFn(i32, void); | 2284 | try map.unmanaged.entries.resize(std.testing.allocator, num_indexed_entries + num_unindexed_entries); |
| 2253 | var al = &map.unmanaged.entries; | 2285 | for (map.keys()[num_indexed_entries..], map.values()[num_indexed_entries..], num_indexed_entries..) |*key, *value, j| { |
| 2254 | while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) { | 2286 | key.* = @intCast(j); |
| 2255 | try al.append(std.testing.allocator, .{ | 2287 | value.* = @intCast(j * 10); |
| 2256 | .key = i, | | |
| 2257 | .value = i * 10, | | |
| 2258 | .hash = hash({}, i), | | |
| 2259 | }); | | |
| 2260 | } | 2288 | } |
| 2261 | | 2289 | |
| 2262 | // After reindexing, we should see everything. | 2290 | // After reindexing, we should see everything. |