194 lines
4.3 KiB
Markdown
194 lines
4.3 KiB
Markdown
# std.HashMap / std.AutoHashMap
|
|
|
|
Hash maps for key-value storage. Use `AutoHashMap` for simple keys, `StringHashMap` for string keys.
|
|
|
|
## Types Overview
|
|
|
|
```zig
|
|
// AutoHashMap - automatic hash/eql for simple types
|
|
std.AutoHashMap(KeyType, ValueType)
|
|
std.AutoHashMapUnmanaged(KeyType, ValueType) // no stored allocator
|
|
|
|
// StringHashMap - optimized for string keys
|
|
std.StringHashMap(ValueType)
|
|
std.StringHashMapUnmanaged(ValueType)
|
|
|
|
// ArrayHashMap - Zig 0.16 unmanaged ordered maps
|
|
std.array_hash_map.Auto(K, V)
|
|
std.array_hash_map.String(V)
|
|
std.array_hash_map.Custom(K, V, Context, store_hash)
|
|
```
|
|
|
|
## AutoHashMap Usage
|
|
|
|
```zig
|
|
// Initialization
|
|
var map = std.AutoHashMap(u32, []const u8).init(allocator);
|
|
defer map.deinit();
|
|
|
|
// Insert
|
|
try map.put(42, "answer");
|
|
|
|
// Get
|
|
if (map.get(42)) |value| {
|
|
// value is []const u8
|
|
}
|
|
|
|
// Get pointer (for modification)
|
|
if (map.getPtr(42)) |ptr| {
|
|
ptr.* = "new value";
|
|
}
|
|
|
|
// Remove
|
|
if (map.fetchRemove(42)) |kv| {
|
|
// kv.key, kv.value - removed entry
|
|
}
|
|
_ = map.remove(42); // returns bool
|
|
|
|
// Check existence
|
|
const exists = map.contains(42);
|
|
|
|
// Count
|
|
const n = map.count();
|
|
```
|
|
|
|
## Unmanaged Variant
|
|
|
|
```zig
|
|
// No stored allocator - pass it to operations that allocate or release memory
|
|
var map: std.AutoHashMapUnmanaged(u32, []const u8) = .empty;
|
|
defer map.deinit(allocator);
|
|
|
|
try map.put(allocator, 42, "answer");
|
|
const val = map.get(42);
|
|
```
|
|
|
|
## StringHashMap
|
|
|
|
```zig
|
|
var map = std.StringHashMap(i32).init(allocator);
|
|
defer map.deinit();
|
|
|
|
try map.put("foo", 123);
|
|
const val = map.get("foo"); // ?i32
|
|
```
|
|
|
|
## getOrPut Pattern
|
|
|
|
Efficient insert-or-update without double lookup:
|
|
|
|
```zig
|
|
const gop = try map.getOrPut(key);
|
|
if (gop.found_existing) {
|
|
// Update existing
|
|
gop.value_ptr.* += 1;
|
|
} else {
|
|
// Initialize new entry
|
|
gop.value_ptr.* = 1;
|
|
}
|
|
```
|
|
|
|
## Iteration
|
|
|
|
```zig
|
|
// Iterate entries
|
|
var iter = map.iterator();
|
|
while (iter.next()) |entry| {
|
|
const key = entry.key_ptr.*;
|
|
const value = entry.value_ptr.*;
|
|
}
|
|
|
|
// Keys only
|
|
var keys = map.keyIterator();
|
|
while (keys.next()) |key_ptr| { _ = key_ptr.*; }
|
|
|
|
// Values only
|
|
var values = map.valueIterator();
|
|
while (values.next()) |value_ptr| { _ = value_ptr.*; }
|
|
```
|
|
|
|
Any modification invalidates iterators. Growth or rehashing can invalidate returned key/value pointers; removal immediately invalidates the removed entry's pointers.
|
|
|
|
## Capacity
|
|
|
|
```zig
|
|
try map.ensureTotalCapacity(100);
|
|
map.clearRetainingCapacity();
|
|
map.clearAndFree();
|
|
```
|
|
|
|
## Custom Context
|
|
|
|
For custom hash/equality functions:
|
|
|
|
```zig
|
|
const Context = struct {
|
|
pub fn hash(self: @This(), key: MyKey) u64 {
|
|
_ = self;
|
|
// compute hash
|
|
}
|
|
pub fn eql(self: @This(), a: MyKey, b: MyKey) bool {
|
|
_ = self;
|
|
// compare
|
|
}
|
|
};
|
|
|
|
var map = std.HashMap(MyKey, Value, Context, 80).init(allocator);
|
|
// Or with context instance:
|
|
var map = std.HashMap(MyKey, Value, Context, 80).initContext(allocator, context);
|
|
```
|
|
|
|
## ArrayHashMap (Ordered)
|
|
|
|
Preserves insertion order, supports indexed access:
|
|
|
|
```zig
|
|
var map: std.array_hash_map.String(i32) = .empty;
|
|
defer map.deinit(allocator);
|
|
|
|
try map.put(allocator, "b", 2);
|
|
try map.put(allocator, "a", 1);
|
|
|
|
// Iterate in insertion order: "b", "a"
|
|
for (map.keys(), map.values()) |k, v| { }
|
|
|
|
// Index access
|
|
const key = map.keys()[0]; // "b"
|
|
const val = map.values()[0]; // 2
|
|
|
|
// Swap remove (O(1) but changes order)
|
|
_ = map.swapRemove("b");
|
|
|
|
// Ordered remove (O(n) but preserves order)
|
|
_ = map.orderedRemove("a");
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
```zig
|
|
// Word frequency counter
|
|
var counts = std.StringHashMap(usize).init(allocator);
|
|
for (words) |word| {
|
|
const gop = try counts.getOrPut(word);
|
|
if (gop.found_existing) {
|
|
gop.value_ptr.* += 1;
|
|
} else {
|
|
gop.value_ptr.* = 1;
|
|
}
|
|
}
|
|
|
|
// Cache with owned keys
|
|
var cache = std.StringHashMap(Data).init(allocator);
|
|
defer cache.deinit();
|
|
defer {
|
|
var owned_keys = cache.keyIterator();
|
|
while (owned_keys.next()) |key_ptr| allocator.free(key_ptr.*);
|
|
}
|
|
// When inserting, dupe the key if needed:
|
|
const key_copy = try allocator.dupe(u8, external_key);
|
|
errdefer allocator.free(key_copy);
|
|
try cache.put(key_copy, data);
|
|
```
|
|
|
|
On removal, free the duplicated key returned by `fetchRemove`. Before clearing or deinitializing the map, iterate and free every remaining owned key as above; the map does not own string storage automatically.
|