| ... | ... | @@ -154,13 +154,16 @@ pub fn ArrayHashMap( |
| 154 | 154 | return self.unmanaged.count(); |
| 155 | 155 | } |
| 156 | 156 | |
| 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. |
| 159 | 161 | pub fn keys(self: Self) []K { |
| 160 | 162 | return self.unmanaged.keys(); |
| 161 | 163 | } |
| 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. |
| 164 | 167 | pub fn values(self: Self) []V { |
| 165 | 168 | return self.unmanaged.values(); |
| 166 | 169 | } |
| ... | ... | @@ -407,8 +410,16 @@ pub fn ArrayHashMap( |
| 407 | 410 | return result; |
| 408 | 411 | } |
| 409 | 412 | |
| 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). |
| 412 | 423 | pub fn reIndex(self: *Self) !void { |
| 413 | 424 | return self.unmanaged.reIndexContext(self.allocator, self.ctx); |
| 414 | 425 | } |
| ... | ... | @@ -477,6 +488,7 @@ pub fn ArrayHashMapUnmanaged( |
| 477 | 488 | ) type { |
| 478 | 489 | return struct { |
| 479 | 490 | /// It is permitted to access this field directly. |
| 491 | /// After any modification to the keys, consider calling `reIndex`. |
| 480 | 492 | entries: DataList = .{}, |
| 481 | 493 | |
| 482 | 494 | /// When entries length is less than `linear_scan_max`, this remains `null`. |
| ... | ... | @@ -599,13 +611,16 @@ pub fn ArrayHashMapUnmanaged( |
| 599 | 611 | return self.entries.len; |
| 600 | 612 | } |
| 601 | 613 | |
| 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. |
| 604 | 618 | pub fn keys(self: Self) []K { |
| 605 | 619 | return self.entries.items(.key); |
| 606 | 620 | } |
| 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. |
| 609 | 624 | pub fn values(self: Self) []V { |
| 610 | 625 | return self.entries.items(.value); |
| 611 | 626 | } |
| ... | ... | @@ -1175,8 +1190,16 @@ pub fn ArrayHashMapUnmanaged( |
| 1175 | 1190 | return result; |
| 1176 | 1191 | } |
| 1177 | 1192 | |
| 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). |
| 1180 | 1203 | pub fn reIndex(self: *Self, allocator: Allocator) !void { |
| 1181 | 1204 | if (@sizeOf(ByIndexContext) != 0) |
| 1182 | 1205 | @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call reIndexContext instead."); |
| ... | ... | @@ -1184,14 +1207,23 @@ pub fn ArrayHashMapUnmanaged( |
| 1184 | 1207 | } |
| 1185 | 1208 | |
| 1186 | 1209 | 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 | } |
| 1195 | 1227 | } |
| 1196 | 1228 | |
| 1197 | 1229 | /// Sorts the entries and then rebuilds the index. |
| ... | ... | @@ -2247,16 +2279,12 @@ test "reIndex" { |
| 2247 | 2279 | // Make sure we allocated an index header. |
| 2248 | 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 | 2283 | 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); |
| 2260 | 2288 | } |
| 2261 | 2289 | |
| 2262 | 2290 | // After reindexing, we should see everything. |