lib/std/array_hash_map.zig (117107B) - Raw
1 const std = @import("std.zig"); 2 const debug = std.debug; 3 const assert = debug.assert; 4 const testing = std.testing; 5 const math = std.math; 6 const mem = std.mem; 7 const autoHash = std.hash.autoHash; 8 const Wyhash = std.hash.Wyhash; 9 const Allocator = mem.Allocator; 10 const hash_map = @This(); 11 12 /// An `ArrayHashMap` with default hash and equal functions. 13 /// 14 /// See `AutoContext` for a description of the hash and equal implementations. 15 pub fn AutoArrayHashMap(comptime K: type, comptime V: type) type { 16 return ArrayHashMap(K, V, AutoContext(K), !autoEqlIsCheap(K)); 17 } 18 19 /// An `ArrayHashMapUnmanaged` with default hash and equal functions. 20 /// 21 /// See `AutoContext` for a description of the hash and equal implementations. 22 pub fn AutoArrayHashMapUnmanaged(comptime K: type, comptime V: type) type { 23 return ArrayHashMapUnmanaged(K, V, AutoContext(K), !autoEqlIsCheap(K)); 24 } 25 26 /// An `ArrayHashMap` with strings as keys. 27 pub fn StringArrayHashMap(comptime V: type) type { 28 return ArrayHashMap([]const u8, V, StringContext, true); 29 } 30 31 /// An `ArrayHashMapUnmanaged` with strings as keys. 32 pub fn StringArrayHashMapUnmanaged(comptime V: type) type { 33 return ArrayHashMapUnmanaged([]const u8, V, StringContext, true); 34 } 35 36 pub const StringContext = struct { 37 pub fn hash(self: @This(), s: []const u8) u32 { 38 _ = self; 39 return hashString(s); 40 } 41 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { 42 _ = self; 43 _ = b_index; 44 return eqlString(a, b); 45 } 46 }; 47 48 pub fn eqlString(a: []const u8, b: []const u8) bool { 49 return mem.eql(u8, a, b); 50 } 51 52 pub fn hashString(s: []const u8) u32 { 53 return @as(u32, @truncate(std.hash.Wyhash.hash(0, s))); 54 } 55 56 /// Deprecated in favor of `ArrayHashMapWithAllocator` (no code changes needed) 57 /// or `ArrayHashMapUnmanaged` (will need to update callsites to pass an 58 /// allocator). After Zig 0.14.0 is released, `ArrayHashMapWithAllocator` will 59 /// be removed and `ArrayHashMapUnmanaged` will be a deprecated alias. After 60 /// Zig 0.15.0 is released, the deprecated alias `ArrayHashMapUnmanaged` will 61 /// be removed. 62 pub const ArrayHashMap = ArrayHashMapWithAllocator; 63 64 /// A hash table of keys and values, each stored sequentially. 65 /// 66 /// Insertion order is preserved. In general, this data structure supports the same 67 /// operations as `std.ArrayList`. 68 /// 69 /// Deletion operations: 70 /// * `swapRemove` - O(1) 71 /// * `orderedRemove` - O(N) 72 /// 73 /// Modifying the hash map while iterating is allowed, however, one must understand 74 /// the (well defined) behavior when mixing insertions and deletions with iteration. 75 /// 76 /// See `ArrayHashMapUnmanaged` for a variant of this data structure that accepts an 77 /// `Allocator` as a parameter when needed rather than storing it. 78 pub fn ArrayHashMapWithAllocator( 79 comptime K: type, 80 comptime V: type, 81 /// A namespace that provides these two functions: 82 /// * `pub fn hash(self, K) u32` 83 /// * `pub fn eql(self, K, K, usize) bool` 84 /// 85 /// The final `usize` in the `eql` function represents the index of the key 86 /// that's already inside the map. 87 comptime Context: type, 88 /// When `false`, this data structure is biased towards cheap `eql` 89 /// functions and avoids storing each key's hash in the table. Setting 90 /// `store_hash` to `true` incurs more memory cost but limits `eql` to 91 /// being called only once per insertion/deletion (provided there are no 92 /// hash collisions). 93 comptime store_hash: bool, 94 ) type { 95 return struct { 96 unmanaged: Unmanaged, 97 allocator: Allocator, 98 ctx: Context, 99 100 /// The ArrayHashMapUnmanaged type using the same settings as this managed map. 101 pub const Unmanaged = ArrayHashMapUnmanaged(K, V, Context, store_hash); 102 103 /// Pointers to a key and value in the backing store of this map. 104 /// Modifying the key is allowed only if it does not change the hash. 105 /// Modifying the value is allowed. 106 /// Entry pointers become invalid whenever this ArrayHashMap is modified, 107 /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. 108 pub const Entry = Unmanaged.Entry; 109 110 /// A KV pair which has been copied out of the backing store 111 pub const KV = Unmanaged.KV; 112 113 /// The Data type used for the MultiArrayList backing this map 114 pub const Data = Unmanaged.Data; 115 /// The MultiArrayList type backing this map 116 pub const DataList = Unmanaged.DataList; 117 118 /// The stored hash type, either u32 or void. 119 pub const Hash = Unmanaged.Hash; 120 121 /// getOrPut variants return this structure, with pointers 122 /// to the backing store and a flag to indicate whether an 123 /// existing entry was found. 124 /// Modifying the key is allowed only if it does not change the hash. 125 /// Modifying the value is allowed. 126 /// Entry pointers become invalid whenever this ArrayHashMap is modified, 127 /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. 128 pub const GetOrPutResult = Unmanaged.GetOrPutResult; 129 130 /// An Iterator over Entry pointers. 131 pub const Iterator = Unmanaged.Iterator; 132 133 const Self = @This(); 134 135 /// Create an ArrayHashMap instance which will use a specified allocator. 136 pub fn init(allocator: Allocator) Self { 137 if (@sizeOf(Context) != 0) 138 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call initContext instead."); 139 return initContext(allocator, undefined); 140 } 141 pub fn initContext(allocator: Allocator, ctx: Context) Self { 142 return .{ 143 .unmanaged = .empty, 144 .allocator = allocator, 145 .ctx = ctx, 146 }; 147 } 148 149 /// Frees the backing allocation and leaves the map in an undefined state. 150 /// Note that this does not free keys or values. You must take care of that 151 /// before calling this function, if it is needed. 152 pub fn deinit(self: *Self) void { 153 self.unmanaged.deinit(self.allocator); 154 self.* = undefined; 155 } 156 157 /// Puts the hash map into a state where any method call that would 158 /// cause an existing key or value pointer to become invalidated will 159 /// instead trigger an assertion. 160 /// 161 /// An additional call to `lockPointers` in such state also triggers an 162 /// assertion. 163 /// 164 /// `unlockPointers` returns the hash map to the previous state. 165 pub fn lockPointers(self: *Self) void { 166 self.unmanaged.lockPointers(); 167 } 168 169 /// Undoes a call to `lockPointers`. 170 pub fn unlockPointers(self: *Self) void { 171 self.unmanaged.unlockPointers(); 172 } 173 174 /// Clears the map but retains the backing allocation for future use. 175 pub fn clearRetainingCapacity(self: *Self) void { 176 return self.unmanaged.clearRetainingCapacity(); 177 } 178 179 /// Clears the map and releases the backing allocation 180 pub fn clearAndFree(self: *Self) void { 181 return self.unmanaged.clearAndFree(self.allocator); 182 } 183 184 /// Returns the number of KV pairs stored in this map. 185 pub fn count(self: Self) usize { 186 return self.unmanaged.count(); 187 } 188 189 /// Returns the backing array of keys in this map. Modifying the map may 190 /// invalidate this array. Modifying this array in a way that changes 191 /// key hashes or key equality puts the map into an unusable state until 192 /// `reIndex` is called. 193 pub fn keys(self: Self) []K { 194 return self.unmanaged.keys(); 195 } 196 /// Returns the backing array of values in this map. Modifying the map 197 /// may invalidate this array. It is permitted to modify the values in 198 /// this array. 199 pub fn values(self: Self) []V { 200 return self.unmanaged.values(); 201 } 202 203 /// Returns an iterator over the pairs in this map. 204 /// Modifying the map may invalidate this iterator. 205 pub fn iterator(self: *const Self) Iterator { 206 return self.unmanaged.iterator(); 207 } 208 209 /// If key exists this function cannot fail. 210 /// If there is an existing item with `key`, then the result 211 /// `Entry` pointer points to it, and found_existing is true. 212 /// Otherwise, puts a new item with undefined value, and 213 /// the `Entry` pointer points to it. Caller should then initialize 214 /// the value (but not the key). 215 pub fn getOrPut(self: *Self, key: K) !GetOrPutResult { 216 return self.unmanaged.getOrPutContext(self.allocator, key, self.ctx); 217 } 218 pub fn getOrPutAdapted(self: *Self, key: anytype, ctx: anytype) !GetOrPutResult { 219 return self.unmanaged.getOrPutContextAdapted(self.allocator, key, ctx, self.ctx); 220 } 221 222 /// If there is an existing item with `key`, then the result 223 /// `Entry` pointer points to it, and found_existing is true. 224 /// Otherwise, puts a new item with undefined value, and 225 /// the `Entry` pointer points to it. Caller should then initialize 226 /// the value (but not the key). 227 /// If a new entry needs to be stored, this function asserts there 228 /// is enough capacity to store it. 229 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { 230 return self.unmanaged.getOrPutAssumeCapacityContext(key, self.ctx); 231 } 232 pub fn getOrPutAssumeCapacityAdapted(self: *Self, key: anytype, ctx: anytype) GetOrPutResult { 233 return self.unmanaged.getOrPutAssumeCapacityAdapted(key, ctx); 234 } 235 pub fn getOrPutValue(self: *Self, key: K, value: V) !GetOrPutResult { 236 return self.unmanaged.getOrPutValueContext(self.allocator, key, value, self.ctx); 237 } 238 239 /// Increases capacity, guaranteeing that insertions up until the 240 /// `expected_count` will not cause an allocation, and therefore cannot fail. 241 pub fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { 242 return self.unmanaged.ensureTotalCapacityContext(self.allocator, new_capacity, self.ctx); 243 } 244 245 /// Increases capacity, guaranteeing that insertions up until 246 /// `additional_count` **more** items will not cause an allocation, and 247 /// therefore cannot fail. 248 pub fn ensureUnusedCapacity(self: *Self, additional_count: usize) !void { 249 return self.unmanaged.ensureUnusedCapacityContext(self.allocator, additional_count, self.ctx); 250 } 251 252 /// Returns the number of total elements which may be present before it is 253 /// no longer guaranteed that no allocations will be performed. 254 pub fn capacity(self: Self) usize { 255 return self.unmanaged.capacity(); 256 } 257 258 /// Clobbers any existing data. To detect if a put would clobber 259 /// existing data, see `getOrPut`. 260 pub fn put(self: *Self, key: K, value: V) !void { 261 return self.unmanaged.putContext(self.allocator, key, value, self.ctx); 262 } 263 264 /// Inserts a key-value pair into the hash map, asserting that no previous 265 /// entry with the same key is already present 266 pub fn putNoClobber(self: *Self, key: K, value: V) !void { 267 return self.unmanaged.putNoClobberContext(self.allocator, key, value, self.ctx); 268 } 269 270 /// Asserts there is enough capacity to store the new key-value pair. 271 /// Clobbers any existing data. To detect if a put would clobber 272 /// existing data, see `getOrPutAssumeCapacity`. 273 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { 274 return self.unmanaged.putAssumeCapacityContext(key, value, self.ctx); 275 } 276 277 /// Asserts there is enough capacity to store the new key-value pair. 278 /// Asserts that it does not clobber any existing data. 279 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. 280 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { 281 return self.unmanaged.putAssumeCapacityNoClobberContext(key, value, self.ctx); 282 } 283 284 /// Inserts a new `Entry` into the hash map, returning the previous one, if any. 285 pub fn fetchPut(self: *Self, key: K, value: V) !?KV { 286 return self.unmanaged.fetchPutContext(self.allocator, key, value, self.ctx); 287 } 288 289 /// Inserts a new `Entry` into the hash map, returning the previous one, if any. 290 /// If insertion happuns, asserts there is enough capacity without allocating. 291 pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?KV { 292 return self.unmanaged.fetchPutAssumeCapacityContext(key, value, self.ctx); 293 } 294 295 /// Finds pointers to the key and value storage associated with a key. 296 pub fn getEntry(self: Self, key: K) ?Entry { 297 return self.unmanaged.getEntryContext(key, self.ctx); 298 } 299 pub fn getEntryAdapted(self: Self, key: anytype, ctx: anytype) ?Entry { 300 return self.unmanaged.getEntryAdapted(key, ctx); 301 } 302 303 /// Finds the index in the `entries` array where a key is stored 304 pub fn getIndex(self: Self, key: K) ?usize { 305 return self.unmanaged.getIndexContext(key, self.ctx); 306 } 307 pub fn getIndexAdapted(self: Self, key: anytype, ctx: anytype) ?usize { 308 return self.unmanaged.getIndexAdapted(key, ctx); 309 } 310 311 /// Find the value associated with a key 312 pub fn get(self: Self, key: K) ?V { 313 return self.unmanaged.getContext(key, self.ctx); 314 } 315 pub fn getAdapted(self: Self, key: anytype, ctx: anytype) ?V { 316 return self.unmanaged.getAdapted(key, ctx); 317 } 318 319 /// Find a pointer to the value associated with a key 320 pub fn getPtr(self: Self, key: K) ?*V { 321 return self.unmanaged.getPtrContext(key, self.ctx); 322 } 323 pub fn getPtrAdapted(self: Self, key: anytype, ctx: anytype) ?*V { 324 return self.unmanaged.getPtrAdapted(key, ctx); 325 } 326 327 /// Find the actual key associated with an adapted key 328 pub fn getKey(self: Self, key: K) ?K { 329 return self.unmanaged.getKeyContext(key, self.ctx); 330 } 331 pub fn getKeyAdapted(self: Self, key: anytype, ctx: anytype) ?K { 332 return self.unmanaged.getKeyAdapted(key, ctx); 333 } 334 335 /// Find a pointer to the actual key associated with an adapted key 336 pub fn getKeyPtr(self: Self, key: K) ?*K { 337 return self.unmanaged.getKeyPtrContext(key, self.ctx); 338 } 339 pub fn getKeyPtrAdapted(self: Self, key: anytype, ctx: anytype) ?*K { 340 return self.unmanaged.getKeyPtrAdapted(key, ctx); 341 } 342 343 /// Check whether a key is stored in the map 344 pub fn contains(self: Self, key: K) bool { 345 return self.unmanaged.containsContext(key, self.ctx); 346 } 347 pub fn containsAdapted(self: Self, key: anytype, ctx: anytype) bool { 348 return self.unmanaged.containsAdapted(key, ctx); 349 } 350 351 /// If there is an `Entry` with a matching key, it is deleted from 352 /// the hash map, and then returned from this function. The entry is 353 /// removed from the underlying array by swapping it with the last 354 /// element. 355 pub fn fetchSwapRemove(self: *Self, key: K) ?KV { 356 return self.unmanaged.fetchSwapRemoveContext(key, self.ctx); 357 } 358 pub fn fetchSwapRemoveAdapted(self: *Self, key: anytype, ctx: anytype) ?KV { 359 return self.unmanaged.fetchSwapRemoveContextAdapted(key, ctx, self.ctx); 360 } 361 362 /// If there is an `Entry` with a matching key, it is deleted from 363 /// the hash map, and then returned from this function. The entry is 364 /// removed from the underlying array by shifting all elements forward 365 /// thereby maintaining the current ordering. 366 pub fn fetchOrderedRemove(self: *Self, key: K) ?KV { 367 return self.unmanaged.fetchOrderedRemoveContext(key, self.ctx); 368 } 369 pub fn fetchOrderedRemoveAdapted(self: *Self, key: anytype, ctx: anytype) ?KV { 370 return self.unmanaged.fetchOrderedRemoveContextAdapted(key, ctx, self.ctx); 371 } 372 373 /// If there is an `Entry` with a matching key, it is deleted from 374 /// the hash map. The entry is removed from the underlying array 375 /// by swapping it with the last element. Returns true if an entry 376 /// was removed, false otherwise. 377 pub fn swapRemove(self: *Self, key: K) bool { 378 return self.unmanaged.swapRemoveContext(key, self.ctx); 379 } 380 pub fn swapRemoveAdapted(self: *Self, key: anytype, ctx: anytype) bool { 381 return self.unmanaged.swapRemoveContextAdapted(key, ctx, self.ctx); 382 } 383 384 /// If there is an `Entry` with a matching key, it is deleted from 385 /// the hash map. The entry is removed from the underlying array 386 /// by shifting all elements forward, thereby maintaining the 387 /// current ordering. Returns true if an entry was removed, false otherwise. 388 pub fn orderedRemove(self: *Self, key: K) bool { 389 return self.unmanaged.orderedRemoveContext(key, self.ctx); 390 } 391 pub fn orderedRemoveAdapted(self: *Self, key: anytype, ctx: anytype) bool { 392 return self.unmanaged.orderedRemoveContextAdapted(key, ctx, self.ctx); 393 } 394 395 /// Deletes the item at the specified index in `entries` from 396 /// the hash map. The entry is removed from the underlying array 397 /// by swapping it with the last element. 398 pub fn swapRemoveAt(self: *Self, index: usize) void { 399 self.unmanaged.swapRemoveAtContext(index, self.ctx); 400 } 401 402 /// Deletes the item at the specified index in `entries` from 403 /// the hash map. The entry is removed from the underlying array 404 /// by shifting all elements forward, thereby maintaining the 405 /// current ordering. 406 pub fn orderedRemoveAt(self: *Self, index: usize) void { 407 self.unmanaged.orderedRemoveAtContext(index, self.ctx); 408 } 409 410 /// Create a copy of the hash map which can be modified separately. 411 /// The copy uses the same context and allocator as this instance. 412 pub fn clone(self: Self) !Self { 413 var other = try self.unmanaged.cloneContext(self.allocator, self.ctx); 414 return other.promoteContext(self.allocator, self.ctx); 415 } 416 /// Create a copy of the hash map which can be modified separately. 417 /// The copy uses the same context as this instance, but the specified 418 /// allocator. 419 pub fn cloneWithAllocator(self: Self, allocator: Allocator) !Self { 420 var other = try self.unmanaged.cloneContext(allocator, self.ctx); 421 return other.promoteContext(allocator, self.ctx); 422 } 423 /// Create a copy of the hash map which can be modified separately. 424 /// The copy uses the same allocator as this instance, but the 425 /// specified context. 426 pub fn cloneWithContext(self: Self, ctx: anytype) !ArrayHashMap(K, V, @TypeOf(ctx), store_hash) { 427 var other = try self.unmanaged.cloneContext(self.allocator, ctx); 428 return other.promoteContext(self.allocator, ctx); 429 } 430 /// Create a copy of the hash map which can be modified separately. 431 /// The copy uses the specified allocator and context. 432 pub fn cloneWithAllocatorAndContext(self: Self, allocator: Allocator, ctx: anytype) !ArrayHashMap(K, V, @TypeOf(ctx), store_hash) { 433 var other = try self.unmanaged.cloneContext(allocator, ctx); 434 return other.promoteContext(allocator, ctx); 435 } 436 437 /// Set the map to an empty state, making deinitialization a no-op, and 438 /// returning a copy of the original. 439 pub fn move(self: *Self) Self { 440 self.unmanaged.pointer_stability.assertUnlocked(); 441 const result = self.*; 442 self.unmanaged = .empty; 443 return result; 444 } 445 446 /// Recomputes stored hashes and rebuilds the key indexes. If the 447 /// underlying keys have been modified directly, call this method to 448 /// recompute the denormalized metadata necessary for the operation of 449 /// the methods of this map that lookup entries by key. 450 /// 451 /// One use case for this is directly calling `entries.resize()` to grow 452 /// the underlying storage, and then setting the `keys` and `values` 453 /// directly without going through the methods of this map. 454 /// 455 /// The time complexity of this operation is O(n). 456 pub fn reIndex(self: *Self) !void { 457 return self.unmanaged.reIndexContext(self.allocator, self.ctx); 458 } 459 460 /// Sorts the entries and then rebuilds the index. 461 /// `sort_ctx` must have this method: 462 /// `fn lessThan(ctx: @TypeOf(ctx), a_index: usize, b_index: usize) bool` 463 pub fn sort(self: *Self, sort_ctx: anytype) void { 464 return self.unmanaged.sortContext(sort_ctx, self.ctx); 465 } 466 467 /// Shrinks the underlying `Entry` array to `new_len` elements and 468 /// discards any associated index entries. Keeps capacity the same. 469 /// 470 /// Asserts the discarded entries remain initialized and capable of 471 /// performing hash and equality checks. Any deinitialization of 472 /// discarded entries must take place *after* calling this function. 473 pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { 474 return self.unmanaged.shrinkRetainingCapacityContext(new_len, self.ctx); 475 } 476 477 /// Shrinks the underlying `Entry` array to `new_len` elements and 478 /// discards any associated index entries. Reduces allocated capacity. 479 /// 480 /// Asserts the discarded entries remain initialized and capable of 481 /// performing hash and equality checks. It is a bug to call this 482 /// function if the discarded entries require deinitialization. For 483 /// that use case, `shrinkRetainingCapacity` can be used instead. 484 pub fn shrinkAndFree(self: *Self, new_len: usize) void { 485 return self.unmanaged.shrinkAndFreeContext(self.allocator, new_len, self.ctx); 486 } 487 488 /// Removes the last inserted `Entry` in the hash map and returns it. 489 pub fn pop(self: *Self) KV { 490 return self.unmanaged.popContext(self.ctx); 491 } 492 493 /// Removes the last inserted `Entry` in the hash map and returns it if count is nonzero. 494 /// Otherwise returns null. 495 pub fn popOrNull(self: *Self) ?KV { 496 return self.unmanaged.popOrNullContext(self.ctx); 497 } 498 }; 499 } 500 501 /// A hash table of keys and values, each stored sequentially. 502 /// 503 /// Insertion order is preserved. In general, this data structure supports the same 504 /// operations as `std.ArrayListUnmanaged`. 505 /// 506 /// Deletion operations: 507 /// * `swapRemove` - O(1) 508 /// * `orderedRemove` - O(N) 509 /// 510 /// Modifying the hash map while iterating is allowed, however, one must understand 511 /// the (well defined) behavior when mixing insertions and deletions with iteration. 512 /// 513 /// This type does not store an `Allocator` field - the `Allocator` must be passed in 514 /// with each function call that requires it. See `ArrayHashMap` for a type that stores 515 /// an `Allocator` field for convenience. 516 /// 517 /// Can be initialized directly using the default field values. 518 /// 519 /// This type is designed to have low overhead for small numbers of entries. When 520 /// `store_hash` is `false` and the number of entries in the map is less than 9, 521 /// the overhead cost of using `ArrayHashMapUnmanaged` rather than `std.ArrayList` is 522 /// only a single pointer-sized integer. 523 /// 524 /// Default initialization of this struct is deprecated; use `.empty` instead. 525 pub fn ArrayHashMapUnmanaged( 526 comptime K: type, 527 comptime V: type, 528 /// A namespace that provides these two functions: 529 /// * `pub fn hash(self, K) u32` 530 /// * `pub fn eql(self, K, K, usize) bool` 531 /// 532 /// The final `usize` in the `eql` function represents the index of the key 533 /// that's already inside the map. 534 comptime Context: type, 535 /// When `false`, this data structure is biased towards cheap `eql` 536 /// functions and avoids storing each key's hash in the table. Setting 537 /// `store_hash` to `true` incurs more memory cost but limits `eql` to 538 /// being called only once per insertion/deletion (provided there are no 539 /// hash collisions). 540 comptime store_hash: bool, 541 ) type { 542 return struct { 543 /// It is permitted to access this field directly. 544 /// After any modification to the keys, consider calling `reIndex`. 545 entries: DataList = .{}, 546 547 /// When entries length is less than `linear_scan_max`, this remains `null`. 548 /// Once entries length grows big enough, this field is allocated. There is 549 /// an IndexHeader followed by an array of Index(I) structs, where I is defined 550 /// by how many total indexes there are. 551 index_header: ?*IndexHeader = null, 552 553 /// Used to detect memory safety violations. 554 pointer_stability: std.debug.SafetyLock = .{}, 555 556 /// A map containing no keys or values. 557 pub const empty: Self = .{ 558 .entries = .{}, 559 .index_header = null, 560 }; 561 562 /// Modifying the key is allowed only if it does not change the hash. 563 /// Modifying the value is allowed. 564 /// Entry pointers become invalid whenever this ArrayHashMap is modified, 565 /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. 566 pub const Entry = struct { 567 key_ptr: *K, 568 value_ptr: *V, 569 }; 570 571 /// A KV pair which has been copied out of the backing store 572 pub const KV = struct { 573 key: K, 574 value: V, 575 }; 576 577 /// The Data type used for the MultiArrayList backing this map 578 pub const Data = struct { 579 hash: Hash, 580 key: K, 581 value: V, 582 }; 583 584 /// The MultiArrayList type backing this map 585 pub const DataList = std.MultiArrayList(Data); 586 587 /// The stored hash type, either u32 or void. 588 pub const Hash = if (store_hash) u32 else void; 589 590 /// getOrPut variants return this structure, with pointers 591 /// to the backing store and a flag to indicate whether an 592 /// existing entry was found. 593 /// Modifying the key is allowed only if it does not change the hash. 594 /// Modifying the value is allowed. 595 /// Entry pointers become invalid whenever this ArrayHashMap is modified, 596 /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. 597 pub const GetOrPutResult = struct { 598 key_ptr: *K, 599 value_ptr: *V, 600 found_existing: bool, 601 index: usize, 602 }; 603 604 /// The ArrayHashMap type using the same settings as this managed map. 605 pub const Managed = ArrayHashMap(K, V, Context, store_hash); 606 607 /// Some functions require a context only if hashes are not stored. 608 /// To keep the api simple, this type is only used internally. 609 const ByIndexContext = if (store_hash) void else Context; 610 611 const Self = @This(); 612 613 const linear_scan_max = 8; 614 615 const RemovalType = enum { 616 swap, 617 ordered, 618 }; 619 620 const Oom = Allocator.Error; 621 622 /// Convert from an unmanaged map to a managed map. After calling this, 623 /// the promoted map should no longer be used. 624 pub fn promote(self: Self, gpa: Allocator) Managed { 625 if (@sizeOf(Context) != 0) 626 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call promoteContext instead."); 627 return self.promoteContext(gpa, undefined); 628 } 629 pub fn promoteContext(self: Self, gpa: Allocator, ctx: Context) Managed { 630 return .{ 631 .unmanaged = self, 632 .allocator = gpa, 633 .ctx = ctx, 634 }; 635 } 636 637 pub fn init(gpa: Allocator, key_list: []const K, value_list: []const V) Oom!Self { 638 var self: Self = .{}; 639 errdefer self.deinit(gpa); 640 try self.reinit(gpa, key_list, value_list); 641 return self; 642 } 643 644 pub fn reinit(self: *Self, gpa: Allocator, key_list: []const K, value_list: []const V) Oom!void { 645 try self.entries.resize(gpa, key_list.len); 646 @memcpy(self.keys(), key_list); 647 if (@sizeOf(V) != 0) { 648 assert(key_list.len == value_list.len); 649 @memcpy(self.values(), value_list); 650 } 651 try self.reIndex(gpa); 652 } 653 654 /// Frees the backing allocation and leaves the map in an undefined state. 655 /// Note that this does not free keys or values. You must take care of that 656 /// before calling this function, if it is needed. 657 pub fn deinit(self: *Self, gpa: Allocator) void { 658 self.pointer_stability.assertUnlocked(); 659 self.entries.deinit(gpa); 660 if (self.index_header) |header| { 661 header.free(gpa); 662 } 663 self.* = undefined; 664 } 665 666 /// Puts the hash map into a state where any method call that would 667 /// cause an existing key or value pointer to become invalidated will 668 /// instead trigger an assertion. 669 /// 670 /// An additional call to `lockPointers` in such state also triggers an 671 /// assertion. 672 /// 673 /// `unlockPointers` returns the hash map to the previous state. 674 pub fn lockPointers(self: *Self) void { 675 self.pointer_stability.lock(); 676 } 677 678 /// Undoes a call to `lockPointers`. 679 pub fn unlockPointers(self: *Self) void { 680 self.pointer_stability.unlock(); 681 } 682 683 /// Clears the map but retains the backing allocation for future use. 684 pub fn clearRetainingCapacity(self: *Self) void { 685 self.pointer_stability.lock(); 686 defer self.pointer_stability.unlock(); 687 688 self.entries.len = 0; 689 if (self.index_header) |header| { 690 switch (header.capacityIndexType()) { 691 .u8 => @memset(header.indexes(u8), Index(u8).empty), 692 .u16 => @memset(header.indexes(u16), Index(u16).empty), 693 .u32 => @memset(header.indexes(u32), Index(u32).empty), 694 } 695 } 696 } 697 698 /// Clears the map and releases the backing allocation 699 pub fn clearAndFree(self: *Self, gpa: Allocator) void { 700 self.pointer_stability.lock(); 701 defer self.pointer_stability.unlock(); 702 703 self.entries.shrinkAndFree(gpa, 0); 704 if (self.index_header) |header| { 705 header.free(gpa); 706 self.index_header = null; 707 } 708 } 709 710 /// Returns the number of KV pairs stored in this map. 711 pub fn count(self: Self) usize { 712 return self.entries.len; 713 } 714 715 /// Returns the backing array of keys in this map. Modifying the map may 716 /// invalidate this array. Modifying this array in a way that changes 717 /// key hashes or key equality puts the map into an unusable state until 718 /// `reIndex` is called. 719 pub fn keys(self: Self) []K { 720 return self.entries.items(.key); 721 } 722 /// Returns the backing array of values in this map. Modifying the map 723 /// may invalidate this array. It is permitted to modify the values in 724 /// this array. 725 pub fn values(self: Self) []V { 726 return self.entries.items(.value); 727 } 728 729 /// Returns an iterator over the pairs in this map. 730 /// Modifying the map may invalidate this iterator. 731 pub fn iterator(self: Self) Iterator { 732 const slice = self.entries.slice(); 733 return .{ 734 .keys = slice.items(.key).ptr, 735 .values = slice.items(.value).ptr, 736 .len = @as(u32, @intCast(slice.len)), 737 }; 738 } 739 pub const Iterator = struct { 740 keys: [*]K, 741 values: [*]V, 742 len: u32, 743 index: u32 = 0, 744 745 pub fn next(it: *Iterator) ?Entry { 746 if (it.index >= it.len) return null; 747 const result = Entry{ 748 .key_ptr = &it.keys[it.index], 749 // workaround for #6974 750 .value_ptr = if (@sizeOf(*V) == 0) undefined else &it.values[it.index], 751 }; 752 it.index += 1; 753 return result; 754 } 755 756 /// Reset the iterator to the initial index 757 pub fn reset(it: *Iterator) void { 758 it.index = 0; 759 } 760 }; 761 762 /// If key exists this function cannot fail. 763 /// If there is an existing item with `key`, then the result 764 /// `Entry` pointer points to it, and found_existing is true. 765 /// Otherwise, puts a new item with undefined value, and 766 /// the `Entry` pointer points to it. Caller should then initialize 767 /// the value (but not the key). 768 pub fn getOrPut(self: *Self, gpa: Allocator, key: K) Oom!GetOrPutResult { 769 if (@sizeOf(Context) != 0) 770 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutContext instead."); 771 return self.getOrPutContext(gpa, key, undefined); 772 } 773 pub fn getOrPutContext(self: *Self, gpa: Allocator, key: K, ctx: Context) Oom!GetOrPutResult { 774 const gop = try self.getOrPutContextAdapted(gpa, key, ctx, ctx); 775 if (!gop.found_existing) { 776 gop.key_ptr.* = key; 777 } 778 return gop; 779 } 780 pub fn getOrPutAdapted(self: *Self, gpa: Allocator, key: anytype, key_ctx: anytype) Oom!GetOrPutResult { 781 if (@sizeOf(Context) != 0) 782 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutContextAdapted instead."); 783 return self.getOrPutContextAdapted(gpa, key, key_ctx, undefined); 784 } 785 pub fn getOrPutContextAdapted(self: *Self, gpa: Allocator, key: anytype, key_ctx: anytype, ctx: Context) Oom!GetOrPutResult { 786 self.ensureTotalCapacityContext(gpa, self.entries.len + 1, ctx) catch |err| { 787 // "If key exists this function cannot fail." 788 const index = self.getIndexAdapted(key, key_ctx) orelse return err; 789 const slice = self.entries.slice(); 790 return GetOrPutResult{ 791 .key_ptr = &slice.items(.key)[index], 792 // workaround for #6974 793 .value_ptr = if (@sizeOf(*V) == 0) undefined else &slice.items(.value)[index], 794 .found_existing = true, 795 .index = index, 796 }; 797 }; 798 return self.getOrPutAssumeCapacityAdapted(key, key_ctx); 799 } 800 801 /// If there is an existing item with `key`, then the result 802 /// `Entry` pointer points to it, and found_existing is true. 803 /// Otherwise, puts a new item with undefined value, and 804 /// the `Entry` pointer points to it. Caller should then initialize 805 /// the value (but not the key). 806 /// If a new entry needs to be stored, this function asserts there 807 /// is enough capacity to store it. 808 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { 809 if (@sizeOf(Context) != 0) 810 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutAssumeCapacityContext instead."); 811 return self.getOrPutAssumeCapacityContext(key, undefined); 812 } 813 pub fn getOrPutAssumeCapacityContext(self: *Self, key: K, ctx: Context) GetOrPutResult { 814 const gop = self.getOrPutAssumeCapacityAdapted(key, ctx); 815 if (!gop.found_existing) { 816 gop.key_ptr.* = key; 817 } 818 return gop; 819 } 820 /// If there is an existing item with `key`, then the result 821 /// `Entry` pointers point to it, and found_existing is true. 822 /// Otherwise, puts a new item with undefined key and value, and 823 /// the `Entry` pointers point to it. Caller must then initialize 824 /// both the key and the value. 825 /// If a new entry needs to be stored, this function asserts there 826 /// is enough capacity to store it. 827 pub fn getOrPutAssumeCapacityAdapted(self: *Self, key: anytype, ctx: anytype) GetOrPutResult { 828 const header = self.index_header orelse { 829 // Linear scan. 830 const h = if (store_hash) checkedHash(ctx, key) else {}; 831 const slice = self.entries.slice(); 832 const hashes_array = slice.items(.hash); 833 const keys_array = slice.items(.key); 834 for (keys_array, 0..) |*item_key, i| { 835 if (hashes_array[i] == h and checkedEql(ctx, key, item_key.*, i)) { 836 return GetOrPutResult{ 837 .key_ptr = item_key, 838 // workaround for #6974 839 .value_ptr = if (@sizeOf(*V) == 0) undefined else &slice.items(.value)[i], 840 .found_existing = true, 841 .index = i, 842 }; 843 } 844 } 845 846 const index = self.entries.addOneAssumeCapacity(); 847 // The slice length changed, so we directly index the pointer. 848 if (store_hash) hashes_array.ptr[index] = h; 849 850 return GetOrPutResult{ 851 .key_ptr = &keys_array.ptr[index], 852 // workaround for #6974 853 .value_ptr = if (@sizeOf(*V) == 0) undefined else &slice.items(.value).ptr[index], 854 .found_existing = false, 855 .index = index, 856 }; 857 }; 858 859 switch (header.capacityIndexType()) { 860 .u8 => return self.getOrPutInternal(key, ctx, header, u8), 861 .u16 => return self.getOrPutInternal(key, ctx, header, u16), 862 .u32 => return self.getOrPutInternal(key, ctx, header, u32), 863 } 864 } 865 866 pub fn getOrPutValue(self: *Self, gpa: Allocator, key: K, value: V) Oom!GetOrPutResult { 867 if (@sizeOf(Context) != 0) 868 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutValueContext instead."); 869 return self.getOrPutValueContext(gpa, key, value, undefined); 870 } 871 pub fn getOrPutValueContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!GetOrPutResult { 872 const res = try self.getOrPutContextAdapted(gpa, key, ctx, ctx); 873 if (!res.found_existing) { 874 res.key_ptr.* = key; 875 res.value_ptr.* = value; 876 } 877 return res; 878 } 879 880 /// Increases capacity, guaranteeing that insertions up until the 881 /// `expected_count` will not cause an allocation, and therefore cannot fail. 882 pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Oom!void { 883 if (@sizeOf(ByIndexContext) != 0) 884 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call ensureTotalCapacityContext instead."); 885 return self.ensureTotalCapacityContext(gpa, new_capacity, undefined); 886 } 887 pub fn ensureTotalCapacityContext(self: *Self, gpa: Allocator, new_capacity: usize, ctx: Context) Oom!void { 888 self.pointer_stability.lock(); 889 defer self.pointer_stability.unlock(); 890 891 if (new_capacity <= linear_scan_max) { 892 try self.entries.ensureTotalCapacity(gpa, new_capacity); 893 return; 894 } 895 896 if (self.index_header) |header| { 897 if (new_capacity <= header.capacity()) { 898 try self.entries.ensureTotalCapacity(gpa, new_capacity); 899 return; 900 } 901 } 902 903 try self.entries.ensureTotalCapacity(gpa, new_capacity); 904 const new_bit_index = try IndexHeader.findBitIndex(new_capacity); 905 const new_header = try IndexHeader.alloc(gpa, new_bit_index); 906 907 if (self.index_header) |old_header| old_header.free(gpa); 908 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header); 909 self.index_header = new_header; 910 } 911 912 /// Increases capacity, guaranteeing that insertions up until 913 /// `additional_count` **more** items will not cause an allocation, and 914 /// therefore cannot fail. 915 pub fn ensureUnusedCapacity( 916 self: *Self, 917 gpa: Allocator, 918 additional_capacity: usize, 919 ) Oom!void { 920 if (@sizeOf(Context) != 0) 921 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call ensureTotalCapacityContext instead."); 922 return self.ensureUnusedCapacityContext(gpa, additional_capacity, undefined); 923 } 924 pub fn ensureUnusedCapacityContext( 925 self: *Self, 926 gpa: Allocator, 927 additional_capacity: usize, 928 ctx: Context, 929 ) Oom!void { 930 return self.ensureTotalCapacityContext(gpa, self.count() + additional_capacity, ctx); 931 } 932 933 /// Returns the number of total elements which may be present before it is 934 /// no longer guaranteed that no allocations will be performed. 935 pub fn capacity(self: Self) usize { 936 const entry_cap = self.entries.capacity; 937 const header = self.index_header orelse return @min(linear_scan_max, entry_cap); 938 const indexes_cap = header.capacity(); 939 return @min(entry_cap, indexes_cap); 940 } 941 942 /// Clobbers any existing data. To detect if a put would clobber 943 /// existing data, see `getOrPut`. 944 pub fn put(self: *Self, gpa: Allocator, key: K, value: V) Oom!void { 945 if (@sizeOf(Context) != 0) 946 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putContext instead."); 947 return self.putContext(gpa, key, value, undefined); 948 } 949 pub fn putContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!void { 950 const result = try self.getOrPutContext(gpa, key, ctx); 951 result.value_ptr.* = value; 952 } 953 954 /// Inserts a key-value pair into the hash map, asserting that no previous 955 /// entry with the same key is already present 956 pub fn putNoClobber(self: *Self, gpa: Allocator, key: K, value: V) Oom!void { 957 if (@sizeOf(Context) != 0) 958 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putNoClobberContext instead."); 959 return self.putNoClobberContext(gpa, key, value, undefined); 960 } 961 pub fn putNoClobberContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!void { 962 const result = try self.getOrPutContext(gpa, key, ctx); 963 assert(!result.found_existing); 964 result.value_ptr.* = value; 965 } 966 967 /// Asserts there is enough capacity to store the new key-value pair. 968 /// Clobbers any existing data. To detect if a put would clobber 969 /// existing data, see `getOrPutAssumeCapacity`. 970 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { 971 if (@sizeOf(Context) != 0) 972 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putAssumeCapacityContext instead."); 973 return self.putAssumeCapacityContext(key, value, undefined); 974 } 975 pub fn putAssumeCapacityContext(self: *Self, key: K, value: V, ctx: Context) void { 976 const result = self.getOrPutAssumeCapacityContext(key, ctx); 977 result.value_ptr.* = value; 978 } 979 980 /// Asserts there is enough capacity to store the new key-value pair. 981 /// Asserts that it does not clobber any existing data. 982 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. 983 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { 984 if (@sizeOf(Context) != 0) 985 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putAssumeCapacityNoClobberContext instead."); 986 return self.putAssumeCapacityNoClobberContext(key, value, undefined); 987 } 988 pub fn putAssumeCapacityNoClobberContext(self: *Self, key: K, value: V, ctx: Context) void { 989 const result = self.getOrPutAssumeCapacityContext(key, ctx); 990 assert(!result.found_existing); 991 result.value_ptr.* = value; 992 } 993 994 /// Inserts a new `Entry` into the hash map, returning the previous one, if any. 995 pub fn fetchPut(self: *Self, gpa: Allocator, key: K, value: V) Oom!?KV { 996 if (@sizeOf(Context) != 0) 997 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchPutContext instead."); 998 return self.fetchPutContext(gpa, key, value, undefined); 999 } 1000 pub fn fetchPutContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!?KV { 1001 const gop = try self.getOrPutContext(gpa, key, ctx); 1002 var result: ?KV = null; 1003 if (gop.found_existing) { 1004 result = KV{ 1005 .key = gop.key_ptr.*, 1006 .value = gop.value_ptr.*, 1007 }; 1008 } 1009 gop.value_ptr.* = value; 1010 return result; 1011 } 1012 1013 /// Inserts a new `Entry` into the hash map, returning the previous one, if any. 1014 /// If insertion happens, asserts there is enough capacity without allocating. 1015 pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?KV { 1016 if (@sizeOf(Context) != 0) 1017 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchPutAssumeCapacityContext instead."); 1018 return self.fetchPutAssumeCapacityContext(key, value, undefined); 1019 } 1020 pub fn fetchPutAssumeCapacityContext(self: *Self, key: K, value: V, ctx: Context) ?KV { 1021 const gop = self.getOrPutAssumeCapacityContext(key, ctx); 1022 var result: ?KV = null; 1023 if (gop.found_existing) { 1024 result = KV{ 1025 .key = gop.key_ptr.*, 1026 .value = gop.value_ptr.*, 1027 }; 1028 } 1029 gop.value_ptr.* = value; 1030 return result; 1031 } 1032 1033 /// Finds pointers to the key and value storage associated with a key. 1034 pub fn getEntry(self: Self, key: K) ?Entry { 1035 if (@sizeOf(Context) != 0) 1036 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getEntryContext instead."); 1037 return self.getEntryContext(key, undefined); 1038 } 1039 pub fn getEntryContext(self: Self, key: K, ctx: Context) ?Entry { 1040 return self.getEntryAdapted(key, ctx); 1041 } 1042 pub fn getEntryAdapted(self: Self, key: anytype, ctx: anytype) ?Entry { 1043 const index = self.getIndexAdapted(key, ctx) orelse return null; 1044 const slice = self.entries.slice(); 1045 return Entry{ 1046 .key_ptr = &slice.items(.key)[index], 1047 // workaround for #6974 1048 .value_ptr = if (@sizeOf(*V) == 0) undefined else &slice.items(.value)[index], 1049 }; 1050 } 1051 1052 /// Finds the index in the `entries` array where a key is stored 1053 pub fn getIndex(self: Self, key: K) ?usize { 1054 if (@sizeOf(Context) != 0) 1055 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getIndexContext instead."); 1056 return self.getIndexContext(key, undefined); 1057 } 1058 pub fn getIndexContext(self: Self, key: K, ctx: Context) ?usize { 1059 return self.getIndexAdapted(key, ctx); 1060 } 1061 pub fn getIndexAdapted(self: Self, key: anytype, ctx: anytype) ?usize { 1062 const header = self.index_header orelse { 1063 // Linear scan. 1064 const h = if (store_hash) checkedHash(ctx, key) else {}; 1065 const slice = self.entries.slice(); 1066 const hashes_array = slice.items(.hash); 1067 const keys_array = slice.items(.key); 1068 for (keys_array, 0..) |*item_key, i| { 1069 if (hashes_array[i] == h and checkedEql(ctx, key, item_key.*, i)) { 1070 return i; 1071 } 1072 } 1073 return null; 1074 }; 1075 switch (header.capacityIndexType()) { 1076 .u8 => return self.getIndexWithHeaderGeneric(key, ctx, header, u8), 1077 .u16 => return self.getIndexWithHeaderGeneric(key, ctx, header, u16), 1078 .u32 => return self.getIndexWithHeaderGeneric(key, ctx, header, u32), 1079 } 1080 } 1081 fn getIndexWithHeaderGeneric(self: Self, key: anytype, ctx: anytype, header: *IndexHeader, comptime I: type) ?usize { 1082 const indexes = header.indexes(I); 1083 const slot = self.getSlotByKey(key, ctx, header, I, indexes) orelse return null; 1084 return indexes[slot].entry_index; 1085 } 1086 1087 /// Find the value associated with a key 1088 pub fn get(self: Self, key: K) ?V { 1089 if (@sizeOf(Context) != 0) 1090 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getContext instead."); 1091 return self.getContext(key, undefined); 1092 } 1093 pub fn getContext(self: Self, key: K, ctx: Context) ?V { 1094 return self.getAdapted(key, ctx); 1095 } 1096 pub fn getAdapted(self: Self, key: anytype, ctx: anytype) ?V { 1097 const index = self.getIndexAdapted(key, ctx) orelse return null; 1098 return self.values()[index]; 1099 } 1100 1101 /// Find a pointer to the value associated with a key 1102 pub fn getPtr(self: Self, key: K) ?*V { 1103 if (@sizeOf(Context) != 0) 1104 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getPtrContext instead."); 1105 return self.getPtrContext(key, undefined); 1106 } 1107 pub fn getPtrContext(self: Self, key: K, ctx: Context) ?*V { 1108 return self.getPtrAdapted(key, ctx); 1109 } 1110 pub fn getPtrAdapted(self: Self, key: anytype, ctx: anytype) ?*V { 1111 const index = self.getIndexAdapted(key, ctx) orelse return null; 1112 // workaround for #6974 1113 return if (@sizeOf(*V) == 0) @as(*V, undefined) else &self.values()[index]; 1114 } 1115 1116 /// Find the actual key associated with an adapted key 1117 pub fn getKey(self: Self, key: K) ?K { 1118 if (@sizeOf(Context) != 0) 1119 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getKeyContext instead."); 1120 return self.getKeyContext(key, undefined); 1121 } 1122 pub fn getKeyContext(self: Self, key: K, ctx: Context) ?K { 1123 return self.getKeyAdapted(key, ctx); 1124 } 1125 pub fn getKeyAdapted(self: Self, key: anytype, ctx: anytype) ?K { 1126 const index = self.getIndexAdapted(key, ctx) orelse return null; 1127 return self.keys()[index]; 1128 } 1129 1130 /// Find a pointer to the actual key associated with an adapted key 1131 pub fn getKeyPtr(self: Self, key: K) ?*K { 1132 if (@sizeOf(Context) != 0) 1133 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getKeyPtrContext instead."); 1134 return self.getKeyPtrContext(key, undefined); 1135 } 1136 pub fn getKeyPtrContext(self: Self, key: K, ctx: Context) ?*K { 1137 return self.getKeyPtrAdapted(key, ctx); 1138 } 1139 pub fn getKeyPtrAdapted(self: Self, key: anytype, ctx: anytype) ?*K { 1140 const index = self.getIndexAdapted(key, ctx) orelse return null; 1141 return &self.keys()[index]; 1142 } 1143 1144 /// Check whether a key is stored in the map 1145 pub fn contains(self: Self, key: K) bool { 1146 if (@sizeOf(Context) != 0) 1147 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call containsContext instead."); 1148 return self.containsContext(key, undefined); 1149 } 1150 pub fn containsContext(self: Self, key: K, ctx: Context) bool { 1151 return self.containsAdapted(key, ctx); 1152 } 1153 pub fn containsAdapted(self: Self, key: anytype, ctx: anytype) bool { 1154 return self.getIndexAdapted(key, ctx) != null; 1155 } 1156 1157 /// If there is an `Entry` with a matching key, it is deleted from 1158 /// the hash map, and then returned from this function. The entry is 1159 /// removed from the underlying array by swapping it with the last 1160 /// element. 1161 pub fn fetchSwapRemove(self: *Self, key: K) ?KV { 1162 if (@sizeOf(Context) != 0) 1163 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchSwapRemoveContext instead."); 1164 return self.fetchSwapRemoveContext(key, undefined); 1165 } 1166 pub fn fetchSwapRemoveContext(self: *Self, key: K, ctx: Context) ?KV { 1167 return self.fetchSwapRemoveContextAdapted(key, ctx, ctx); 1168 } 1169 pub fn fetchSwapRemoveAdapted(self: *Self, key: anytype, ctx: anytype) ?KV { 1170 if (@sizeOf(ByIndexContext) != 0) 1171 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchSwapRemoveContextAdapted instead."); 1172 return self.fetchSwapRemoveContextAdapted(key, ctx, undefined); 1173 } 1174 pub fn fetchSwapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { 1175 self.pointer_stability.lock(); 1176 defer self.pointer_stability.unlock(); 1177 1178 return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); 1179 } 1180 1181 /// If there is an `Entry` with a matching key, it is deleted from 1182 /// the hash map, and then returned from this function. The entry is 1183 /// removed from the underlying array by shifting all elements forward 1184 /// thereby maintaining the current ordering. 1185 pub fn fetchOrderedRemove(self: *Self, key: K) ?KV { 1186 if (@sizeOf(Context) != 0) 1187 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchOrderedRemoveContext instead."); 1188 return self.fetchOrderedRemoveContext(key, undefined); 1189 } 1190 pub fn fetchOrderedRemoveContext(self: *Self, key: K, ctx: Context) ?KV { 1191 return self.fetchOrderedRemoveContextAdapted(key, ctx, ctx); 1192 } 1193 pub fn fetchOrderedRemoveAdapted(self: *Self, key: anytype, ctx: anytype) ?KV { 1194 if (@sizeOf(ByIndexContext) != 0) 1195 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchOrderedRemoveContextAdapted instead."); 1196 return self.fetchOrderedRemoveContextAdapted(key, ctx, undefined); 1197 } 1198 pub fn fetchOrderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { 1199 self.pointer_stability.lock(); 1200 defer self.pointer_stability.unlock(); 1201 1202 return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); 1203 } 1204 1205 /// If there is an `Entry` with a matching key, it is deleted from 1206 /// the hash map. The entry is removed from the underlying array 1207 /// by swapping it with the last element. Returns true if an entry 1208 /// was removed, false otherwise. 1209 pub fn swapRemove(self: *Self, key: K) bool { 1210 if (@sizeOf(Context) != 0) 1211 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call swapRemoveContext instead."); 1212 return self.swapRemoveContext(key, undefined); 1213 } 1214 pub fn swapRemoveContext(self: *Self, key: K, ctx: Context) bool { 1215 return self.swapRemoveContextAdapted(key, ctx, ctx); 1216 } 1217 pub fn swapRemoveAdapted(self: *Self, key: anytype, ctx: anytype) bool { 1218 if (@sizeOf(ByIndexContext) != 0) 1219 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call swapRemoveContextAdapted instead."); 1220 return self.swapRemoveContextAdapted(key, ctx, undefined); 1221 } 1222 pub fn swapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { 1223 self.pointer_stability.lock(); 1224 defer self.pointer_stability.unlock(); 1225 1226 return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); 1227 } 1228 1229 /// If there is an `Entry` with a matching key, it is deleted from 1230 /// the hash map. The entry is removed from the underlying array 1231 /// by shifting all elements forward, thereby maintaining the 1232 /// current ordering. Returns true if an entry was removed, false otherwise. 1233 pub fn orderedRemove(self: *Self, key: K) bool { 1234 if (@sizeOf(Context) != 0) 1235 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call orderedRemoveContext instead."); 1236 return self.orderedRemoveContext(key, undefined); 1237 } 1238 pub fn orderedRemoveContext(self: *Self, key: K, ctx: Context) bool { 1239 return self.orderedRemoveContextAdapted(key, ctx, ctx); 1240 } 1241 pub fn orderedRemoveAdapted(self: *Self, key: anytype, ctx: anytype) bool { 1242 if (@sizeOf(ByIndexContext) != 0) 1243 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call orderedRemoveContextAdapted instead."); 1244 return self.orderedRemoveContextAdapted(key, ctx, undefined); 1245 } 1246 pub fn orderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { 1247 self.pointer_stability.lock(); 1248 defer self.pointer_stability.unlock(); 1249 1250 return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); 1251 } 1252 1253 /// Deletes the item at the specified index in `entries` from 1254 /// the hash map. The entry is removed from the underlying array 1255 /// by swapping it with the last element. 1256 pub fn swapRemoveAt(self: *Self, index: usize) void { 1257 if (@sizeOf(ByIndexContext) != 0) 1258 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call swapRemoveAtContext instead."); 1259 return self.swapRemoveAtContext(index, undefined); 1260 } 1261 pub fn swapRemoveAtContext(self: *Self, index: usize, ctx: Context) void { 1262 self.pointer_stability.lock(); 1263 defer self.pointer_stability.unlock(); 1264 1265 self.removeByIndex(index, if (store_hash) {} else ctx, .swap); 1266 } 1267 1268 /// Deletes the item at the specified index in `entries` from 1269 /// the hash map. The entry is removed from the underlying array 1270 /// by shifting all elements forward, thereby maintaining the 1271 /// current ordering. 1272 pub fn orderedRemoveAt(self: *Self, index: usize) void { 1273 if (@sizeOf(ByIndexContext) != 0) 1274 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call orderedRemoveAtContext instead."); 1275 return self.orderedRemoveAtContext(index, undefined); 1276 } 1277 pub fn orderedRemoveAtContext(self: *Self, index: usize, ctx: Context) void { 1278 self.pointer_stability.lock(); 1279 defer self.pointer_stability.unlock(); 1280 1281 self.removeByIndex(index, if (store_hash) {} else ctx, .ordered); 1282 } 1283 1284 /// Create a copy of the hash map which can be modified separately. 1285 /// The copy uses the same context as this instance, but is allocated 1286 /// with the provided allocator. 1287 pub fn clone(self: Self, gpa: Allocator) Oom!Self { 1288 if (@sizeOf(ByIndexContext) != 0) 1289 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call cloneContext instead."); 1290 return self.cloneContext(gpa, undefined); 1291 } 1292 pub fn cloneContext(self: Self, gpa: Allocator, ctx: Context) Oom!Self { 1293 var other: Self = .{}; 1294 other.entries = try self.entries.clone(gpa); 1295 errdefer other.entries.deinit(gpa); 1296 1297 if (self.index_header) |header| { 1298 // TODO: I'm pretty sure this could be memcpy'd instead of 1299 // doing all this work. 1300 const new_header = try IndexHeader.alloc(gpa, header.bit_index); 1301 other.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header); 1302 other.index_header = new_header; 1303 } 1304 return other; 1305 } 1306 1307 /// Set the map to an empty state, making deinitialization a no-op, and 1308 /// returning a copy of the original. 1309 pub fn move(self: *Self) Self { 1310 self.pointer_stability.assertUnlocked(); 1311 const result = self.*; 1312 self.* = .empty; 1313 return result; 1314 } 1315 1316 /// Recomputes stored hashes and rebuilds the key indexes. If the 1317 /// underlying keys have been modified directly, call this method to 1318 /// recompute the denormalized metadata necessary for the operation of 1319 /// the methods of this map that lookup entries by key. 1320 /// 1321 /// One use case for this is directly calling `entries.resize()` to grow 1322 /// the underlying storage, and then setting the `keys` and `values` 1323 /// directly without going through the methods of this map. 1324 /// 1325 /// The time complexity of this operation is O(n). 1326 pub fn reIndex(self: *Self, gpa: Allocator) Oom!void { 1327 if (@sizeOf(ByIndexContext) != 0) 1328 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call reIndexContext instead."); 1329 return self.reIndexContext(gpa, undefined); 1330 } 1331 1332 pub fn reIndexContext(self: *Self, gpa: Allocator, ctx: Context) Oom!void { 1333 // Recompute all hashes. 1334 if (store_hash) { 1335 for (self.keys(), self.entries.items(.hash)) |key, *hash| { 1336 const h = checkedHash(ctx, key); 1337 hash.* = h; 1338 } 1339 } 1340 // Rebuild the index. 1341 if (self.entries.capacity > linear_scan_max) { 1342 // We're going to rebuild the index header and replace the existing one (if any). The 1343 // indexes should sized such that they will be at most 60% full. 1344 const bit_index = try IndexHeader.findBitIndex(self.entries.capacity); 1345 const new_header = try IndexHeader.alloc(gpa, bit_index); 1346 if (self.index_header) |header| header.free(gpa); 1347 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header); 1348 self.index_header = new_header; 1349 } 1350 } 1351 1352 /// Sorts the entries and then rebuilds the index. 1353 /// `sort_ctx` must have this method: 1354 /// `fn lessThan(ctx: @TypeOf(ctx), a_index: usize, b_index: usize) bool` 1355 /// Uses a stable sorting algorithm. 1356 pub inline fn sort(self: *Self, sort_ctx: anytype) void { 1357 if (@sizeOf(ByIndexContext) != 0) 1358 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call sortContext instead."); 1359 return sortContextInternal(self, .stable, sort_ctx, undefined); 1360 } 1361 1362 /// Sorts the entries and then rebuilds the index. 1363 /// `sort_ctx` must have this method: 1364 /// `fn lessThan(ctx: @TypeOf(ctx), a_index: usize, b_index: usize) bool` 1365 /// Uses an unstable sorting algorithm. 1366 pub inline fn sortUnstable(self: *Self, sort_ctx: anytype) void { 1367 if (@sizeOf(ByIndexContext) != 0) 1368 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call sortUnstableContext instead."); 1369 return self.sortContextInternal(.unstable, sort_ctx, undefined); 1370 } 1371 1372 pub inline fn sortContext(self: *Self, sort_ctx: anytype, ctx: Context) void { 1373 return sortContextInternal(self, .stable, sort_ctx, ctx); 1374 } 1375 1376 pub inline fn sortUnstableContext(self: *Self, sort_ctx: anytype, ctx: Context) void { 1377 return sortContextInternal(self, .unstable, sort_ctx, ctx); 1378 } 1379 1380 fn sortContextInternal( 1381 self: *Self, 1382 comptime mode: std.sort.Mode, 1383 sort_ctx: anytype, 1384 ctx: Context, 1385 ) void { 1386 self.pointer_stability.lock(); 1387 defer self.pointer_stability.unlock(); 1388 1389 switch (mode) { 1390 .stable => self.entries.sort(sort_ctx), 1391 .unstable => self.entries.sortUnstable(sort_ctx), 1392 } 1393 const header = self.index_header orelse return; 1394 header.reset(); 1395 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, header); 1396 } 1397 1398 /// Shrinks the underlying `Entry` array to `new_len` elements and 1399 /// discards any associated index entries. Keeps capacity the same. 1400 /// 1401 /// Asserts the discarded entries remain initialized and capable of 1402 /// performing hash and equality checks. Any deinitialization of 1403 /// discarded entries must take place *after* calling this function. 1404 pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { 1405 if (@sizeOf(ByIndexContext) != 0) 1406 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call shrinkRetainingCapacityContext instead."); 1407 return self.shrinkRetainingCapacityContext(new_len, undefined); 1408 } 1409 1410 /// Shrinks the underlying `Entry` array to `new_len` elements and 1411 /// discards any associated index entries. Keeps capacity the same. 1412 /// 1413 /// Asserts the discarded entries remain initialized and capable of 1414 /// performing hash and equality checks. Any deinitialization of 1415 /// discarded entries must take place *after* calling this function. 1416 pub fn shrinkRetainingCapacityContext(self: *Self, new_len: usize, ctx: Context) void { 1417 self.pointer_stability.lock(); 1418 defer self.pointer_stability.unlock(); 1419 1420 // Remove index entries from the new length onwards. 1421 // Explicitly choose to ONLY remove index entries and not the underlying array list 1422 // entries as we're going to remove them in the subsequent shrink call. 1423 if (self.index_header) |header| { 1424 var i: usize = new_len; 1425 while (i < self.entries.len) : (i += 1) 1426 self.removeFromIndexByIndex(i, if (store_hash) {} else ctx, header); 1427 } 1428 self.entries.shrinkRetainingCapacity(new_len); 1429 } 1430 1431 /// Shrinks the underlying `Entry` array to `new_len` elements and 1432 /// discards any associated index entries. Reduces allocated capacity. 1433 /// 1434 /// Asserts the discarded entries remain initialized and capable of 1435 /// performing hash and equality checks. It is a bug to call this 1436 /// function if the discarded entries require deinitialization. For 1437 /// that use case, `shrinkRetainingCapacity` can be used instead. 1438 pub fn shrinkAndFree(self: *Self, gpa: Allocator, new_len: usize) void { 1439 if (@sizeOf(ByIndexContext) != 0) 1440 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call shrinkAndFreeContext instead."); 1441 return self.shrinkAndFreeContext(gpa, new_len, undefined); 1442 } 1443 1444 /// Shrinks the underlying `Entry` array to `new_len` elements and 1445 /// discards any associated index entries. Reduces allocated capacity. 1446 /// 1447 /// Asserts the discarded entries remain initialized and capable of 1448 /// performing hash and equality checks. It is a bug to call this 1449 /// function if the discarded entries require deinitialization. For 1450 /// that use case, `shrinkRetainingCapacityContext` can be used 1451 /// instead. 1452 pub fn shrinkAndFreeContext(self: *Self, gpa: Allocator, new_len: usize, ctx: Context) void { 1453 self.pointer_stability.lock(); 1454 defer self.pointer_stability.unlock(); 1455 1456 // Remove index entries from the new length onwards. 1457 // Explicitly choose to ONLY remove index entries and not the underlying array list 1458 // entries as we're going to remove them in the subsequent shrink call. 1459 if (self.index_header) |header| { 1460 var i: usize = new_len; 1461 while (i < self.entries.len) : (i += 1) 1462 self.removeFromIndexByIndex(i, if (store_hash) {} else ctx, header); 1463 } 1464 self.entries.shrinkAndFree(gpa, new_len); 1465 } 1466 1467 /// Removes the last inserted `Entry` in the hash map and returns it. 1468 pub fn pop(self: *Self) KV { 1469 if (@sizeOf(ByIndexContext) != 0) 1470 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call popContext instead."); 1471 return self.popContext(undefined); 1472 } 1473 pub fn popContext(self: *Self, ctx: Context) KV { 1474 self.pointer_stability.lock(); 1475 defer self.pointer_stability.unlock(); 1476 1477 const item = self.entries.get(self.entries.len - 1); 1478 if (self.index_header) |header| 1479 self.removeFromIndexByIndex(self.entries.len - 1, if (store_hash) {} else ctx, header); 1480 self.entries.len -= 1; 1481 return .{ 1482 .key = item.key, 1483 .value = item.value, 1484 }; 1485 } 1486 1487 /// Removes the last inserted `Entry` in the hash map and returns it if count is nonzero. 1488 /// Otherwise returns null. 1489 pub fn popOrNull(self: *Self) ?KV { 1490 if (@sizeOf(ByIndexContext) != 0) 1491 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call popContext instead."); 1492 return self.popOrNullContext(undefined); 1493 } 1494 pub fn popOrNullContext(self: *Self, ctx: Context) ?KV { 1495 return if (self.entries.len == 0) null else self.popContext(ctx); 1496 } 1497 1498 fn fetchRemoveByKey( 1499 self: *Self, 1500 key: anytype, 1501 key_ctx: anytype, 1502 ctx: ByIndexContext, 1503 comptime removal_type: RemovalType, 1504 ) ?KV { 1505 const header = self.index_header orelse { 1506 // Linear scan. 1507 const key_hash = if (store_hash) key_ctx.hash(key) else {}; 1508 const slice = self.entries.slice(); 1509 const hashes_array = if (store_hash) slice.items(.hash) else {}; 1510 const keys_array = slice.items(.key); 1511 for (keys_array, 0..) |*item_key, i| { 1512 const hash_match = if (store_hash) hashes_array[i] == key_hash else true; 1513 if (hash_match and key_ctx.eql(key, item_key.*, i)) { 1514 const removed_entry: KV = .{ 1515 .key = keys_array[i], 1516 .value = slice.items(.value)[i], 1517 }; 1518 switch (removal_type) { 1519 .swap => self.entries.swapRemove(i), 1520 .ordered => self.entries.orderedRemove(i), 1521 } 1522 return removed_entry; 1523 } 1524 } 1525 return null; 1526 }; 1527 return switch (header.capacityIndexType()) { 1528 .u8 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u8, removal_type), 1529 .u16 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u16, removal_type), 1530 .u32 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u32, removal_type), 1531 }; 1532 } 1533 fn fetchRemoveByKeyGeneric( 1534 self: *Self, 1535 key: anytype, 1536 key_ctx: anytype, 1537 ctx: ByIndexContext, 1538 header: *IndexHeader, 1539 comptime I: type, 1540 comptime removal_type: RemovalType, 1541 ) ?KV { 1542 const indexes = header.indexes(I); 1543 const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return null; 1544 const slice = self.entries.slice(); 1545 const removed_entry: KV = .{ 1546 .key = slice.items(.key)[entry_index], 1547 .value = slice.items(.value)[entry_index], 1548 }; 1549 self.removeFromArrayAndUpdateIndex(entry_index, ctx, header, I, indexes, removal_type); 1550 return removed_entry; 1551 } 1552 1553 fn removeByKey( 1554 self: *Self, 1555 key: anytype, 1556 key_ctx: anytype, 1557 ctx: ByIndexContext, 1558 comptime removal_type: RemovalType, 1559 ) bool { 1560 const header = self.index_header orelse { 1561 // Linear scan. 1562 const key_hash = if (store_hash) key_ctx.hash(key) else {}; 1563 const slice = self.entries.slice(); 1564 const hashes_array = if (store_hash) slice.items(.hash) else {}; 1565 const keys_array = slice.items(.key); 1566 for (keys_array, 0..) |*item_key, i| { 1567 const hash_match = if (store_hash) hashes_array[i] == key_hash else true; 1568 if (hash_match and key_ctx.eql(key, item_key.*, i)) { 1569 switch (removal_type) { 1570 .swap => self.entries.swapRemove(i), 1571 .ordered => self.entries.orderedRemove(i), 1572 } 1573 return true; 1574 } 1575 } 1576 return false; 1577 }; 1578 return switch (header.capacityIndexType()) { 1579 .u8 => self.removeByKeyGeneric(key, key_ctx, ctx, header, u8, removal_type), 1580 .u16 => self.removeByKeyGeneric(key, key_ctx, ctx, header, u16, removal_type), 1581 .u32 => self.removeByKeyGeneric(key, key_ctx, ctx, header, u32, removal_type), 1582 }; 1583 } 1584 fn removeByKeyGeneric(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) bool { 1585 const indexes = header.indexes(I); 1586 const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return false; 1587 self.removeFromArrayAndUpdateIndex(entry_index, ctx, header, I, indexes, removal_type); 1588 return true; 1589 } 1590 1591 fn removeByIndex(self: *Self, entry_index: usize, ctx: ByIndexContext, comptime removal_type: RemovalType) void { 1592 assert(entry_index < self.entries.len); 1593 const header = self.index_header orelse { 1594 switch (removal_type) { 1595 .swap => self.entries.swapRemove(entry_index), 1596 .ordered => self.entries.orderedRemove(entry_index), 1597 } 1598 return; 1599 }; 1600 switch (header.capacityIndexType()) { 1601 .u8 => self.removeByIndexGeneric(entry_index, ctx, header, u8, removal_type), 1602 .u16 => self.removeByIndexGeneric(entry_index, ctx, header, u16, removal_type), 1603 .u32 => self.removeByIndexGeneric(entry_index, ctx, header, u32, removal_type), 1604 } 1605 } 1606 fn removeByIndexGeneric(self: *Self, entry_index: usize, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) void { 1607 const indexes = header.indexes(I); 1608 self.removeFromIndexByIndexGeneric(entry_index, ctx, header, I, indexes); 1609 self.removeFromArrayAndUpdateIndex(entry_index, ctx, header, I, indexes, removal_type); 1610 } 1611 1612 fn removeFromArrayAndUpdateIndex(self: *Self, entry_index: usize, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, indexes: []Index(I), comptime removal_type: RemovalType) void { 1613 const last_index = self.entries.len - 1; // overflow => remove from empty map 1614 switch (removal_type) { 1615 .swap => { 1616 if (last_index != entry_index) { 1617 // Because of the swap remove, now we need to update the index that was 1618 // pointing to the last entry and is now pointing to this removed item slot. 1619 self.updateEntryIndex(header, last_index, entry_index, ctx, I, indexes); 1620 } 1621 // updateEntryIndex reads from the old entry index, 1622 // so it needs to run before removal. 1623 self.entries.swapRemove(entry_index); 1624 }, 1625 .ordered => { 1626 var i: usize = entry_index; 1627 while (i < last_index) : (i += 1) { 1628 // Because of the ordered remove, everything from the entry index onwards has 1629 // been shifted forward so we'll need to update the index entries. 1630 self.updateEntryIndex(header, i + 1, i, ctx, I, indexes); 1631 } 1632 // updateEntryIndex reads from the old entry index, 1633 // so it needs to run before removal. 1634 self.entries.orderedRemove(entry_index); 1635 }, 1636 } 1637 } 1638 1639 fn updateEntryIndex( 1640 self: *Self, 1641 header: *IndexHeader, 1642 old_entry_index: usize, 1643 new_entry_index: usize, 1644 ctx: ByIndexContext, 1645 comptime I: type, 1646 indexes: []Index(I), 1647 ) void { 1648 const slot = self.getSlotByIndex(old_entry_index, ctx, header, I, indexes); 1649 indexes[slot].entry_index = @as(I, @intCast(new_entry_index)); 1650 } 1651 1652 fn removeFromIndexByIndex(self: *Self, entry_index: usize, ctx: ByIndexContext, header: *IndexHeader) void { 1653 switch (header.capacityIndexType()) { 1654 .u8 => self.removeFromIndexByIndexGeneric(entry_index, ctx, header, u8, header.indexes(u8)), 1655 .u16 => self.removeFromIndexByIndexGeneric(entry_index, ctx, header, u16, header.indexes(u16)), 1656 .u32 => self.removeFromIndexByIndexGeneric(entry_index, ctx, header, u32, header.indexes(u32)), 1657 } 1658 } 1659 fn removeFromIndexByIndexGeneric(self: *Self, entry_index: usize, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, indexes: []Index(I)) void { 1660 const slot = self.getSlotByIndex(entry_index, ctx, header, I, indexes); 1661 removeSlot(slot, header, I, indexes); 1662 } 1663 1664 fn removeFromIndexByKey(self: *Self, key: anytype, ctx: anytype, header: *IndexHeader, comptime I: type, indexes: []Index(I)) ?usize { 1665 const slot = self.getSlotByKey(key, ctx, header, I, indexes) orelse return null; 1666 const removed_entry_index = indexes[slot].entry_index; 1667 removeSlot(slot, header, I, indexes); 1668 return removed_entry_index; 1669 } 1670 1671 fn removeSlot(removed_slot: usize, header: *IndexHeader, comptime I: type, indexes: []Index(I)) void { 1672 const start_index = removed_slot +% 1; 1673 const end_index = start_index +% indexes.len; 1674 1675 var last_slot = removed_slot; 1676 var index: usize = start_index; 1677 while (index != end_index) : (index +%= 1) { 1678 const slot = header.constrainIndex(index); 1679 const slot_data = indexes[slot]; 1680 if (slot_data.isEmpty() or slot_data.distance_from_start_index == 0) { 1681 indexes[last_slot].setEmpty(); 1682 return; 1683 } 1684 indexes[last_slot] = .{ 1685 .entry_index = slot_data.entry_index, 1686 .distance_from_start_index = slot_data.distance_from_start_index - 1, 1687 }; 1688 last_slot = slot; 1689 } 1690 unreachable; 1691 } 1692 1693 fn getSlotByIndex(self: *Self, entry_index: usize, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, indexes: []Index(I)) usize { 1694 const slice = self.entries.slice(); 1695 const h = if (store_hash) slice.items(.hash)[entry_index] else checkedHash(ctx, slice.items(.key)[entry_index]); 1696 const start_index = safeTruncate(usize, h); 1697 const end_index = start_index +% indexes.len; 1698 1699 var index = start_index; 1700 var distance_from_start_index: I = 0; 1701 while (index != end_index) : ({ 1702 index +%= 1; 1703 distance_from_start_index += 1; 1704 }) { 1705 const slot = header.constrainIndex(index); 1706 const slot_data = indexes[slot]; 1707 1708 // This is the fundamental property of the array hash map index. If this 1709 // assert fails, it probably means that the entry was not in the index. 1710 assert(!slot_data.isEmpty()); 1711 assert(slot_data.distance_from_start_index >= distance_from_start_index); 1712 1713 if (slot_data.entry_index == entry_index) { 1714 return slot; 1715 } 1716 } 1717 unreachable; 1718 } 1719 1720 /// Must `ensureTotalCapacity`/`ensureUnusedCapacity` before calling this. 1721 fn getOrPutInternal(self: *Self, key: anytype, ctx: anytype, header: *IndexHeader, comptime I: type) GetOrPutResult { 1722 const slice = self.entries.slice(); 1723 const hashes_array = if (store_hash) slice.items(.hash) else {}; 1724 const keys_array = slice.items(.key); 1725 const values_array = slice.items(.value); 1726 const indexes = header.indexes(I); 1727 1728 const h = checkedHash(ctx, key); 1729 const start_index = safeTruncate(usize, h); 1730 const end_index = start_index +% indexes.len; 1731 1732 var index = start_index; 1733 var distance_from_start_index: I = 0; 1734 while (index != end_index) : ({ 1735 index +%= 1; 1736 distance_from_start_index += 1; 1737 }) { 1738 var slot = header.constrainIndex(index); 1739 var slot_data = indexes[slot]; 1740 1741 // If the slot is empty, there can be no more items in this run. 1742 // We didn't find a matching item, so this must be new. 1743 // Put it in the empty slot. 1744 if (slot_data.isEmpty()) { 1745 const new_index = self.entries.addOneAssumeCapacity(); 1746 indexes[slot] = .{ 1747 .distance_from_start_index = distance_from_start_index, 1748 .entry_index = @as(I, @intCast(new_index)), 1749 }; 1750 1751 // update the hash if applicable 1752 if (store_hash) hashes_array.ptr[new_index] = h; 1753 1754 return .{ 1755 .found_existing = false, 1756 .key_ptr = &keys_array.ptr[new_index], 1757 // workaround for #6974 1758 .value_ptr = if (@sizeOf(*V) == 0) undefined else &values_array.ptr[new_index], 1759 .index = new_index, 1760 }; 1761 } 1762 1763 // This pointer survives the following append because we call 1764 // entries.ensureTotalCapacity before getOrPutInternal. 1765 const i = slot_data.entry_index; 1766 const hash_match = if (store_hash) h == hashes_array[i] else true; 1767 if (hash_match and checkedEql(ctx, key, keys_array[i], i)) { 1768 return .{ 1769 .found_existing = true, 1770 .key_ptr = &keys_array[slot_data.entry_index], 1771 // workaround for #6974 1772 .value_ptr = if (@sizeOf(*V) == 0) undefined else &values_array[slot_data.entry_index], 1773 .index = slot_data.entry_index, 1774 }; 1775 } 1776 1777 // If the entry is closer to its target than our current distance, 1778 // the entry we are looking for does not exist. It would be in 1779 // this slot instead if it was here. So stop looking, and switch 1780 // to insert mode. 1781 if (slot_data.distance_from_start_index < distance_from_start_index) { 1782 // In this case, we did not find the item. We will put a new entry. 1783 // However, we will use this index for the new entry, and move 1784 // the previous index down the line, to keep the max distance_from_start_index 1785 // as small as possible. 1786 const new_index = self.entries.addOneAssumeCapacity(); 1787 if (store_hash) hashes_array.ptr[new_index] = h; 1788 indexes[slot] = .{ 1789 .entry_index = @as(I, @intCast(new_index)), 1790 .distance_from_start_index = distance_from_start_index, 1791 }; 1792 distance_from_start_index = slot_data.distance_from_start_index; 1793 var displaced_index = slot_data.entry_index; 1794 1795 // Find somewhere to put the index we replaced by shifting 1796 // following indexes backwards. 1797 index +%= 1; 1798 distance_from_start_index += 1; 1799 while (index != end_index) : ({ 1800 index +%= 1; 1801 distance_from_start_index += 1; 1802 }) { 1803 slot = header.constrainIndex(index); 1804 slot_data = indexes[slot]; 1805 if (slot_data.isEmpty()) { 1806 indexes[slot] = .{ 1807 .entry_index = displaced_index, 1808 .distance_from_start_index = distance_from_start_index, 1809 }; 1810 return .{ 1811 .found_existing = false, 1812 .key_ptr = &keys_array.ptr[new_index], 1813 // workaround for #6974 1814 .value_ptr = if (@sizeOf(*V) == 0) undefined else &values_array.ptr[new_index], 1815 .index = new_index, 1816 }; 1817 } 1818 1819 if (slot_data.distance_from_start_index < distance_from_start_index) { 1820 indexes[slot] = .{ 1821 .entry_index = displaced_index, 1822 .distance_from_start_index = distance_from_start_index, 1823 }; 1824 displaced_index = slot_data.entry_index; 1825 distance_from_start_index = slot_data.distance_from_start_index; 1826 } 1827 } 1828 unreachable; 1829 } 1830 } 1831 unreachable; 1832 } 1833 1834 fn getSlotByKey(self: Self, key: anytype, ctx: anytype, header: *IndexHeader, comptime I: type, indexes: []Index(I)) ?usize { 1835 const slice = self.entries.slice(); 1836 const hashes_array = if (store_hash) slice.items(.hash) else {}; 1837 const keys_array = slice.items(.key); 1838 const h = checkedHash(ctx, key); 1839 1840 const start_index = safeTruncate(usize, h); 1841 const end_index = start_index +% indexes.len; 1842 1843 var index = start_index; 1844 var distance_from_start_index: I = 0; 1845 while (index != end_index) : ({ 1846 index +%= 1; 1847 distance_from_start_index += 1; 1848 }) { 1849 const slot = header.constrainIndex(index); 1850 const slot_data = indexes[slot]; 1851 if (slot_data.isEmpty() or slot_data.distance_from_start_index < distance_from_start_index) 1852 return null; 1853 1854 const i = slot_data.entry_index; 1855 const hash_match = if (store_hash) h == hashes_array[i] else true; 1856 if (hash_match and checkedEql(ctx, key, keys_array[i], i)) 1857 return slot; 1858 } 1859 unreachable; 1860 } 1861 1862 fn insertAllEntriesIntoNewHeader(self: *Self, ctx: ByIndexContext, header: *IndexHeader) void { 1863 switch (header.capacityIndexType()) { 1864 .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(ctx, header, u8), 1865 .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(ctx, header, u16), 1866 .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(ctx, header, u32), 1867 } 1868 } 1869 fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, ctx: ByIndexContext, header: *IndexHeader, comptime I: type) void { 1870 const slice = self.entries.slice(); 1871 const items = if (store_hash) slice.items(.hash) else slice.items(.key); 1872 const indexes = header.indexes(I); 1873 1874 entry_loop: for (items, 0..) |key, i| { 1875 const h = if (store_hash) key else checkedHash(ctx, key); 1876 const start_index = safeTruncate(usize, h); 1877 const end_index = start_index +% indexes.len; 1878 var index = start_index; 1879 var entry_index = @as(I, @intCast(i)); 1880 var distance_from_start_index: I = 0; 1881 while (index != end_index) : ({ 1882 index +%= 1; 1883 distance_from_start_index += 1; 1884 }) { 1885 const slot = header.constrainIndex(index); 1886 const next_index = indexes[slot]; 1887 if (next_index.isEmpty()) { 1888 indexes[slot] = .{ 1889 .distance_from_start_index = distance_from_start_index, 1890 .entry_index = entry_index, 1891 }; 1892 continue :entry_loop; 1893 } 1894 if (next_index.distance_from_start_index < distance_from_start_index) { 1895 indexes[slot] = .{ 1896 .distance_from_start_index = distance_from_start_index, 1897 .entry_index = entry_index, 1898 }; 1899 distance_from_start_index = next_index.distance_from_start_index; 1900 entry_index = next_index.entry_index; 1901 } 1902 } 1903 unreachable; 1904 } 1905 } 1906 1907 fn checkedHash(ctx: anytype, key: anytype) u32 { 1908 // If you get a compile error on the next line, it means that your 1909 // generic hash function doesn't accept your key. 1910 return ctx.hash(key); 1911 } 1912 1913 fn checkedEql(ctx: anytype, a: anytype, b: K, b_index: usize) bool { 1914 // If you get a compile error on the next line, it means that your 1915 // generic eql function doesn't accept (self, adapt key, K, index). 1916 return ctx.eql(a, b, b_index); 1917 } 1918 1919 fn dumpState(self: Self, comptime keyFmt: []const u8, comptime valueFmt: []const u8) void { 1920 if (@sizeOf(ByIndexContext) != 0) 1921 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call dumpStateContext instead."); 1922 self.dumpStateContext(keyFmt, valueFmt, undefined); 1923 } 1924 fn dumpStateContext(self: Self, comptime keyFmt: []const u8, comptime valueFmt: []const u8, ctx: Context) void { 1925 const p = std.debug.print; 1926 p("{s}:\n", .{@typeName(Self)}); 1927 const slice = self.entries.slice(); 1928 const hash_status = if (store_hash) "stored" else "computed"; 1929 p(" len={} capacity={} hashes {s}\n", .{ slice.len, slice.capacity, hash_status }); 1930 var i: usize = 0; 1931 const mask: u32 = if (self.index_header) |header| header.mask() else ~@as(u32, 0); 1932 while (i < slice.len) : (i += 1) { 1933 const hash = if (store_hash) slice.items(.hash)[i] else checkedHash(ctx, slice.items(.key)[i]); 1934 if (store_hash) { 1935 p( 1936 " [{}]: key=" ++ keyFmt ++ " value=" ++ valueFmt ++ " hash=0x{x} slot=[0x{x}]\n", 1937 .{ i, slice.items(.key)[i], slice.items(.value)[i], hash, hash & mask }, 1938 ); 1939 } else { 1940 p( 1941 " [{}]: key=" ++ keyFmt ++ " value=" ++ valueFmt ++ " slot=[0x{x}]\n", 1942 .{ i, slice.items(.key)[i], slice.items(.value)[i], hash & mask }, 1943 ); 1944 } 1945 } 1946 if (self.index_header) |header| { 1947 p("\n", .{}); 1948 switch (header.capacityIndexType()) { 1949 .u8 => dumpIndex(header, u8), 1950 .u16 => dumpIndex(header, u16), 1951 .u32 => dumpIndex(header, u32), 1952 } 1953 } 1954 } 1955 fn dumpIndex(header: *IndexHeader, comptime I: type) void { 1956 const p = std.debug.print; 1957 p(" index len=0x{x} type={}\n", .{ header.length(), header.capacityIndexType() }); 1958 const indexes = header.indexes(I); 1959 if (indexes.len == 0) return; 1960 var is_empty = false; 1961 for (indexes, 0..) |idx, i| { 1962 if (idx.isEmpty()) { 1963 is_empty = true; 1964 } else { 1965 if (is_empty) { 1966 is_empty = false; 1967 p(" ...\n", .{}); 1968 } 1969 p(" [0x{x}]: [{}] +{}\n", .{ i, idx.entry_index, idx.distance_from_start_index }); 1970 } 1971 } 1972 if (is_empty) { 1973 p(" ...\n", .{}); 1974 } 1975 } 1976 }; 1977 } 1978 1979 const CapacityIndexType = enum { u8, u16, u32 }; 1980 1981 fn capacityIndexType(bit_index: u8) CapacityIndexType { 1982 if (bit_index <= 8) 1983 return .u8; 1984 if (bit_index <= 16) 1985 return .u16; 1986 assert(bit_index <= 32); 1987 return .u32; 1988 } 1989 1990 fn capacityIndexSize(bit_index: u8) usize { 1991 switch (capacityIndexType(bit_index)) { 1992 .u8 => return @sizeOf(Index(u8)), 1993 .u16 => return @sizeOf(Index(u16)), 1994 .u32 => return @sizeOf(Index(u32)), 1995 } 1996 } 1997 1998 /// @truncate fails if the target type is larger than the 1999 /// target value. This causes problems when one of the types 2000 /// is usize, which may be larger or smaller than u32 on different 2001 /// systems. This version of truncate is safe to use if either 2002 /// parameter has dynamic size, and will perform widening conversion 2003 /// when needed. Both arguments must have the same signedness. 2004 fn safeTruncate(comptime T: type, val: anytype) T { 2005 if (@bitSizeOf(T) >= @bitSizeOf(@TypeOf(val))) 2006 return val; 2007 return @as(T, @truncate(val)); 2008 } 2009 2010 /// A single entry in the lookup acceleration structure. These structs 2011 /// are found in an array after the IndexHeader. Hashes index into this 2012 /// array, and linear probing is used for collisions. 2013 fn Index(comptime I: type) type { 2014 return extern struct { 2015 const Self = @This(); 2016 2017 /// The index of this entry in the backing store. If the index is 2018 /// empty, this is empty_sentinel. 2019 entry_index: I, 2020 2021 /// The distance between this slot and its ideal placement. This is 2022 /// used to keep maximum scan length small. This value is undefined 2023 /// if the index is empty. 2024 distance_from_start_index: I, 2025 2026 /// The special entry_index value marking an empty slot. 2027 const empty_sentinel = ~@as(I, 0); 2028 2029 /// A constant empty index 2030 const empty = Self{ 2031 .entry_index = empty_sentinel, 2032 .distance_from_start_index = undefined, 2033 }; 2034 2035 /// Checks if a slot is empty 2036 fn isEmpty(idx: Self) bool { 2037 return idx.entry_index == empty_sentinel; 2038 } 2039 2040 /// Sets a slot to empty 2041 fn setEmpty(idx: *Self) void { 2042 idx.entry_index = empty_sentinel; 2043 idx.distance_from_start_index = undefined; 2044 } 2045 }; 2046 } 2047 2048 /// the byte size of the index must fit in a usize. This is a power of two 2049 /// length * the size of an Index(u32). The index is 8 bytes (3 bits repr) 2050 /// and max_usize + 1 is not representable, so we need to subtract out 4 bits. 2051 const max_representable_index_len = @bitSizeOf(usize) - 4; 2052 const max_bit_index = @min(32, max_representable_index_len); 2053 const min_bit_index = 5; 2054 const max_capacity = (1 << max_bit_index) - 1; 2055 const index_capacities = blk: { 2056 var caps: [max_bit_index + 1]u32 = undefined; 2057 for (caps[0..max_bit_index], 0..) |*item, i| { 2058 item.* = (1 << i) * 3 / 5; 2059 } 2060 caps[max_bit_index] = max_capacity; 2061 break :blk caps; 2062 }; 2063 2064 /// This struct is trailed by two arrays of length indexes_len 2065 /// of integers, whose integer size is determined by indexes_len. 2066 /// These arrays are indexed by constrainIndex(hash). The 2067 /// entryIndexes array contains the index in the dense backing store 2068 /// where the entry's data can be found. Entries which are not in 2069 /// use have their index value set to emptySentinel(I). 2070 /// The entryDistances array stores the distance between an entry 2071 /// and its ideal hash bucket. This is used when adding elements 2072 /// to balance the maximum scan length. 2073 const IndexHeader = struct { 2074 /// This field tracks the total number of items in the arrays following 2075 /// this header. It is the bit index of the power of two number of indices. 2076 /// This value is between min_bit_index and max_bit_index, inclusive. 2077 bit_index: u8 align(@alignOf(u32)), 2078 2079 /// Map from an incrementing index to an index slot in the attached arrays. 2080 fn constrainIndex(header: IndexHeader, i: usize) usize { 2081 // This is an optimization for modulo of power of two integers; 2082 // it requires `indexes_len` to always be a power of two. 2083 return @as(usize, @intCast(i & header.mask())); 2084 } 2085 2086 /// Returns the attached array of indexes. I must match the type 2087 /// returned by capacityIndexType. 2088 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) { 2089 const start_ptr: [*]Index(I) = @alignCast(@ptrCast(@as([*]u8, @ptrCast(header)) + @sizeOf(IndexHeader))); 2090 return start_ptr[0..header.length()]; 2091 } 2092 2093 /// Returns the type used for the index arrays. 2094 fn capacityIndexType(header: IndexHeader) CapacityIndexType { 2095 return hash_map.capacityIndexType(header.bit_index); 2096 } 2097 2098 fn capacity(self: IndexHeader) u32 { 2099 return index_capacities[self.bit_index]; 2100 } 2101 fn length(self: IndexHeader) usize { 2102 return @as(usize, 1) << @as(math.Log2Int(usize), @intCast(self.bit_index)); 2103 } 2104 fn mask(self: IndexHeader) u32 { 2105 return @as(u32, @intCast(self.length() - 1)); 2106 } 2107 2108 fn findBitIndex(desired_capacity: usize) Allocator.Error!u8 { 2109 if (desired_capacity > max_capacity) return error.OutOfMemory; 2110 var new_bit_index = @as(u8, @intCast(std.math.log2_int_ceil(usize, desired_capacity))); 2111 if (desired_capacity > index_capacities[new_bit_index]) new_bit_index += 1; 2112 if (new_bit_index < min_bit_index) new_bit_index = min_bit_index; 2113 assert(desired_capacity <= index_capacities[new_bit_index]); 2114 return new_bit_index; 2115 } 2116 2117 /// Allocates an index header, and fills the entryIndexes array with empty. 2118 /// The distance array contents are undefined. 2119 fn alloc(gpa: Allocator, new_bit_index: u8) Allocator.Error!*IndexHeader { 2120 const len = @as(usize, 1) << @as(math.Log2Int(usize), @intCast(new_bit_index)); 2121 const index_size = hash_map.capacityIndexSize(new_bit_index); 2122 const nbytes = @sizeOf(IndexHeader) + index_size * len; 2123 const bytes = try gpa.alignedAlloc(u8, @alignOf(IndexHeader), nbytes); 2124 @memset(bytes[@sizeOf(IndexHeader)..], 0xff); 2125 const result: *IndexHeader = @alignCast(@ptrCast(bytes.ptr)); 2126 result.* = .{ 2127 .bit_index = new_bit_index, 2128 }; 2129 return result; 2130 } 2131 2132 /// Releases the memory for a header and its associated arrays. 2133 fn free(header: *IndexHeader, gpa: Allocator) void { 2134 const index_size = hash_map.capacityIndexSize(header.bit_index); 2135 const ptr: [*]align(@alignOf(IndexHeader)) u8 = @ptrCast(header); 2136 const slice = ptr[0 .. @sizeOf(IndexHeader) + header.length() * index_size]; 2137 gpa.free(slice); 2138 } 2139 2140 /// Puts an IndexHeader into the state that it would be in after being freshly allocated. 2141 fn reset(header: *IndexHeader) void { 2142 const index_size = hash_map.capacityIndexSize(header.bit_index); 2143 const ptr: [*]align(@alignOf(IndexHeader)) u8 = @ptrCast(header); 2144 const nbytes = @sizeOf(IndexHeader) + header.length() * index_size; 2145 @memset(ptr[@sizeOf(IndexHeader)..nbytes], 0xff); 2146 } 2147 2148 // Verify that the header has sufficient alignment to produce aligned arrays. 2149 comptime { 2150 if (@alignOf(u32) > @alignOf(IndexHeader)) 2151 @compileError("IndexHeader must have a larger alignment than its indexes!"); 2152 } 2153 }; 2154 2155 test "basic hash map usage" { 2156 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2157 defer map.deinit(); 2158 2159 try testing.expect((try map.fetchPut(1, 11)) == null); 2160 try testing.expect((try map.fetchPut(2, 22)) == null); 2161 try testing.expect((try map.fetchPut(3, 33)) == null); 2162 try testing.expect((try map.fetchPut(4, 44)) == null); 2163 2164 try map.putNoClobber(5, 55); 2165 try testing.expect((try map.fetchPut(5, 66)).?.value == 55); 2166 try testing.expect((try map.fetchPut(5, 55)).?.value == 66); 2167 2168 const gop1 = try map.getOrPut(5); 2169 try testing.expect(gop1.found_existing == true); 2170 try testing.expect(gop1.value_ptr.* == 55); 2171 try testing.expect(gop1.index == 4); 2172 gop1.value_ptr.* = 77; 2173 try testing.expect(map.getEntry(5).?.value_ptr.* == 77); 2174 2175 const gop2 = try map.getOrPut(99); 2176 try testing.expect(gop2.found_existing == false); 2177 try testing.expect(gop2.index == 5); 2178 gop2.value_ptr.* = 42; 2179 try testing.expect(map.getEntry(99).?.value_ptr.* == 42); 2180 2181 const gop3 = try map.getOrPutValue(5, 5); 2182 try testing.expect(gop3.value_ptr.* == 77); 2183 2184 const gop4 = try map.getOrPutValue(100, 41); 2185 try testing.expect(gop4.value_ptr.* == 41); 2186 2187 try testing.expect(map.contains(2)); 2188 try testing.expect(map.getEntry(2).?.value_ptr.* == 22); 2189 try testing.expect(map.get(2).? == 22); 2190 2191 const rmv1 = map.fetchSwapRemove(2); 2192 try testing.expect(rmv1.?.key == 2); 2193 try testing.expect(rmv1.?.value == 22); 2194 try testing.expect(map.fetchSwapRemove(2) == null); 2195 try testing.expect(map.swapRemove(2) == false); 2196 try testing.expect(map.getEntry(2) == null); 2197 try testing.expect(map.get(2) == null); 2198 2199 // Since we've used `swapRemove` above, the index of this entry should remain unchanged. 2200 try testing.expect(map.getIndex(100).? == 1); 2201 const gop5 = try map.getOrPut(5); 2202 try testing.expect(gop5.found_existing == true); 2203 try testing.expect(gop5.value_ptr.* == 77); 2204 try testing.expect(gop5.index == 4); 2205 2206 // Whereas, if we do an `orderedRemove`, it should move the index forward one spot. 2207 const rmv2 = map.fetchOrderedRemove(100); 2208 try testing.expect(rmv2.?.key == 100); 2209 try testing.expect(rmv2.?.value == 41); 2210 try testing.expect(map.fetchOrderedRemove(100) == null); 2211 try testing.expect(map.orderedRemove(100) == false); 2212 try testing.expect(map.getEntry(100) == null); 2213 try testing.expect(map.get(100) == null); 2214 const gop6 = try map.getOrPut(5); 2215 try testing.expect(gop6.found_existing == true); 2216 try testing.expect(gop6.value_ptr.* == 77); 2217 try testing.expect(gop6.index == 3); 2218 2219 try testing.expect(map.swapRemove(3)); 2220 } 2221 2222 test "iterator hash map" { 2223 var reset_map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2224 defer reset_map.deinit(); 2225 2226 // test ensureTotalCapacity with a 0 parameter 2227 try reset_map.ensureTotalCapacity(0); 2228 2229 try reset_map.putNoClobber(0, 11); 2230 try reset_map.putNoClobber(1, 22); 2231 try reset_map.putNoClobber(2, 33); 2232 2233 const keys = [_]i32{ 2234 0, 2, 1, 2235 }; 2236 2237 const values = [_]i32{ 2238 11, 33, 22, 2239 }; 2240 2241 var buffer = [_]i32{ 2242 0, 0, 0, 2243 }; 2244 2245 var it = reset_map.iterator(); 2246 const first_entry = it.next().?; 2247 it.reset(); 2248 2249 var count: usize = 0; 2250 while (it.next()) |entry| : (count += 1) { 2251 buffer[@as(usize, @intCast(entry.key_ptr.*))] = entry.value_ptr.*; 2252 } 2253 try testing.expect(count == 3); 2254 try testing.expect(it.next() == null); 2255 2256 for (buffer, 0..) |_, i| { 2257 try testing.expect(buffer[@as(usize, @intCast(keys[i]))] == values[i]); 2258 } 2259 2260 it.reset(); 2261 count = 0; 2262 while (it.next()) |entry| { 2263 buffer[@as(usize, @intCast(entry.key_ptr.*))] = entry.value_ptr.*; 2264 count += 1; 2265 if (count >= 2) break; 2266 } 2267 2268 for (buffer[0..2], 0..) |_, i| { 2269 try testing.expect(buffer[@as(usize, @intCast(keys[i]))] == values[i]); 2270 } 2271 2272 it.reset(); 2273 const entry = it.next().?; 2274 try testing.expect(entry.key_ptr.* == first_entry.key_ptr.*); 2275 try testing.expect(entry.value_ptr.* == first_entry.value_ptr.*); 2276 } 2277 2278 test "ensure capacity" { 2279 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2280 defer map.deinit(); 2281 2282 try map.ensureTotalCapacity(20); 2283 const initial_capacity = map.capacity(); 2284 try testing.expect(initial_capacity >= 20); 2285 var i: i32 = 0; 2286 while (i < 20) : (i += 1) { 2287 try testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null); 2288 } 2289 // shouldn't resize from putAssumeCapacity 2290 try testing.expect(initial_capacity == map.capacity()); 2291 } 2292 2293 test "ensure capacity leak" { 2294 try testing.checkAllAllocationFailures(std.testing.allocator, struct { 2295 pub fn f(allocator: Allocator) !void { 2296 var map = AutoArrayHashMap(i32, i32).init(allocator); 2297 defer map.deinit(); 2298 2299 var i: i32 = 0; 2300 // put more than `linear_scan_max` in so index_header gets allocated. 2301 while (i <= 20) : (i += 1) try map.put(i, i); 2302 } 2303 }.f, .{}); 2304 } 2305 2306 test "big map" { 2307 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2308 defer map.deinit(); 2309 2310 var i: i32 = 0; 2311 while (i < 8) : (i += 1) { 2312 try map.put(i, i + 10); 2313 } 2314 2315 i = 0; 2316 while (i < 8) : (i += 1) { 2317 try testing.expectEqual(@as(?i32, i + 10), map.get(i)); 2318 } 2319 while (i < 16) : (i += 1) { 2320 try testing.expectEqual(@as(?i32, null), map.get(i)); 2321 } 2322 2323 i = 4; 2324 while (i < 12) : (i += 1) { 2325 try map.put(i, i + 12); 2326 } 2327 2328 i = 0; 2329 while (i < 4) : (i += 1) { 2330 try testing.expectEqual(@as(?i32, i + 10), map.get(i)); 2331 } 2332 while (i < 12) : (i += 1) { 2333 try testing.expectEqual(@as(?i32, i + 12), map.get(i)); 2334 } 2335 while (i < 16) : (i += 1) { 2336 try testing.expectEqual(@as(?i32, null), map.get(i)); 2337 } 2338 2339 i = 0; 2340 while (i < 4) : (i += 1) { 2341 try testing.expect(map.orderedRemove(i)); 2342 } 2343 while (i < 8) : (i += 1) { 2344 try testing.expect(map.swapRemove(i)); 2345 } 2346 2347 i = 0; 2348 while (i < 8) : (i += 1) { 2349 try testing.expectEqual(@as(?i32, null), map.get(i)); 2350 } 2351 while (i < 12) : (i += 1) { 2352 try testing.expectEqual(@as(?i32, i + 12), map.get(i)); 2353 } 2354 while (i < 16) : (i += 1) { 2355 try testing.expectEqual(@as(?i32, null), map.get(i)); 2356 } 2357 } 2358 2359 test "clone" { 2360 var original = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2361 defer original.deinit(); 2362 2363 // put more than `linear_scan_max` so we can test that the index header is properly cloned 2364 var i: u8 = 0; 2365 while (i < 10) : (i += 1) { 2366 try original.putNoClobber(i, i * 10); 2367 } 2368 2369 var copy = try original.clone(); 2370 defer copy.deinit(); 2371 2372 i = 0; 2373 while (i < 10) : (i += 1) { 2374 try testing.expect(original.get(i).? == i * 10); 2375 try testing.expect(copy.get(i).? == i * 10); 2376 try testing.expect(original.getPtr(i).? != copy.getPtr(i).?); 2377 } 2378 2379 while (i < 20) : (i += 1) { 2380 try testing.expect(original.get(i) == null); 2381 try testing.expect(copy.get(i) == null); 2382 } 2383 } 2384 2385 test "shrink" { 2386 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2387 defer map.deinit(); 2388 2389 // This test is more interesting if we insert enough entries to allocate the index header. 2390 const num_entries = 20; 2391 var i: i32 = 0; 2392 while (i < num_entries) : (i += 1) 2393 try testing.expect((try map.fetchPut(i, i * 10)) == null); 2394 2395 try testing.expect(map.unmanaged.index_header != null); 2396 try testing.expect(map.count() == num_entries); 2397 2398 // Test `shrinkRetainingCapacity`. 2399 map.shrinkRetainingCapacity(17); 2400 try testing.expect(map.count() == 17); 2401 try testing.expect(map.capacity() == 20); 2402 i = 0; 2403 while (i < num_entries) : (i += 1) { 2404 const gop = try map.getOrPut(i); 2405 if (i < 17) { 2406 try testing.expect(gop.found_existing == true); 2407 try testing.expect(gop.value_ptr.* == i * 10); 2408 } else try testing.expect(gop.found_existing == false); 2409 } 2410 2411 // Test `shrinkAndFree`. 2412 map.shrinkAndFree(15); 2413 try testing.expect(map.count() == 15); 2414 try testing.expect(map.capacity() == 15); 2415 i = 0; 2416 while (i < num_entries) : (i += 1) { 2417 const gop = try map.getOrPut(i); 2418 if (i < 15) { 2419 try testing.expect(gop.found_existing == true); 2420 try testing.expect(gop.value_ptr.* == i * 10); 2421 } else try testing.expect(gop.found_existing == false); 2422 } 2423 } 2424 2425 test "pop" { 2426 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2427 defer map.deinit(); 2428 2429 // Insert just enough entries so that the map expands. Afterwards, 2430 // pop all entries out of the map. 2431 2432 var i: i32 = 0; 2433 while (i < 9) : (i += 1) { 2434 try testing.expect((try map.fetchPut(i, i)) == null); 2435 } 2436 2437 while (i > 0) : (i -= 1) { 2438 const pop = map.pop(); 2439 try testing.expect(pop.key == i - 1 and pop.value == i - 1); 2440 } 2441 } 2442 2443 test "popOrNull" { 2444 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2445 defer map.deinit(); 2446 2447 // Insert just enough entries so that the map expands. Afterwards, 2448 // pop all entries out of the map. 2449 2450 var i: i32 = 0; 2451 while (i < 9) : (i += 1) { 2452 try testing.expect((try map.fetchPut(i, i)) == null); 2453 } 2454 2455 while (map.popOrNull()) |pop| { 2456 try testing.expect(pop.key == i - 1 and pop.value == i - 1); 2457 i -= 1; 2458 } 2459 2460 try testing.expect(map.count() == 0); 2461 } 2462 2463 test "reIndex" { 2464 var map = ArrayHashMap(i32, i32, AutoContext(i32), true).init(std.testing.allocator); 2465 defer map.deinit(); 2466 2467 // Populate via the API. 2468 const num_indexed_entries = 20; 2469 var i: i32 = 0; 2470 while (i < num_indexed_entries) : (i += 1) 2471 try testing.expect((try map.fetchPut(i, i * 10)) == null); 2472 2473 // Make sure we allocated an index header. 2474 try testing.expect(map.unmanaged.index_header != null); 2475 2476 // Now write to the arrays directly. 2477 const num_unindexed_entries = 20; 2478 try map.unmanaged.entries.resize(std.testing.allocator, num_indexed_entries + num_unindexed_entries); 2479 for (map.keys()[num_indexed_entries..], map.values()[num_indexed_entries..], num_indexed_entries..) |*key, *value, j| { 2480 key.* = @intCast(j); 2481 value.* = @intCast(j * 10); 2482 } 2483 2484 // After reindexing, we should see everything. 2485 try map.reIndex(); 2486 i = 0; 2487 while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) { 2488 const gop = try map.getOrPut(i); 2489 try testing.expect(gop.found_existing == true); 2490 try testing.expect(gop.value_ptr.* == i * 10); 2491 try testing.expect(gop.index == i); 2492 } 2493 } 2494 2495 test "auto store_hash" { 2496 const HasCheapEql = AutoArrayHashMap(i32, i32); 2497 const HasExpensiveEql = AutoArrayHashMap([32]i32, i32); 2498 try testing.expect(std.meta.fieldInfo(HasCheapEql.Data, .hash).type == void); 2499 try testing.expect(std.meta.fieldInfo(HasExpensiveEql.Data, .hash).type != void); 2500 2501 const HasCheapEqlUn = AutoArrayHashMapUnmanaged(i32, i32); 2502 const HasExpensiveEqlUn = AutoArrayHashMapUnmanaged([32]i32, i32); 2503 try testing.expect(std.meta.fieldInfo(HasCheapEqlUn.Data, .hash).type == void); 2504 try testing.expect(std.meta.fieldInfo(HasExpensiveEqlUn.Data, .hash).type != void); 2505 } 2506 2507 test "sort" { 2508 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); 2509 defer map.deinit(); 2510 2511 for ([_]i32{ 8, 3, 12, 10, 2, 4, 9, 5, 6, 13, 14, 15, 16, 1, 11, 17, 7 }) |x| { 2512 try map.put(x, x * 3); 2513 } 2514 2515 const C = struct { 2516 keys: []i32, 2517 2518 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { 2519 return ctx.keys[a_index] < ctx.keys[b_index]; 2520 } 2521 }; 2522 2523 map.sort(C{ .keys = map.keys() }); 2524 2525 var x: i32 = 1; 2526 for (map.keys(), 0..) |key, i| { 2527 try testing.expect(key == x); 2528 try testing.expect(map.values()[i] == x * 3); 2529 x += 1; 2530 } 2531 } 2532 2533 test "0 sized key" { 2534 var map = AutoArrayHashMap(u0, i32).init(std.testing.allocator); 2535 defer map.deinit(); 2536 2537 try testing.expectEqual(map.get(0), null); 2538 2539 try map.put(0, 5); 2540 try testing.expectEqual(map.get(0), 5); 2541 2542 try map.put(0, 10); 2543 try testing.expectEqual(map.get(0), 10); 2544 2545 try testing.expectEqual(map.swapRemove(0), true); 2546 try testing.expectEqual(map.get(0), null); 2547 } 2548 2549 test "0 sized key and 0 sized value" { 2550 var map = AutoArrayHashMap(u0, u0).init(std.testing.allocator); 2551 defer map.deinit(); 2552 2553 try testing.expectEqual(map.get(0), null); 2554 2555 try map.put(0, 0); 2556 try testing.expectEqual(map.get(0), 0); 2557 2558 try testing.expectEqual(map.swapRemove(0), true); 2559 try testing.expectEqual(map.get(0), null); 2560 } 2561 2562 pub fn getHashPtrAddrFn(comptime K: type, comptime Context: type) (fn (Context, K) u32) { 2563 return struct { 2564 fn hash(ctx: Context, key: K) u32 { 2565 _ = ctx; 2566 return getAutoHashFn(usize, void)({}, @intFromPtr(key)); 2567 } 2568 }.hash; 2569 } 2570 2571 pub fn getTrivialEqlFn(comptime K: type, comptime Context: type) (fn (Context, K, K) bool) { 2572 return struct { 2573 fn eql(ctx: Context, a: K, b: K) bool { 2574 _ = ctx; 2575 return a == b; 2576 } 2577 }.eql; 2578 } 2579 2580 pub fn AutoContext(comptime K: type) type { 2581 return struct { 2582 pub const hash = getAutoHashFn(K, @This()); 2583 pub const eql = getAutoEqlFn(K, @This()); 2584 }; 2585 } 2586 2587 pub fn getAutoHashFn(comptime K: type, comptime Context: type) (fn (Context, K) u32) { 2588 return struct { 2589 fn hash(ctx: Context, key: K) u32 { 2590 _ = ctx; 2591 if (std.meta.hasUniqueRepresentation(K)) { 2592 return @truncate(Wyhash.hash(0, std.mem.asBytes(&key))); 2593 } else { 2594 var hasher = Wyhash.init(0); 2595 autoHash(&hasher, key); 2596 return @truncate(hasher.final()); 2597 } 2598 } 2599 }.hash; 2600 } 2601 2602 pub fn getAutoEqlFn(comptime K: type, comptime Context: type) (fn (Context, K, K, usize) bool) { 2603 return struct { 2604 fn eql(ctx: Context, a: K, b: K, b_index: usize) bool { 2605 _ = b_index; 2606 _ = ctx; 2607 return std.meta.eql(a, b); 2608 } 2609 }.eql; 2610 } 2611 2612 pub fn autoEqlIsCheap(comptime K: type) bool { 2613 return switch (@typeInfo(K)) { 2614 .bool, 2615 .int, 2616 .float, 2617 .pointer, 2618 .comptime_float, 2619 .comptime_int, 2620 .@"enum", 2621 .@"fn", 2622 .error_set, 2623 .@"anyframe", 2624 .enum_literal, 2625 => true, 2626 else => false, 2627 }; 2628 } 2629 2630 pub fn getAutoHashStratFn(comptime K: type, comptime Context: type, comptime strategy: std.hash.Strategy) (fn (Context, K) u32) { 2631 return struct { 2632 fn hash(ctx: Context, key: K) u32 { 2633 _ = ctx; 2634 var hasher = Wyhash.init(0); 2635 std.hash.autoHashStrat(&hasher, key, strategy); 2636 return @as(u32, @truncate(hasher.final())); 2637 } 2638 }.hash; 2639 }