authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-03 23:57:24+00:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-05 21:11:42+00:00
logb3b6ccba50ef7a683ad05546cba2b71e7d10489f
treed49e5c0de641a60c3b8276a8e2afab014acf0289
parent70dca0a0c6fd27bc39ac3a37edd2a6908bc0198f

reimplement std.HashMap

* breaking changes to the API. Some of the weird decisions from before are changed to what would be more expected. - `get` returns `?V`, use `getEntry` for the old API. - `put` returns `!void`, use `fetchPut` for the old API. * HashMap now has a comptime parameter of whether to store hashes with entries. AutoHashMap has heuristics on whether to set this parameter. For example, for integers, it is false, since equality checking is cheap, but for strings, it is true, since equality checking is probably expensive. * The implementation has a separate array for entry_index / distance_from_start_index. Entries no longer has holes; it is an ArrayList, and iteration is simpler and more cache coherent. This is inspired by Python's new dictionaries. * HashMap is separated into an "unmanaged" and a "managed" API. The unmanaged API is where the actual implementation is; the managed API wraps it and provides a more convenient API, storing the allocator. * Memory usage: When there are less than or equal to 8 entries, HashMap now incurs only a single pointer-size integer as overhead, opposed to using an ArrayList. * Since the entries array is separate from the indexes array, the holes in the indexes array take up less room than the holes in the entries array otherwise would. However the entries array also allocates additional capacity for appending into the array. * HashMap now maintains insertion order. Deletion performs a "swap remove". It's now possible to modify the HashMap while iterating.

3 files changed, 766 insertions(+), 310 deletions(-)

lib/std/array_list.zig+16
...@@ -210,6 +210,14 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -210,6 +210,14 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
210 self.capacity = new_len;210 self.capacity = new_len;
211 }211 }
212212
213 /// Reduce length to `new_len`.
214 /// Invalidates element pointers.
215 /// Keeps capacity the same.
216 pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
217 assert(new_len <= self.items.len);
218 self.items.len = new_len;
219 }
220
213 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {221 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
214 var better_capacity = self.capacity;222 var better_capacity = self.capacity;
215 if (better_capacity >= new_capacity) return;223 if (better_capacity >= new_capacity) return;
...@@ -432,6 +440,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -432,6 +440,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
432 self.capacity = new_len;440 self.capacity = new_len;
433 }441 }
434442
443 /// Reduce length to `new_len`.
444 /// Invalidates element pointers.
445 /// Keeps capacity the same.
446 pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
447 assert(new_len <= self.items.len);
448 self.items.len = new_len;
449 }
450
435 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {451 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
436 var better_capacity = self.capacity;452 var better_capacity = self.capacity;
437 if (better_capacity >= new_capacity) return;453 if (better_capacity >= new_capacity) return;
lib/std/debug.zig+1-1
...@@ -1278,7 +1278,7 @@ pub const DebugInfo = struct {...@@ -1278,7 +1278,7 @@ pub const DebugInfo = struct {
1278 else => return error.MissingDebugInfo,1278 else => return error.MissingDebugInfo,
1279 }1279 }
12801280
1281 if (self.address_map.getValue(ctx.base_address)) |obj_di| {1281 if (self.address_map.get(ctx.base_address)) |obj_di| {
1282 return obj_di;1282 return obj_di;
1283 }1283 }
12841284
lib/std/hash_map.zig+749-309
...@@ -9,17 +9,15 @@ const autoHash = std.hash.autoHash;...@@ -9,17 +9,15 @@ const autoHash = std.hash.autoHash;
9const Wyhash = std.hash.Wyhash;9const Wyhash = std.hash.Wyhash;
10const Allocator = mem.Allocator;10const Allocator = mem.Allocator;
11const builtin = @import("builtin");11const builtin = @import("builtin");
1212const hash_map = @This();
13const want_modification_safety = std.debug.runtime_safety;
14const debug_u32 = if (want_modification_safety) u32 else void;
1513
16pub fn AutoHashMap(comptime K: type, comptime V: type) type {14pub fn AutoHashMap(comptime K: type, comptime V: type) type {
17 return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K));15 return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K));
18}16}
1917
20/// Builtin hashmap for strings as keys.18/// Builtin hashmap for strings as keys.
21pub fn StringHashMap(comptime V: type) type {19pub fn StringHashMap(comptime V: type) type {
22 return HashMap([]const u8, V, hashString, eqlString);20 return HashMap([]const u8, V, hashString, eqlString, true);
23}21}
2422
25pub fn eqlString(a: []const u8, b: []const u8) bool {23pub fn eqlString(a: []const u8, b: []const u8) bool {
...@@ -30,422 +28,846 @@ pub fn hashString(s: []const u8) u32 {...@@ -30,422 +28,846 @@ pub fn hashString(s: []const u8) u32 {
30 return @truncate(u32, std.hash.Wyhash.hash(0, s));28 return @truncate(u32, std.hash.Wyhash.hash(0, s));
31}29}
3230
33pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u32, comptime eql: fn (a: K, b: K) bool) type {31/// Insertion order is preserved.
32/// Deletions perform a "swap removal" on the entries list.
33/// Modifying the hash map while iterating is allowed, however one must understand
34/// the (well defined) behavior when mixing insertions and deletions with iteration.
35/// For a hash map that can be initialized directly that does not store an Allocator
36/// field, see `HashMapUnmanaged`.
37/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`
38/// functions. It does not store each item's hash in the table. Setting `store_hash`
39/// to `true` incurs slightly more memory cost by storing each key's hash in the table
40/// but only has to call `eql` for hash collisions.
41pub fn HashMap(
42 comptime K: type,
43 comptime V: type,
44 comptime hash: fn (key: K) u32,
45 comptime eql: fn (a: K, b: K) bool,
46 comptime store_hash: bool,
47) type {
34 return struct {48 return struct {
35 entries: []Entry,49 unmanaged: Unmanaged,
36 size: usize,
37 max_distance_from_start_index: usize,
38 allocator: *Allocator,50 allocator: *Allocator,
3951
40 /// This is used to detect bugs where a hashtable is edited while an iterator is running.52 pub const Unmanaged = HashMapUnmanaged(K, V, hash, eql, store_hash);
41 modification_count: debug_u32,53 pub const Entry = Unmanaged.Entry;
4254 pub const Hash = Unmanaged.Hash;
43 const Self = @This();55 pub const GetOrPutResult = Unmanaged.GetOrPutResult;
44
45 /// A *KV is a mutable pointer into this HashMap's internal storage.
46 /// Modifying the key is undefined behavior.
47 /// Modifying the value is harmless.
48 /// *KV pointers become invalid whenever this HashMap is modified,
49 /// and then any access to the *KV is undefined behavior.
50 pub const KV = struct {
51 key: K,
52 value: V,
53 };
54
55 const Entry = struct {
56 used: bool,
57 distance_from_start_index: usize,
58 kv: KV,
59 };
60
61 pub const GetOrPutResult = struct {
62 kv: *KV,
63 found_existing: bool,
64 };
6556
57 /// Deprecated. Iterate using `items`.
66 pub const Iterator = struct {58 pub const Iterator = struct {
67 hm: *const Self,59 hm: *const Self,
68 // how many items have we returned60 /// Iterator through the entry array.
69 count: usize,
70 // iterator through the entry array
71 index: usize,61 index: usize,
72 // used to detect concurrent modification
73 initial_modification_count: debug_u32,
7462
75 pub fn next(it: *Iterator) ?*KV {63 pub fn next(it: *Iterator) ?*Entry {
76 if (want_modification_safety) {64 if (it.index >= it.hm.unmanaged.entries.items.len) return null;
77 assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification65 const result = &it.hm.unmanaged.entries.items[it.index];
78 }66 it.index += 1;
79 if (it.count >= it.hm.size) return null;67 return result;
80 while (it.index < it.hm.entries.len) : (it.index += 1) {
81 const entry = &it.hm.entries[it.index];
82 if (entry.used) {
83 it.index += 1;
84 it.count += 1;
85 return &entry.kv;
86 }
87 }
88 unreachable; // no next item
89 }68 }
9069
91 // Reset the iterator to the initial index70 /// Reset the iterator to the initial index
92 pub fn reset(it: *Iterator) void {71 pub fn reset(it: *Iterator) void {
93 it.count = 0;
94 it.index = 0;72 it.index = 0;
95 // Resetting the modification count too
96 it.initial_modification_count = it.hm.modification_count;
97 }73 }
98 };74 };
9975
76 const Self = @This();
77 const Index = Unmanaged.Index;
78
100 pub fn init(allocator: *Allocator) Self {79 pub fn init(allocator: *Allocator) Self {
101 return Self{80 return .{
102 .entries = &[_]Entry{},81 .unmanaged = .{},
103 .allocator = allocator,82 .allocator = allocator,
104 .size = 0,
105 .max_distance_from_start_index = 0,
106 .modification_count = if (want_modification_safety) 0 else {},
107 };83 };
108 }84 }
10985
110 pub fn deinit(hm: Self) void {86 pub fn deinit(self: *Self) void {
111 hm.allocator.free(hm.entries);87 self.unmanaged.deinit(self.allocator);
88 self.* = undefined;
112 }89 }
11390
114 pub fn clear(hm: *Self) void {91 pub fn clearRetainingCapacity(self: *Self) void {
115 for (hm.entries) |*entry| {92 return self.unmanaged.clearRetainingCapacity();
116 entry.used = false;
117 }
118 hm.size = 0;
119 hm.max_distance_from_start_index = 0;
120 hm.incrementModificationCount();
121 }93 }
12294
95 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
96 return self.unmanaged.clearAndFree(self.allocator);
97 }
98
99 /// Deprecated. Use `items().len`.
123 pub fn count(self: Self) usize {100 pub fn count(self: Self) usize {
124 return self.size;101 return self.items().len;
102 }
103
104 /// Deprecated. Iterate using `items`.
105 pub fn iterator(self: *const Self) Iterator {
106 return Iterator{
107 .hm = self,
108 .index = 0,
109 };
125 }110 }
126111
127 /// If key exists this function cannot fail.112 /// If key exists this function cannot fail.
128 /// If there is an existing item with `key`, then the result113 /// If there is an existing item with `key`, then the result
129 /// kv pointer points to it, and found_existing is true.114 /// `Entry` pointer points to it, and found_existing is true.
130 /// Otherwise, puts a new item with undefined value, and115 /// Otherwise, puts a new item with undefined value, and
131 /// the kv pointer points to it. Caller should then initialize116 /// the `Entry` pointer points to it. Caller should then initialize
132 /// the data.117 /// the value (but not the key).
133 pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {118 pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {
134 // TODO this implementation can be improved - we should only119 return self.unmanaged.getOrPut(self.allocator, key);
135 // have to hash once and find the entry once.120 }
136 if (self.get(key)) |kv| {121
137 return GetOrPutResult{122 /// If there is an existing item with `key`, then the result
138 .kv = kv,123 /// `Entry` pointer points to it, and found_existing is true.
139 .found_existing = true,124 /// Otherwise, puts a new item with undefined value, and
140 };125 /// the `Entry` pointer points to it. Caller should then initialize
141 }126 /// the value (but not the key).
142 self.incrementModificationCount();127 /// If a new entry needs to be stored, this function asserts there
143 try self.autoCapacity();128 /// is enough capacity to store it.
144 const put_result = self.internalPut(key);129 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
145 assert(put_result.old_kv == null);130 return self.unmanaged.getOrPutAssumeCapacity(key);
146 return GetOrPutResult{131 }
147 .kv = &put_result.new_entry.kv,132
148 .found_existing = false,133 pub fn getOrPutValue(self: *Self, key: K, value: V) !*Entry {
134 return self.unmanaged.getOrPutValue(self.allocator, key, value);
135 }
136
137 /// Increases capacity, guaranteeing that insertions up until the
138 /// `expected_count` will not cause an allocation, and therefore cannot fail.
139 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
140 return self.unmanaged.ensureCapacity(self.allocator, new_capacity);
141 }
142
143 /// Returns the number of total elements which may be present before it is
144 /// no longer guaranteed that no allocations will be performed.
145 pub fn capacity(self: *Self) usize {
146 return self.unmanaged.capacity();
147 }
148
149 /// Clobbers any existing data. To detect if a put would clobber
150 /// existing data, see `getOrPut`.
151 pub fn put(self: *Self, key: K, value: V) !void {
152 return self.unmanaged.put(self.allocator, key, value);
153 }
154
155 /// Inserts a key-value pair into the hash map, asserting that no previous
156 /// entry with the same key is already present
157 pub fn putNoClobber(self: *Self, key: K, value: V) !void {
158 return self.unmanaged.putNoClobber(self.allocator, key, value);
159 }
160
161 /// Asserts there is enough capacity to store the new key-value pair.
162 /// Clobbers any existing data. To detect if a put would clobber
163 /// existing data, see `getOrPutAssumeCapacity`.
164 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
165 return self.unmanaged.putAssumeCapacity(key, value);
166 }
167
168 /// Asserts there is enough capacity to store the new key-value pair.
169 /// Asserts that it does not clobber any existing data.
170 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
171 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
172 return self.unmanaged.putAssumeCapacityNoClobber(key, value);
173 }
174
175 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
176 pub fn fetchPut(self: *Self, key: K, value: V) !?Entry {
177 return self.unmanaged.fetchPut(self.allocator, key, value);
178 }
179
180 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
181 /// If insertion happuns, asserts there is enough capacity without allocating.
182 pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry {
183 return self.unmanaged.fetchPutAssumeCapacity(key, value);
184 }
185
186 pub fn getEntry(self: Self, key: K) ?*Entry {
187 return self.unmanaged.getEntry(key);
188 }
189
190 pub fn get(self: Self, key: K) ?V {
191 return self.unmanaged.get(key);
192 }
193
194 pub fn contains(self: Self, key: K) bool {
195 return self.unmanaged.contains(key);
196 }
197
198 /// If there is an `Entry` with a matching key, it is deleted from
199 /// the hash map, and then returned from this function.
200 pub fn remove(self: *Self, key: K) ?Entry {
201 return self.unmanaged.remove(key);
202 }
203
204 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
205 /// and discards it.
206 pub fn removeAssertDiscard(self: *Self, key: K) void {
207 return self.unmanaged.removeAssertDiscard(key);
208 }
209
210 pub fn items(self: Self) []Entry {
211 return self.unmanaged.items();
212 }
213
214 pub fn clone(self: Self) !Self {
215 var other = try self.unmanaged.clone(self.allocator);
216 return other.promote(self.allocator);
217 }
218 };
219}
220
221/// General purpose hash table.
222/// Insertion order is preserved.
223/// Deletions perform a "swap removal" on the entries list.
224/// Modifying the hash map while iterating is allowed, however one must understand
225/// the (well defined) behavior when mixing insertions and deletions with iteration.
226/// This type does not store an Allocator field - the Allocator must be passed in
227/// with each function call that requires it. See `HashMap` for a type that stores
228/// an Allocator field for convenience.
229/// Can be initialized directly using the default field values.
230/// This type is designed to have low overhead for small numbers of entries. When
231/// `store_hash` is `false` and the number of entries in the map is less than 9,
232/// the overhead cost of using `HashMapUnmanaged` rather than `std.ArrayList` is
233/// only a single pointer-sized integer.
234/// When `store_hash` is `false`, this data structure is biased towards cheap `eql`
235/// functions. It does not store each item's hash in the table. Setting `store_hash`
236/// to `true` incurs slightly more memory cost by storing each key's hash in the table
237/// but guarantees only one call to `eql` per insertion/deletion.
238pub fn HashMapUnmanaged(
239 comptime K: type,
240 comptime V: type,
241 comptime hash: fn (key: K) u32,
242 comptime eql: fn (a: K, b: K) bool,
243 comptime store_hash: bool,
244) type {
245 return struct {
246 /// It is permitted to access this field directly.
247 entries: std.ArrayListUnmanaged(Entry) = .{},
248
249 /// When entries length is less than `linear_scan_max`, this remains `null`.
250 /// Once entries length grows big enough, this field is allocated. There is
251 /// an IndexHeader followed by an array of Index(I) structs, where I is defined
252 /// by how many total indexes there are.
253 index_header: ?*IndexHeader = null,
254
255 /// Modifying the key is illegal behavior.
256 /// Modifying the value is allowed.
257 /// Entry pointers become invalid whenever this HashMap is modified,
258 /// unless `ensureCapacity` was previously used.
259 pub const Entry = struct {
260 /// This field is `void` if `store_hash` is `false`.
261 hash: Hash,
262 key: K,
263 value: V,
264 };
265
266 pub const Hash = if (store_hash) u32 else void;
267
268 pub const GetOrPutResult = struct {
269 entry: *Entry,
270 found_existing: bool,
271 };
272
273 pub const Managed = HashMap(K, V, hash, eql, store_hash);
274
275 const Self = @This();
276
277 const linear_scan_max = 8;
278
279 pub fn promote(self: Self, allocator: *Allocator) Managed {
280 return .{
281 .unmanaged = self,
282 .allocator = allocator,
149 };283 };
150 }284 }
151285
152 pub fn getOrPutValue(self: *Self, key: K, value: V) !*KV {286 pub fn deinit(self: *Self, allocator: *Allocator) void {
153 const res = try self.getOrPut(key);287 self.entries.deinit(allocator);
154 if (!res.found_existing)288 if (self.index_header) |header| {
155 res.kv.value = value;289 header.free(allocator);
290 }
291 self.* = undefined;
292 }
156293
157 return res.kv;294 pub fn clearRetainingCapacity(self: *Self) void {
295 self.entries.items.len = 0;
296 if (self.header) |header| {
297 header.max_distance_from_start_index = 0;
298 const indexes = header.indexes(u8);
299 @memset(indexes.ptr, 0xff, indexes.len);
300 }
158 }301 }
159302
160 fn optimizedCapacity(expected_count: usize) usize {303 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
161 // ensure that the hash map will be at most 60% full if304 self.entries.shrink(allocator, 0);
162 // expected_count items are put into it305 if (self.header) |header| {
163 var optimized_capacity = expected_count * 5 / 3;306 header.free(allocator);
164 // an overflow here would mean the amount of memory required would not307 self.header = null;
165 // be representable in the address space308 }
166 return math.ceilPowerOfTwo(usize, optimized_capacity) catch unreachable;
167 }309 }
168310
169 /// Increases capacity so that the hash map will be at most311 /// If key exists this function cannot fail.
170 /// 60% full when expected_count items are put into it312 /// If there is an existing item with `key`, then the result
171 pub fn ensureCapacity(self: *Self, expected_count: usize) !void {313 /// `Entry` pointer points to it, and found_existing is true.
172 if (expected_count == 0) return;314 /// Otherwise, puts a new item with undefined value, and
173 const optimized_capacity = optimizedCapacity(expected_count);315 /// the `Entry` pointer points to it. Caller should then initialize
174 return self.ensureCapacityExact(optimized_capacity);316 /// the value (but not the key).
317 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {
318 self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| {
319 // "If key exists this function cannot fail."
320 return GetOrPutResult{
321 .entry = self.getEntry(key) orelse return err,
322 .found_existing = true,
323 };
324 };
325 return self.getOrPutAssumeCapacity(key);
175 }326 }
176327
177 /// Sets the capacity to the new capacity if the new328 /// If there is an existing item with `key`, then the result
178 /// capacity is greater than the current capacity.329 /// `Entry` pointer points to it, and found_existing is true.
179 /// New capacity must be a power of two.330 /// Otherwise, puts a new item with undefined value, and
180 fn ensureCapacityExact(self: *Self, new_capacity: usize) !void {331 /// the `Entry` pointer points to it. Caller should then initialize
181 // capacity must always be a power of two to allow for modulo332 /// the value (but not the key).
182 // optimization in the constrainIndex fn333 /// If a new entry needs to be stored, this function asserts there
183 assert(math.isPowerOfTwo(new_capacity));334 /// is enough capacity to store it.
335 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
336 const header = self.index_header orelse {
337 // Linear scan.
338 const h = if (store_hash) hash(key) else {};
339 for (self.entries.items) |*item| {
340 if (item.hash == h and eql(key, item.key)) {
341 return GetOrPutResult{
342 .entry = item,
343 .found_existing = true,
344 };
345 }
346 }
347 const new_entry = self.entries.addOneAssumeCapacity();
348 new_entry.* = .{
349 .hash = if (store_hash) h else {},
350 .key = key,
351 .value = undefined,
352 };
353 return GetOrPutResult{
354 .entry = new_entry,
355 .found_existing = false,
356 };
357 };
184358
185 if (new_capacity <= self.entries.len) {359 switch (header.capacityIndexType()) {
186 return;360 .u8 => return self.getOrPutInternal(key, header, u8),
361 .u16 => return self.getOrPutInternal(key, header, u16),
362 .u32 => return self.getOrPutInternal(key, header, u32),
363 .usize => return self.getOrPutInternal(key, header, usize),
187 }364 }
365 }
188366
189 const old_entries = self.entries;367 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {
190 try self.initCapacity(new_capacity);368 const res = try self.getOrPut(allocator, key);
191 self.incrementModificationCount();369 if (!res.found_existing)
192 if (old_entries.len > 0) {370 res.entry.value = value;
193 // dump all of the old elements into the new table371
194 for (old_entries) |*old_entry| {372 return res.entry;
195 if (old_entry.used) {373 }
196 self.internalPut(old_entry.kv.key).new_entry.kv.value = old_entry.kv.value;374
375 /// Increases capacity, guaranteeing that insertions up until the
376 /// `expected_count` will not cause an allocation, and therefore cannot fail.
377 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
378 try self.entries.ensureCapacity(allocator, new_capacity);
379 if (new_capacity <= linear_scan_max) return;
380
381 // Resize if indexes would be more than 75% full.
382 const needed_len = new_capacity * 4 / 3;
383 if (self.index_header) |header| {
384 if (needed_len > header.indexes_len) {
385 var new_indexes_len = header.indexes_len;
386 while (true) {
387 new_indexes_len += new_indexes_len / 2 + 8;
388 if (new_indexes_len >= needed_len) break;
197 }389 }
390 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);
391 self.insertAllEntriesIntoNewHeader(new_header);
392 header.free(allocator);
393 self.index_header = new_header;
198 }394 }
199 self.allocator.free(old_entries);395 } else {
396 const header = try IndexHeader.alloc(allocator, needed_len);
397 self.insertAllEntriesIntoNewHeader(header);
398 self.index_header = header;
200 }399 }
201 }400 }
202401
203 /// Returns the kv pair that was already there.402 /// Returns the number of total elements which may be present before it is
204 pub fn put(self: *Self, key: K, value: V) !?KV {403 /// no longer guaranteed that no allocations will be performed.
205 try self.autoCapacity();404 pub fn capacity(self: Self) usize {
206 return putAssumeCapacity(self, key, value);405 const entry_cap = self.entries.capacity;
406 const header = self.index_header orelse return math.min(linear_scan_max, entry_cap);
407 const indexes_cap = (header.indexes_len + 1) * 3 / 4;
408 return math.min(entry_cap, indexes_cap);
207 }409 }
208410
209 /// Calls put() and asserts that no kv pair is clobbered.411 /// Clobbers any existing data. To detect if a put would clobber
210 pub fn putNoClobber(self: *Self, key: K, value: V) !void {412 /// existing data, see `getOrPut`.
211 assert((try self.put(key, value)) == null);413 pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void {
414 const result = try self.getOrPut(allocator, key);
415 result.entry.value = value;
212 }416 }
213417
214 pub fn putAssumeCapacity(self: *Self, key: K, value: V) ?KV {418 /// Inserts a key-value pair into the hash map, asserting that no previous
215 assert(self.count() < self.entries.len);419 /// entry with the same key is already present
216 self.incrementModificationCount();420 pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void {
421 const result = try self.getOrPut(allocator, key);
422 assert(!result.found_existing);
423 result.entry.value = value;
424 }
217425
218 const put_result = self.internalPut(key);426 /// Asserts there is enough capacity to store the new key-value pair.
219 put_result.new_entry.kv.value = value;427 /// Clobbers any existing data. To detect if a put would clobber
220 return put_result.old_kv;428 /// existing data, see `getOrPutAssumeCapacity`.
429 pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
430 const result = self.getOrPutAssumeCapacity(key);
431 result.entry.value = value;
221 }432 }
222433
434 /// Asserts there is enough capacity to store the new key-value pair.
435 /// Asserts that it does not clobber any existing data.
436 /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`.
223 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {437 pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
224 assert(self.putAssumeCapacity(key, value) == null);438 const result = self.getOrPutAssumeCapacity(key);
439 assert(!result.found_existing);
440 result.entry.value = value;
225 }441 }
226442
227 pub fn get(hm: *const Self, key: K) ?*KV {443 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
228 if (hm.entries.len == 0) {444 pub fn fetchPut(self: *Self, allocator: *Allocator, key: K, value: V) !?Entry {
445 const gop = try self.getOrPut(allocator, key);
446 var result: ?Entry = null;
447 if (gop.found_existing) {
448 result = gop.entry.*;
449 }
450 gop.entry.value = value;
451 return result;
452 }
453
454 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
455 /// If insertion happuns, asserts there is enough capacity without allocating.
456 pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry {
457 const gop = self.getOrPutAssumeCapacity(key);
458 var result: ?Entry = null;
459 if (gop.found_existing) {
460 result = gop.entry.*;
461 }
462 gop.entry.value = value;
463 return result;
464 }
465
466 pub fn getEntry(self: Self, key: K) ?*Entry {
467 const header = self.index_header orelse {
468 // Linear scan.
469 const h = if (store_hash) hash(key) else {};
470 for (self.entries.items) |*item| {
471 if (item.hash == h and eql(key, item.key)) {
472 return item;
473 }
474 }
229 return null;475 return null;
476 };
477
478 switch (header.capacityIndexType()) {
479 .u8 => return self.getInternal(key, header, u8),
480 .u16 => return self.getInternal(key, header, u16),
481 .u32 => return self.getInternal(key, header, u32),
482 .usize => return self.getInternal(key, header, usize),
230 }483 }
231 return hm.internalGet(key);
232 }484 }
233485
234 pub fn getValue(hm: *const Self, key: K) ?V {486 pub fn get(self: Self, key: K) ?V {
235 return if (hm.get(key)) |kv| kv.value else null;487 return if (self.getEntry(key)) |entry| entry.value else null;
236 }488 }
237489
238 pub fn contains(hm: *const Self, key: K) bool {490 pub fn contains(self: Self, key: K) bool {
239 return hm.get(key) != null;491 return self.getEntry(key) != null;
240 }492 }
241493
242 /// Returns any kv pair that was removed.494 /// If there is an `Entry` with a matching key, it is deleted from
243 pub fn remove(hm: *Self, key: K) ?KV {495 /// the hash map, and then returned from this function.
244 if (hm.entries.len == 0) return null;496 pub fn remove(self: *Self, key: K) ?Entry {
245 hm.incrementModificationCount();497 const header = self.index_header orelse {
246 const start_index = hm.keyToIndex(key);498 // Linear scan.
247 {499 const h = if (store_hash) hash(key) else {};
248 var roll_over: usize = 0;500 for (self.entries.items) |item, i| {
249 while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) {501 if (item.hash == h and eql(key, item.key)) {
250 const index = hm.constrainIndex(start_index + roll_over);502 return self.entries.swapRemove(i);
251 var entry = &hm.entries[index];
252
253 if (!entry.used) return null;
254
255 if (!eql(entry.kv.key, key)) continue;
256
257 const removed_kv = entry.kv;
258 while (roll_over < hm.entries.len) : (roll_over += 1) {
259 const next_index = hm.constrainIndex(start_index + roll_over + 1);
260 const next_entry = &hm.entries[next_index];
261 if (!next_entry.used or next_entry.distance_from_start_index == 0) {
262 entry.used = false;
263 hm.size -= 1;
264 return removed_kv;
265 }
266 entry.* = next_entry.*;
267 entry.distance_from_start_index -= 1;
268 entry = next_entry;
269 }503 }
270 unreachable; // shifting everything in the table
271 }504 }
505 return null;
506 };
507 switch (header.capacityIndexType()) {
508 .u8 => return self.removeInternal(key, header, u8),
509 .u16 => return self.removeInternal(key, header, u16),
510 .u32 => return self.removeInternal(key, header, u32),
511 .usize => return self.removeInternal(key, header, usize),
272 }512 }
273 return null;
274 }513 }
275514
276 /// Calls remove(), asserts that a kv pair is removed, and discards it.515 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
277 pub fn removeAssertDiscard(hm: *Self, key: K) void {516 /// and discards it.
278 assert(hm.remove(key) != null);517 pub fn removeAssertDiscard(self: *Self, key: K) void {
518 assert(self.remove(key) != null);
279 }519 }
280520
281 pub fn iterator(hm: *const Self) Iterator {521 pub fn items(self: Self) []Entry {
282 return Iterator{522 return self.entries.items;
283 .hm = hm,
284 .count = 0,
285 .index = 0,
286 .initial_modification_count = hm.modification_count,
287 };
288 }523 }
289524
290 pub fn clone(self: Self) !Self {525 pub fn clone(self: Self, allocator: *Allocator) !Self {
291 var other = Self.init(self.allocator);526 // TODO this can be made more efficient by directly allocating
292 try other.initCapacity(self.entries.len);527 // the memory slices and memcpying the elements.
293 var it = self.iterator();528 var other = Self.init();
294 while (it.next()) |entry| {529 try other.initCapacity(allocator, self.entries.len);
295 try other.putNoClobber(entry.key, entry.value);530 for (self.entries.items) |entry| {
531 other.putAssumeCapacityNoClobber(entry.key, entry.value);
296 }532 }
297 return other;533 return other;
298 }534 }
299535
300 fn autoCapacity(self: *Self) !void {536 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {
301 if (self.entries.len == 0) {537 const indexes = header.indexes(I);
302 return self.ensureCapacityExact(16);538 const h = hash(key);
303 }539 const start_index = header.hashToIndex(h);
304 // if we get too full (60%), double the capacity540 var roll_over: usize = 0;
305 if (self.size * 5 >= self.entries.len * 3) {541 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
306 return self.ensureCapacityExact(self.entries.len * 2);542 const index_index = (start_index + roll_over) % header.indexes_len;
307 }543 var index = &indexes[index_index];
308 }544 if (index.isEmpty())
545 return null;
546
547 const entry = &self.entries.items[index.entry_index];
548
549 const hash_match = if (store_hash) h == entry.hash else true;
550 if (!hash_match or !eql(key, entry.key))
551 continue;
309552
310 fn initCapacity(hm: *Self, capacity: usize) !void {553 const removed_entry = self.entries.swapRemove(index.entry_index);
311 hm.entries = try hm.allocator.alloc(Entry, capacity);554 if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) {
312 hm.size = 0;555 // Because of the swap remove, now we need to update the index that was
313 hm.max_distance_from_start_index = 0;556 // pointing to the last entry and is now pointing to this removed item slot.
314 for (hm.entries) |*entry| {557 self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes);
315 entry.used = false;558 }
559
560 // Now we have to shift over the following indexes.
561 roll_over += 1;
562 while (roll_over < header.indexes_len) : (roll_over += 1) {
563 const next_index_index = (start_index + roll_over) % header.indexes_len;
564 const next_index = &indexes[next_index_index];
565 if (next_index.isEmpty() or next_index.distance_from_start_index == 0) {
566 index.setEmpty();
567 return removed_entry;
568 }
569 index.* = next_index.*;
570 index.distance_from_start_index -= 1;
571 index = next_index;
572 }
573 unreachable;
316 }574 }
575 return null;
317 }576 }
318577
319 fn incrementModificationCount(hm: *Self) void {578 fn updateEntryIndex(
320 if (want_modification_safety) {579 self: *Self,
321 hm.modification_count +%= 1;580 header: *IndexHeader,
581 old_entry_index: usize,
582 new_entry_index: usize,
583 comptime I: type,
584 indexes: []Index(I),
585 ) void {
586 const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key);
587 const start_index = header.hashToIndex(h);
588 var roll_over: usize = 0;
589 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
590 const index_index = (start_index + roll_over) % header.indexes_len;
591 const index = &indexes[index_index];
592 if (index.entry_index == old_entry_index) {
593 index.entry_index = @intCast(I, new_entry_index);
594 return;
595 }
322 }596 }
597 unreachable;
323 }598 }
324599
325 const InternalPutResult = struct {600 /// Must ensureCapacity before calling this.
326 new_entry: *Entry,601 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {
327 old_kv: ?KV,602 const indexes = header.indexes(I);
328 };603 const h = hash(key);
329604 const start_index = header.hashToIndex(h);
330 /// Returns a pointer to the new entry.
331 /// Asserts that there is enough space for the new item.
332 fn internalPut(self: *Self, orig_key: K) InternalPutResult {
333 var key = orig_key;
334 var value: V = undefined;
335 const start_index = self.keyToIndex(key);
336 var roll_over: usize = 0;605 var roll_over: usize = 0;
337 var distance_from_start_index: usize = 0;606 var distance_from_start_index: usize = 0;
338 var got_result_entry = false;607 while (roll_over <= header.indexes_len) : ({
339 var result = InternalPutResult{
340 .new_entry = undefined,
341 .old_kv = null,
342 };
343 while (roll_over < self.entries.len) : ({
344 roll_over += 1;608 roll_over += 1;
345 distance_from_start_index += 1;609 distance_from_start_index += 1;
346 }) {610 }) {
347 const index = self.constrainIndex(start_index + roll_over);611 const index_index = (start_index + roll_over) % header.indexes_len;
348 const entry = &self.entries[index];612 const index = indexes[index_index];
349613 if (index.isEmpty()) {
350 if (entry.used and !eql(entry.kv.key, key)) {614 indexes[index_index] = .{
351 if (entry.distance_from_start_index < distance_from_start_index) {615 .distance_from_start_index = @intCast(I, distance_from_start_index),
352 // robin hood to the rescue616 .entry_index = @intCast(I, self.entries.items.len),
353 const tmp = entry.*;617 };
354 self.max_distance_from_start_index = math.max(self.max_distance_from_start_index, distance_from_start_index);618 header.maybeBumpMax(distance_from_start_index);
355 if (!got_result_entry) {619 const new_entry = self.entries.addOneAssumeCapacity();
356 got_result_entry = true;620 new_entry.* = .{
357 result.new_entry = entry;621 .hash = if (store_hash) h else {},
622 .key = key,
623 .value = undefined,
624 };
625 return .{
626 .found_existing = false,
627 .entry = new_entry,
628 };
629 }
630
631 // This pointer survives the following append because we call
632 // entries.ensureCapacity before getOrPutInternal.
633 const entry = &self.entries.items[index.entry_index];
634 const hash_match = if (store_hash) h == entry.hash else true;
635 if (hash_match and eql(key, entry.key)) {
636 return .{
637 .found_existing = true,
638 .entry = entry,
639 };
640 }
641 if (index.distance_from_start_index < distance_from_start_index) {
642 // In this case, we did not find the item. We will put a new entry.
643 // However, we will use this index for the new entry, and move
644 // the previous index down the line, to keep the max_distance_from_start_index
645 // as small as possible.
646 indexes[index_index] = .{
647 .distance_from_start_index = @intCast(I, distance_from_start_index),
648 .entry_index = @intCast(I, self.entries.items.len),
649 };
650 header.maybeBumpMax(distance_from_start_index);
651 const new_entry = self.entries.addOneAssumeCapacity();
652 new_entry.* = .{
653 .hash = if (store_hash) h else {},
654 .key = key,
655 .value = undefined,
656 };
657
658 distance_from_start_index = index.distance_from_start_index;
659 var prev_entry_index = index.entry_index;
660
661 // Find somewhere to put the index we replaced by shifting
662 // following indexes backwards.
663 roll_over += 1;
664 distance_from_start_index += 1;
665 while (roll_over < header.indexes_len) : ({
666 roll_over += 1;
667 distance_from_start_index += 1;
668 }) {
669 const next_index_index = (start_index + roll_over) % header.indexes_len;
670 const next_index = indexes[next_index_index];
671 if (next_index.isEmpty()) {
672 header.maybeBumpMax(distance_from_start_index);
673 indexes[next_index_index] = .{
674 .entry_index = prev_entry_index,
675 .distance_from_start_index = @intCast(I, distance_from_start_index),
676 };
677 return .{
678 .found_existing = false,
679 .entry = new_entry,
680 };
681 }
682 if (next_index.distance_from_start_index < distance_from_start_index) {
683 header.maybeBumpMax(distance_from_start_index);
684 indexes[next_index_index] = .{
685 .entry_index = prev_entry_index,
686 .distance_from_start_index = @intCast(I, distance_from_start_index),
687 };
688 distance_from_start_index = next_index.distance_from_start_index;
689 prev_entry_index = next_index.entry_index;
358 }690 }
359 entry.* = Entry{
360 .used = true,
361 .distance_from_start_index = distance_from_start_index,
362 .kv = KV{
363 .key = key,
364 .value = value,
365 },
366 };
367 key = tmp.kv.key;
368 value = tmp.kv.value;
369 distance_from_start_index = tmp.distance_from_start_index;
370 }691 }
371 continue;692 unreachable;
372 }693 }
694 }
695 unreachable;
696 }
373697
374 if (entry.used) {698 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
375 result.old_kv = entry.kv;699 const indexes = header.indexes(I);
376 } else {700 const h = hash(key);
377 // adding an entry. otherwise overwriting old value with701 const start_index = header.hashToIndex(h);
378 // same key702 var roll_over: usize = 0;
379 self.size += 1;703 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
380 }704 const index_index = (start_index + roll_over) % header.indexes_len;
705 const index = indexes[index_index];
706 if (index.isEmpty())
707 return null;
708
709 const entry = &self.entries.items[index.entry_index];
710 const hash_match = if (store_hash) h == entry.hash else true;
711 if (hash_match and eql(key, entry.key))
712 return entry;
713 }
714 return null;
715 }
381716
382 self.max_distance_from_start_index = math.max(distance_from_start_index, self.max_distance_from_start_index);717 fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void {
383 if (!got_result_entry) {718 switch (header.capacityIndexType()) {
384 result.new_entry = entry;719 .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8),
385 }720 .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16),
386 entry.* = Entry{721 .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32),
387 .used = true,722 .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize),
388 .distance_from_start_index = distance_from_start_index,
389 .kv = KV{
390 .key = key,
391 .value = value,
392 },
393 };
394 return result;
395 }723 }
396 unreachable; // put into a full map
397 }724 }
398725
399 fn internalGet(hm: Self, key: K) ?*KV {726 fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void {
400 const start_index = hm.keyToIndex(key);727 const indexes = header.indexes(I);
401 {728 entry_loop: for (self.entries.items) |entry, i| {
729 const h = if (store_hash) entry.hash else hash(entry.key);
730 const start_index = header.hashToIndex(h);
731 var entry_index = i;
402 var roll_over: usize = 0;732 var roll_over: usize = 0;
403 while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) {733 var distance_from_start_index: usize = 0;
404 const index = hm.constrainIndex(start_index + roll_over);734 while (roll_over < header.indexes_len) : ({
405 const entry = &hm.entries[index];735 roll_over += 1;
406736 distance_from_start_index += 1;
407 if (!entry.used) return null;737 }) {
408 if (eql(entry.kv.key, key)) return &entry.kv;738 const index_index = (start_index + roll_over) % header.indexes_len;
739 const next_index = indexes[index_index];
740 if (next_index.isEmpty()) {
741 header.maybeBumpMax(distance_from_start_index);
742 indexes[index_index] = .{
743 .distance_from_start_index = @intCast(I, distance_from_start_index),
744 .entry_index = @intCast(I, entry_index),
745 };
746 continue :entry_loop;
747 }
748 if (next_index.distance_from_start_index < distance_from_start_index) {
749 header.maybeBumpMax(distance_from_start_index);
750 indexes[index_index] = .{
751 .distance_from_start_index = @intCast(I, distance_from_start_index),
752 .entry_index = @intCast(I, entry_index),
753 };
754 distance_from_start_index = next_index.distance_from_start_index;
755 entry_index = next_index.entry_index;
756 }
409 }757 }
758 unreachable;
410 }759 }
411 return null;
412 }760 }
761 };
762}
763
764const CapacityIndexType = enum { u8, u16, u32, usize };
413765
414 fn keyToIndex(hm: Self, key: K) usize {766fn capacityIndexType(indexes_len: usize) CapacityIndexType {
415 return hm.constrainIndex(@as(usize, hash(key)));767 if (indexes_len < math.maxInt(u8))
768 return .u8;
769 if (indexes_len < math.maxInt(u16))
770 return .u16;
771 if (indexes_len < math.maxInt(u32))
772 return .u32;
773 return .usize;
774}
775
776fn capacityIndexSize(indexes_len: usize) usize {
777 switch (capacityIndexType(indexes_len)) {
778 .u8 => return @sizeOf(Index(u8)),
779 .u16 => return @sizeOf(Index(u16)),
780 .u32 => return @sizeOf(Index(u32)),
781 .usize => return @sizeOf(Index(usize)),
782 }
783}
784
785fn Index(comptime I: type) type {
786 return extern struct {
787 entry_index: I,
788 distance_from_start_index: I,
789
790 const Self = @This();
791
792 fn isEmpty(idx: Self) bool {
793 return idx.entry_index == math.maxInt(I);
416 }794 }
417795
418 fn constrainIndex(hm: Self, i: usize) usize {796 fn setEmpty(idx: *Self) void {
419 // this is an optimization for modulo of power of two integers;797 idx.entry_index = math.maxInt(I);
420 // it requires hm.entries.len to always be a power of two
421 return i & (hm.entries.len - 1);
422 }798 }
423 };799 };
424}800}
425801
802/// This struct is trailed by an array of `Index(I)`, where `I`
803/// and the array length are determined by `indexes_len`.
804const IndexHeader = struct {
805 max_distance_from_start_index: usize,
806 indexes_len: usize,
807
808 fn hashToIndex(header: IndexHeader, h: u32) usize {
809 return @as(usize, h) % header.indexes_len;
810 }
811
812 fn indexes(header: *IndexHeader, comptime I: type) []Index(I) {
813 const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader));
814 return start[0..header.indexes_len];
815 }
816
817 fn capacityIndexType(header: IndexHeader) CapacityIndexType {
818 return hash_map.capacityIndexType(header.indexes_len);
819 }
820
821 fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void {
822 if (distance_from_start_index > header.max_distance_from_start_index) {
823 header.max_distance_from_start_index = distance_from_start_index;
824 }
825 }
826
827 fn alloc(allocator: *Allocator, len: usize) !*IndexHeader {
828 const index_size = hash_map.capacityIndexSize(len);
829 const nbytes = @sizeOf(IndexHeader) + index_size * len;
830 const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact);
831 @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader));
832 const result = @ptrCast(*IndexHeader, bytes.ptr);
833 result.* = .{
834 .max_distance_from_start_index = 0,
835 .indexes_len = len,
836 };
837 return result;
838 }
839
840 fn free(header: *IndexHeader, allocator: *Allocator) void {
841 const index_size = hash_map.capacityIndexSize(header.indexes_len);
842 const ptr = @ptrCast([*]u8, header);
843 const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size];
844 allocator.free(slice);
845 }
846};
847
426test "basic hash map usage" {848test "basic hash map usage" {
427 var map = AutoHashMap(i32, i32).init(std.testing.allocator);849 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
428 defer map.deinit();850 defer map.deinit();
429851
430 testing.expect((try map.put(1, 11)) == null);852 testing.expect((try map.fetchPut(1, 11)) == null);
431 testing.expect((try map.put(2, 22)) == null);853 testing.expect((try map.fetchPut(2, 22)) == null);
432 testing.expect((try map.put(3, 33)) == null);854 testing.expect((try map.fetchPut(3, 33)) == null);
433 testing.expect((try map.put(4, 44)) == null);855 testing.expect((try map.fetchPut(4, 44)) == null);
434856
435 try map.putNoClobber(5, 55);857 try map.putNoClobber(5, 55);
436 testing.expect((try map.put(5, 66)).?.value == 55);858 testing.expect((try map.fetchPut(5, 66)).?.value == 55);
437 testing.expect((try map.put(5, 55)).?.value == 66);859 testing.expect((try map.fetchPut(5, 55)).?.value == 66);
438860
439 const gop1 = try map.getOrPut(5);861 const gop1 = try map.getOrPut(5);
440 testing.expect(gop1.found_existing == true);862 testing.expect(gop1.found_existing == true);
441 testing.expect(gop1.kv.value == 55);863 testing.expect(gop1.entry.value == 55);
442 gop1.kv.value = 77;864 gop1.entry.value = 77;
443 testing.expect(map.get(5).?.value == 77);865 testing.expect(map.getEntry(5).?.value == 77);
444866
445 const gop2 = try map.getOrPut(99);867 const gop2 = try map.getOrPut(99);
446 testing.expect(gop2.found_existing == false);868 testing.expect(gop2.found_existing == false);
447 gop2.kv.value = 42;869 gop2.entry.value = 42;
448 testing.expect(map.get(99).?.value == 42);870 testing.expect(map.getEntry(99).?.value == 42);
449871
450 const gop3 = try map.getOrPutValue(5, 5);872 const gop3 = try map.getOrPutValue(5, 5);
451 testing.expect(gop3.value == 77);873 testing.expect(gop3.value == 77);
...@@ -454,15 +876,15 @@ test "basic hash map usage" {...@@ -454,15 +876,15 @@ test "basic hash map usage" {
454 testing.expect(gop4.value == 41);876 testing.expect(gop4.value == 41);
455877
456 testing.expect(map.contains(2));878 testing.expect(map.contains(2));
457 testing.expect(map.get(2).?.value == 22);879 testing.expect(map.getEntry(2).?.value == 22);
458 testing.expect(map.getValue(2).? == 22);880 testing.expect(map.get(2).? == 22);
459881
460 const rmv1 = map.remove(2);882 const rmv1 = map.remove(2);
461 testing.expect(rmv1.?.key == 2);883 testing.expect(rmv1.?.key == 2);
462 testing.expect(rmv1.?.value == 22);884 testing.expect(rmv1.?.value == 22);
463 testing.expect(map.remove(2) == null);885 testing.expect(map.remove(2) == null);
886 testing.expect(map.getEntry(2) == null);
464 testing.expect(map.get(2) == null);887 testing.expect(map.get(2) == null);
465 testing.expect(map.getValue(2) == null);
466888
467 map.removeAssertDiscard(3);889 map.removeAssertDiscard(3);
468}890}
...@@ -498,8 +920,8 @@ test "iterator hash map" {...@@ -498,8 +920,8 @@ test "iterator hash map" {
498 it.reset();920 it.reset();
499921
500 var count: usize = 0;922 var count: usize = 0;
501 while (it.next()) |kv| : (count += 1) {923 while (it.next()) |entry| : (count += 1) {
502 buffer[@intCast(usize, kv.key)] = kv.value;924 buffer[@intCast(usize, entry.key)] = entry.value;
503 }925 }
504 testing.expect(count == 3);926 testing.expect(count == 3);
505 testing.expect(it.next() == null);927 testing.expect(it.next() == null);
...@@ -510,8 +932,8 @@ test "iterator hash map" {...@@ -510,8 +932,8 @@ test "iterator hash map" {
510932
511 it.reset();933 it.reset();
512 count = 0;934 count = 0;
513 while (it.next()) |kv| {935 while (it.next()) |entry| {
514 buffer[@intCast(usize, kv.key)] = kv.value;936 buffer[@intCast(usize, entry.key)] = entry.value;
515 count += 1;937 count += 1;
516 if (count >= 2) break;938 if (count >= 2) break;
517 }939 }
...@@ -531,14 +953,14 @@ test "ensure capacity" {...@@ -531,14 +953,14 @@ test "ensure capacity" {
531 defer map.deinit();953 defer map.deinit();
532954
533 try map.ensureCapacity(20);955 try map.ensureCapacity(20);
534 const initialCapacity = map.entries.len;956 const initial_capacity = map.capacity();
535 testing.expect(initialCapacity >= 20);957 testing.expect(initial_capacity >= 20);
536 var i: i32 = 0;958 var i: i32 = 0;
537 while (i < 20) : (i += 1) {959 while (i < 20) : (i += 1) {
538 testing.expect(map.putAssumeCapacity(i, i + 10) == null);960 testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
539 }961 }
540 // shouldn't resize from putAssumeCapacity962 // shouldn't resize from putAssumeCapacity
541 testing.expect(initialCapacity == map.entries.len);963 testing.expect(initial_capacity == map.capacity());
542}964}
543965
544pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {966pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {
...@@ -575,6 +997,24 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {...@@ -575,6 +997,24 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
575 }.eql;997 }.eql;
576}998}
577999
1000pub fn autoEqlIsCheap(comptime K: type) bool {
1001 return switch (@typeInfo(K)) {
1002 .Bool,
1003 .Int,
1004 .Float,
1005 .Pointer,
1006 .ComptimeFloat,
1007 .ComptimeInt,
1008 .Enum,
1009 .Fn,
1010 .ErrorSet,
1011 .AnyFrame,
1012 .EnumLiteral,
1013 => true,
1014 else => false,
1015 };
1016}
1017
578pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) {1018pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) {
579 return struct {1019 return struct {
580 fn hash(key: K) u32 {1020 fn hash(key: K) u32 {