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 {
210210 self.capacity = new_len;
211211 }
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
213221 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
214222 var better_capacity = self.capacity;
215223 if (better_capacity >= new_capacity) return;
......@@ -432,6 +440,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
432440 self.capacity = new_len;
433441 }
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
435451 pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
436452 var better_capacity = self.capacity;
437453 if (better_capacity >= new_capacity) return;
lib/std/debug.zig+1-1
......@@ -1278,7 +1278,7 @@ pub const DebugInfo = struct {
12781278 else => return error.MissingDebugInfo,
12791279 }
12801280
1281 if (self.address_map.getValue(ctx.base_address)) |obj_di| {
1281 if (self.address_map.get(ctx.base_address)) |obj_di| {
12821282 return obj_di;
12831283 }
12841284
lib/std/hash_map.zig+749-309
......@@ -9,17 +9,15 @@ const autoHash = std.hash.autoHash;
99const Wyhash = std.hash.Wyhash;
1010const Allocator = mem.Allocator;
1111const builtin = @import("builtin");
12
13const want_modification_safety = std.debug.runtime_safety;
14const debug_u32 = if (want_modification_safety) u32 else void;
12const hash_map = @This();
1513
1614pub 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));
1816}
1917
2018/// Builtin hashmap for strings as keys.
2119pub fn StringHashMap(comptime V: type) type {
22 return HashMap([]const u8, V, hashString, eqlString);
20 return HashMap([]const u8, V, hashString, eqlString, true);
2321}
2422
2523pub fn eqlString(a: []const u8, b: []const u8) bool {
......@@ -30,422 +28,846 @@ pub fn hashString(s: []const u8) u32 {
3028 return @truncate(u32, std.hash.Wyhash.hash(0, s));
3129}
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 {
3448 return struct {
35 entries: []Entry,
36 size: usize,
37 max_distance_from_start_index: usize,
49 unmanaged: Unmanaged,
3850 allocator: *Allocator,
3951
40 /// This is used to detect bugs where a hashtable is edited while an iterator is running.
41 modification_count: debug_u32,
42
43 const Self = @This();
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 };
52 pub const Unmanaged = HashMapUnmanaged(K, V, hash, eql, store_hash);
53 pub const Entry = Unmanaged.Entry;
54 pub const Hash = Unmanaged.Hash;
55 pub const GetOrPutResult = Unmanaged.GetOrPutResult;
6556
57 /// Deprecated. Iterate using `items`.
6658 pub const Iterator = struct {
6759 hm: *const Self,
68 // how many items have we returned
69 count: usize,
70 // iterator through the entry array
60 /// Iterator through the entry array.
7161 index: usize,
72 // used to detect concurrent modification
73 initial_modification_count: debug_u32,
7462
75 pub fn next(it: *Iterator) ?*KV {
76 if (want_modification_safety) {
77 assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification
78 }
79 if (it.count >= it.hm.size) return null;
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
63 pub fn next(it: *Iterator) ?*Entry {
64 if (it.index >= it.hm.unmanaged.entries.items.len) return null;
65 const result = &it.hm.unmanaged.entries.items[it.index];
66 it.index += 1;
67 return result;
8968 }
9069
91 // Reset the iterator to the initial index
70 /// Reset the iterator to the initial index
9271 pub fn reset(it: *Iterator) void {
93 it.count = 0;
9472 it.index = 0;
95 // Resetting the modification count too
96 it.initial_modification_count = it.hm.modification_count;
9773 }
9874 };
9975
76 const Self = @This();
77 const Index = Unmanaged.Index;
78
10079 pub fn init(allocator: *Allocator) Self {
101 return Self{
102 .entries = &[_]Entry{},
80 return .{
81 .unmanaged = .{},
10382 .allocator = allocator,
104 .size = 0,
105 .max_distance_from_start_index = 0,
106 .modification_count = if (want_modification_safety) 0 else {},
10783 };
10884 }
10985
110 pub fn deinit(hm: Self) void {
111 hm.allocator.free(hm.entries);
86 pub fn deinit(self: *Self) void {
87 self.unmanaged.deinit(self.allocator);
88 self.* = undefined;
11289 }
11390
114 pub fn clear(hm: *Self) void {
115 for (hm.entries) |*entry| {
116 entry.used = false;
117 }
118 hm.size = 0;
119 hm.max_distance_from_start_index = 0;
120 hm.incrementModificationCount();
91 pub fn clearRetainingCapacity(self: *Self) void {
92 return self.unmanaged.clearRetainingCapacity();
12193 }
12294
95 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
96 return self.unmanaged.clearAndFree(self.allocator);
97 }
98
99 /// Deprecated. Use `items().len`.
123100 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 };
125110 }
126111
127112 /// If key exists this function cannot fail.
128113 /// 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.
130115 /// Otherwise, puts a new item with undefined value, and
131 /// the kv pointer points to it. Caller should then initialize
132 /// the data.
116 /// the `Entry` pointer points to it. Caller should then initialize
117 /// the value (but not the key).
133118 pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {
134 // TODO this implementation can be improved - we should only
135 // have to hash once and find the entry once.
136 if (self.get(key)) |kv| {
137 return GetOrPutResult{
138 .kv = kv,
139 .found_existing = true,
140 };
141 }
142 self.incrementModificationCount();
143 try self.autoCapacity();
144 const put_result = self.internalPut(key);
145 assert(put_result.old_kv == null);
146 return GetOrPutResult{
147 .kv = &put_result.new_entry.kv,
148 .found_existing = false,
119 return self.unmanaged.getOrPut(self.allocator, key);
120 }
121
122 /// If there is an existing item with `key`, then the result
123 /// `Entry` pointer points to it, and found_existing is true.
124 /// Otherwise, puts a new item with undefined value, and
125 /// the `Entry` pointer points to it. Caller should then initialize
126 /// the value (but not the key).
127 /// If a new entry needs to be stored, this function asserts there
128 /// is enough capacity to store it.
129 pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult {
130 return self.unmanaged.getOrPutAssumeCapacity(key);
131 }
132
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,
149283 };
150284 }
151285
152 pub fn getOrPutValue(self: *Self, key: K, value: V) !*KV {
153 const res = try self.getOrPut(key);
154 if (!res.found_existing)
155 res.kv.value = value;
286 pub fn deinit(self: *Self, allocator: *Allocator) void {
287 self.entries.deinit(allocator);
288 if (self.index_header) |header| {
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 }
158301 }
159302
160 fn optimizedCapacity(expected_count: usize) usize {
161 // ensure that the hash map will be at most 60% full if
162 // expected_count items are put into it
163 var optimized_capacity = expected_count * 5 / 3;
164 // an overflow here would mean the amount of memory required would not
165 // be representable in the address space
166 return math.ceilPowerOfTwo(usize, optimized_capacity) catch unreachable;
303 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
304 self.entries.shrink(allocator, 0);
305 if (self.header) |header| {
306 header.free(allocator);
307 self.header = null;
308 }
167309 }
168310
169 /// Increases capacity so that the hash map will be at most
170 /// 60% full when expected_count items are put into it
171 pub fn ensureCapacity(self: *Self, expected_count: usize) !void {
172 if (expected_count == 0) return;
173 const optimized_capacity = optimizedCapacity(expected_count);
174 return self.ensureCapacityExact(optimized_capacity);
311 /// If key exists this function cannot fail.
312 /// If there is an existing item with `key`, then the result
313 /// `Entry` pointer points to it, and found_existing is true.
314 /// Otherwise, puts a new item with undefined value, and
315 /// the `Entry` pointer points to it. Caller should then initialize
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);
175326 }
176327
177 /// Sets the capacity to the new capacity if the new
178 /// capacity is greater than the current capacity.
179 /// New capacity must be a power of two.
180 fn ensureCapacityExact(self: *Self, new_capacity: usize) !void {
181 // capacity must always be a power of two to allow for modulo
182 // optimization in the constrainIndex fn
183 assert(math.isPowerOfTwo(new_capacity));
328 /// If there is an existing item with `key`, then the result
329 /// `Entry` pointer points to it, and found_existing is true.
330 /// Otherwise, puts a new item with undefined value, and
331 /// the `Entry` pointer points to it. Caller should then initialize
332 /// the value (but not the key).
333 /// If a new entry needs to be stored, this function asserts there
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) {
186 return;
359 switch (header.capacityIndexType()) {
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),
187364 }
365 }
188366
189 const old_entries = self.entries;
190 try self.initCapacity(new_capacity);
191 self.incrementModificationCount();
192 if (old_entries.len > 0) {
193 // dump all of the old elements into the new table
194 for (old_entries) |*old_entry| {
195 if (old_entry.used) {
196 self.internalPut(old_entry.kv.key).new_entry.kv.value = old_entry.kv.value;
367 pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry {
368 const res = try self.getOrPut(allocator, key);
369 if (!res.found_existing)
370 res.entry.value = value;
371
372 return res.entry;
373 }
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;
197389 }
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;
198394 }
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;
200399 }
201400 }
202401
203 /// Returns the kv pair that was already there.
204 pub fn put(self: *Self, key: K, value: V) !?KV {
205 try self.autoCapacity();
206 return putAssumeCapacity(self, key, value);
402 /// Returns the number of total elements which may be present before it is
403 /// no longer guaranteed that no allocations will be performed.
404 pub fn capacity(self: Self) usize {
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);
207409 }
208410
209 /// Calls put() and asserts that no kv pair is clobbered.
210 pub fn putNoClobber(self: *Self, key: K, value: V) !void {
211 assert((try self.put(key, value)) == null);
411 /// Clobbers any existing data. To detect if a put would clobber
412 /// existing data, see `getOrPut`.
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;
212416 }
213417
214 pub fn putAssumeCapacity(self: *Self, key: K, value: V) ?KV {
215 assert(self.count() < self.entries.len);
216 self.incrementModificationCount();
418 /// Inserts a key-value pair into the hash map, asserting that no previous
419 /// entry with the same key is already present
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);
219 put_result.new_entry.kv.value = value;
220 return put_result.old_kv;
426 /// Asserts there is enough capacity to store the new key-value pair.
427 /// Clobbers any existing data. To detect if a put would clobber
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;
221432 }
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`.
223437 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;
225441 }
226442
227 pub fn get(hm: *const Self, key: K) ?*KV {
228 if (hm.entries.len == 0) {
443 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
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 }
229475 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),
230483 }
231 return hm.internalGet(key);
232484 }
233485
234 pub fn getValue(hm: *const Self, key: K) ?V {
235 return if (hm.get(key)) |kv| kv.value else null;
486 pub fn get(self: Self, key: K) ?V {
487 return if (self.getEntry(key)) |entry| entry.value else null;
236488 }
237489
238 pub fn contains(hm: *const Self, key: K) bool {
239 return hm.get(key) != null;
490 pub fn contains(self: Self, key: K) bool {
491 return self.getEntry(key) != null;
240492 }
241493
242 /// Returns any kv pair that was removed.
243 pub fn remove(hm: *Self, key: K) ?KV {
244 if (hm.entries.len == 0) return null;
245 hm.incrementModificationCount();
246 const start_index = hm.keyToIndex(key);
247 {
248 var roll_over: usize = 0;
249 while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) {
250 const index = hm.constrainIndex(start_index + roll_over);
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;
494 /// If there is an `Entry` with a matching key, it is deleted from
495 /// the hash map, and then returned from this function.
496 pub fn remove(self: *Self, key: K) ?Entry {
497 const header = self.index_header orelse {
498 // Linear scan.
499 const h = if (store_hash) hash(key) else {};
500 for (self.entries.items) |item, i| {
501 if (item.hash == h and eql(key, item.key)) {
502 return self.entries.swapRemove(i);
269503 }
270 unreachable; // shifting everything in the table
271504 }
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),
272512 }
273 return null;
274513 }
275514
276 /// Calls remove(), asserts that a kv pair is removed, and discards it.
277 pub fn removeAssertDiscard(hm: *Self, key: K) void {
278 assert(hm.remove(key) != null);
515 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
516 /// and discards it.
517 pub fn removeAssertDiscard(self: *Self, key: K) void {
518 assert(self.remove(key) != null);
279519 }
280520
281 pub fn iterator(hm: *const Self) Iterator {
282 return Iterator{
283 .hm = hm,
284 .count = 0,
285 .index = 0,
286 .initial_modification_count = hm.modification_count,
287 };
521 pub fn items(self: Self) []Entry {
522 return self.entries.items;
288523 }
289524
290 pub fn clone(self: Self) !Self {
291 var other = Self.init(self.allocator);
292 try other.initCapacity(self.entries.len);
293 var it = self.iterator();
294 while (it.next()) |entry| {
295 try other.putNoClobber(entry.key, entry.value);
525 pub fn clone(self: Self, allocator: *Allocator) !Self {
526 // TODO this can be made more efficient by directly allocating
527 // the memory slices and memcpying the elements.
528 var other = Self.init();
529 try other.initCapacity(allocator, self.entries.len);
530 for (self.entries.items) |entry| {
531 other.putAssumeCapacityNoClobber(entry.key, entry.value);
296532 }
297533 return other;
298534 }
299535
300 fn autoCapacity(self: *Self) !void {
301 if (self.entries.len == 0) {
302 return self.ensureCapacityExact(16);
303 }
304 // if we get too full (60%), double the capacity
305 if (self.size * 5 >= self.entries.len * 3) {
306 return self.ensureCapacityExact(self.entries.len * 2);
307 }
308 }
536 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {
537 const indexes = header.indexes(I);
538 const h = hash(key);
539 const start_index = header.hashToIndex(h);
540 var roll_over: usize = 0;
541 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
542 const index_index = (start_index + roll_over) % header.indexes_len;
543 var index = &indexes[index_index];
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 {
311 hm.entries = try hm.allocator.alloc(Entry, capacity);
312 hm.size = 0;
313 hm.max_distance_from_start_index = 0;
314 for (hm.entries) |*entry| {
315 entry.used = false;
553 const removed_entry = self.entries.swapRemove(index.entry_index);
554 if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) {
555 // Because of the swap remove, now we need to update the index that was
556 // pointing to the last entry and is now pointing to this removed item slot.
557 self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes);
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;
316574 }
575 return null;
317576 }
318577
319 fn incrementModificationCount(hm: *Self) void {
320 if (want_modification_safety) {
321 hm.modification_count +%= 1;
578 fn updateEntryIndex(
579 self: *Self,
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 }
322596 }
597 unreachable;
323598 }
324599
325 const InternalPutResult = struct {
326 new_entry: *Entry,
327 old_kv: ?KV,
328 };
329
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);
600 /// Must ensureCapacity before calling this.
601 fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult {
602 const indexes = header.indexes(I);
603 const h = hash(key);
604 const start_index = header.hashToIndex(h);
336605 var roll_over: usize = 0;
337606 var distance_from_start_index: usize = 0;
338 var got_result_entry = false;
339 var result = InternalPutResult{
340 .new_entry = undefined,
341 .old_kv = null,
342 };
343 while (roll_over < self.entries.len) : ({
607 while (roll_over <= header.indexes_len) : ({
344608 roll_over += 1;
345609 distance_from_start_index += 1;
346610 }) {
347 const index = self.constrainIndex(start_index + roll_over);
348 const entry = &self.entries[index];
349
350 if (entry.used and !eql(entry.kv.key, key)) {
351 if (entry.distance_from_start_index < distance_from_start_index) {
352 // robin hood to the rescue
353 const tmp = entry.*;
354 self.max_distance_from_start_index = math.max(self.max_distance_from_start_index, distance_from_start_index);
355 if (!got_result_entry) {
356 got_result_entry = true;
357 result.new_entry = entry;
611 const index_index = (start_index + roll_over) % header.indexes_len;
612 const index = indexes[index_index];
613 if (index.isEmpty()) {
614 indexes[index_index] = .{
615 .distance_from_start_index = @intCast(I, distance_from_start_index),
616 .entry_index = @intCast(I, self.entries.items.len),
617 };
618 header.maybeBumpMax(distance_from_start_index);
619 const new_entry = self.entries.addOneAssumeCapacity();
620 new_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;
358690 }
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;
370691 }
371 continue;
692 unreachable;
372693 }
694 }
695 unreachable;
696 }
373697
374 if (entry.used) {
375 result.old_kv = entry.kv;
376 } else {
377 // adding an entry. otherwise overwriting old value with
378 // same key
379 self.size += 1;
380 }
698 fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
699 const indexes = header.indexes(I);
700 const h = hash(key);
701 const start_index = header.hashToIndex(h);
702 var roll_over: usize = 0;
703 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
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);
383 if (!got_result_entry) {
384 result.new_entry = entry;
385 }
386 entry.* = Entry{
387 .used = true,
388 .distance_from_start_index = distance_from_start_index,
389 .kv = KV{
390 .key = key,
391 .value = value,
392 },
393 };
394 return result;
717 fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void {
718 switch (header.capacityIndexType()) {
719 .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8),
720 .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16),
721 .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32),
722 .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize),
395723 }
396 unreachable; // put into a full map
397724 }
398725
399 fn internalGet(hm: Self, key: K) ?*KV {
400 const start_index = hm.keyToIndex(key);
401 {
726 fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void {
727 const indexes = header.indexes(I);
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;
402732 var roll_over: usize = 0;
403 while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) {
404 const index = hm.constrainIndex(start_index + roll_over);
405 const entry = &hm.entries[index];
406
407 if (!entry.used) return null;
408 if (eql(entry.kv.key, key)) return &entry.kv;
733 var distance_from_start_index: usize = 0;
734 while (roll_over < header.indexes_len) : ({
735 roll_over += 1;
736 distance_from_start_index += 1;
737 }) {
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 }
409757 }
758 unreachable;
410759 }
411 return null;
412760 }
761 };
762}
763
764const CapacityIndexType = enum { u8, u16, u32, usize };
413765
414 fn keyToIndex(hm: Self, key: K) usize {
415 return hm.constrainIndex(@as(usize, hash(key)));
766fn capacityIndexType(indexes_len: usize) CapacityIndexType {
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);
416794 }
417795
418 fn constrainIndex(hm: Self, i: usize) usize {
419 // this is an optimization for modulo of power of two integers;
420 // it requires hm.entries.len to always be a power of two
421 return i & (hm.entries.len - 1);
796 fn setEmpty(idx: *Self) void {
797 idx.entry_index = math.maxInt(I);
422798 }
423799 };
424800}
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
426848test "basic hash map usage" {
427849 var map = AutoHashMap(i32, i32).init(std.testing.allocator);
428850 defer map.deinit();
429851
430 testing.expect((try map.put(1, 11)) == null);
431 testing.expect((try map.put(2, 22)) == null);
432 testing.expect((try map.put(3, 33)) == null);
433 testing.expect((try map.put(4, 44)) == null);
852 testing.expect((try map.fetchPut(1, 11)) == null);
853 testing.expect((try map.fetchPut(2, 22)) == null);
854 testing.expect((try map.fetchPut(3, 33)) == null);
855 testing.expect((try map.fetchPut(4, 44)) == null);
434856
435857 try map.putNoClobber(5, 55);
436 testing.expect((try map.put(5, 66)).?.value == 55);
437 testing.expect((try map.put(5, 55)).?.value == 66);
858 testing.expect((try map.fetchPut(5, 66)).?.value == 55);
859 testing.expect((try map.fetchPut(5, 55)).?.value == 66);
438860
439861 const gop1 = try map.getOrPut(5);
440862 testing.expect(gop1.found_existing == true);
441 testing.expect(gop1.kv.value == 55);
442 gop1.kv.value = 77;
443 testing.expect(map.get(5).?.value == 77);
863 testing.expect(gop1.entry.value == 55);
864 gop1.entry.value = 77;
865 testing.expect(map.getEntry(5).?.value == 77);
444866
445867 const gop2 = try map.getOrPut(99);
446868 testing.expect(gop2.found_existing == false);
447 gop2.kv.value = 42;
448 testing.expect(map.get(99).?.value == 42);
869 gop2.entry.value = 42;
870 testing.expect(map.getEntry(99).?.value == 42);
449871
450872 const gop3 = try map.getOrPutValue(5, 5);
451873 testing.expect(gop3.value == 77);
......@@ -454,15 +876,15 @@ test "basic hash map usage" {
454876 testing.expect(gop4.value == 41);
455877
456878 testing.expect(map.contains(2));
457 testing.expect(map.get(2).?.value == 22);
458 testing.expect(map.getValue(2).? == 22);
879 testing.expect(map.getEntry(2).?.value == 22);
880 testing.expect(map.get(2).? == 22);
459881
460882 const rmv1 = map.remove(2);
461883 testing.expect(rmv1.?.key == 2);
462884 testing.expect(rmv1.?.value == 22);
463885 testing.expect(map.remove(2) == null);
886 testing.expect(map.getEntry(2) == null);
464887 testing.expect(map.get(2) == null);
465 testing.expect(map.getValue(2) == null);
466888
467889 map.removeAssertDiscard(3);
468890}
......@@ -498,8 +920,8 @@ test "iterator hash map" {
498920 it.reset();
499921
500922 var count: usize = 0;
501 while (it.next()) |kv| : (count += 1) {
502 buffer[@intCast(usize, kv.key)] = kv.value;
923 while (it.next()) |entry| : (count += 1) {
924 buffer[@intCast(usize, entry.key)] = entry.value;
503925 }
504926 testing.expect(count == 3);
505927 testing.expect(it.next() == null);
......@@ -510,8 +932,8 @@ test "iterator hash map" {
510932
511933 it.reset();
512934 count = 0;
513 while (it.next()) |kv| {
514 buffer[@intCast(usize, kv.key)] = kv.value;
935 while (it.next()) |entry| {
936 buffer[@intCast(usize, entry.key)] = entry.value;
515937 count += 1;
516938 if (count >= 2) break;
517939 }
......@@ -531,14 +953,14 @@ test "ensure capacity" {
531953 defer map.deinit();
532954
533955 try map.ensureCapacity(20);
534 const initialCapacity = map.entries.len;
535 testing.expect(initialCapacity >= 20);
956 const initial_capacity = map.capacity();
957 testing.expect(initial_capacity >= 20);
536958 var i: i32 = 0;
537959 while (i < 20) : (i += 1) {
538 testing.expect(map.putAssumeCapacity(i, i + 10) == null);
960 testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null);
539961 }
540962 // shouldn't resize from putAssumeCapacity
541 testing.expect(initialCapacity == map.entries.len);
963 testing.expect(initial_capacity == map.capacity());
542964}
543965
544966pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {
......@@ -575,6 +997,24 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
575997 }.eql;
576998}
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
5781018pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) {
5791019 return struct {
5801020 fn hash(key: K) u32 {