234 lines
5.4 KiB
Markdown
234 lines
5.4 KiB
Markdown
# Array Hash Map Migration (Zig 0.16.0)
|
|
|
|
Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html
|
|
|
|
Zig 0.16 removed the managed array hash map aliases:
|
|
|
|
- `std.ArrayHashMap` removed.
|
|
- `std.AutoArrayHashMap` removed.
|
|
- `std.StringArrayHashMap` removed.
|
|
- `std.AutoArrayHashMapUnmanaged` -> `std.array_hash_map.Auto`
|
|
- `std.StringArrayHashMapUnmanaged` -> `std.array_hash_map.String`
|
|
- `std.ArrayHashMapUnmanaged` -> `std.array_hash_map.Custom`
|
|
|
|
Old examples below may use removed names; translate them to the `std.array_hash_map` names before using them in Zig 0.16 code.
|
|
|
|
A hash map that preserves insertion order and stores keys/values in contiguous arrays. Combines hash table lookup with array-like iteration.
|
|
|
|
## When to Use
|
|
|
|
- Need deterministic iteration order (insertion order)
|
|
- Need array-style access to keys/values
|
|
- JSON object preservation
|
|
- When iteration performance matters more than removal performance
|
|
|
|
## Variants
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `AutoArrayHashMap(K, V)` | Auto-hashing for common key types |
|
|
| `ArrayHashMap(K, V, Ctx, store_hash)` | Custom hash/equal context |
|
|
| `StringArrayHashMap(V)` | String keys |
|
|
| `ArrayHashMapUnmanaged(...)` | No stored allocator |
|
|
|
|
## Basic Usage
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
var map = std.AutoArrayHashMap(u32, []const u8).init(allocator);
|
|
defer map.deinit();
|
|
|
|
// Insert
|
|
try map.put(1, "one");
|
|
try map.put(2, "two");
|
|
try map.put(3, "three");
|
|
|
|
// Lookup
|
|
if (map.get(2)) |value| {
|
|
std.debug.print("2 = {s}\n", .{value});
|
|
}
|
|
|
|
// Check existence
|
|
if (map.contains(1)) {
|
|
// key exists
|
|
}
|
|
```
|
|
|
|
## Insertion Order Preserved
|
|
|
|
```zig
|
|
try map.put(10, "ten");
|
|
try map.put(5, "five");
|
|
try map.put(15, "fifteen");
|
|
|
|
// Iteration is in insertion order: 10, 5, 15
|
|
var it = map.iterator();
|
|
while (it.next()) |entry| {
|
|
std.debug.print("{}: {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
|
|
}
|
|
```
|
|
|
|
## Array Access
|
|
|
|
```zig
|
|
// Direct access to underlying arrays
|
|
const keys = map.keys(); // []K slice of all keys
|
|
const values = map.values(); // []V slice of all values
|
|
|
|
// Access by index
|
|
for (keys, values) |k, v| {
|
|
std.debug.print("{}: {s}\n", .{ k, v });
|
|
}
|
|
```
|
|
|
|
## Removal (Two Options)
|
|
|
|
```zig
|
|
// O(1) removal - swaps with last element, changes order
|
|
_ = map.swapRemove(key);
|
|
|
|
// O(n) removal - shifts elements, preserves order
|
|
_ = map.orderedRemove(key);
|
|
|
|
// Fetch and remove
|
|
if (map.fetchSwapRemove(key)) |kv| {
|
|
std.debug.print("removed {}: {s}\n", .{ kv.key, kv.value });
|
|
}
|
|
```
|
|
|
|
## Get or Put
|
|
|
|
```zig
|
|
// Get existing or insert new
|
|
const result = try map.getOrPut(key);
|
|
if (!result.found_existing) {
|
|
result.value_ptr.* = "new_value";
|
|
}
|
|
|
|
// Get or put with default value
|
|
const result2 = try map.getOrPutValue(key, "default");
|
|
```
|
|
|
|
## Index-Based Operations
|
|
|
|
```zig
|
|
// Get index of key
|
|
if (map.getIndex(key)) |idx| {
|
|
// Remove by index
|
|
map.swapRemoveAt(idx);
|
|
// or
|
|
map.orderedRemoveAt(idx);
|
|
}
|
|
```
|
|
|
|
## Capacity Management
|
|
|
|
```zig
|
|
try map.ensureTotalCapacity(100);
|
|
try map.ensureUnusedCapacity(10);
|
|
|
|
const cap = map.capacity();
|
|
const len = map.count();
|
|
|
|
map.clearRetainingCapacity();
|
|
map.clearAndFree();
|
|
```
|
|
|
|
## String Keys
|
|
|
|
```zig
|
|
var map = std.StringArrayHashMap(i32).init(allocator);
|
|
defer map.deinit();
|
|
|
|
try map.put("apple", 1);
|
|
try map.put("banana", 2);
|
|
|
|
// Keys are stored by reference, not copied
|
|
// Make sure string lifetime exceeds map usage
|
|
```
|
|
|
|
## Custom Context
|
|
|
|
```zig
|
|
const CaseInsensitiveContext = struct {
|
|
pub fn hash(_: @This(), key: []const u8) u32 {
|
|
var h: u32 = 0;
|
|
for (key) |c| {
|
|
h = h *% 31 +% std.ascii.toLower(c);
|
|
}
|
|
return h;
|
|
}
|
|
pub fn eql(_: @This(), a: []const u8, b: []const u8, _: usize) bool {
|
|
return std.ascii.eqlIgnoreCase(a, b);
|
|
}
|
|
};
|
|
|
|
var map = std.ArrayHashMap(
|
|
[]const u8,
|
|
i32,
|
|
CaseInsensitiveContext,
|
|
true, // store_hash for better performance
|
|
).initContext(allocator, .{});
|
|
defer map.deinit();
|
|
|
|
try map.put("Hello", 1);
|
|
_ = map.get("HELLO"); // finds it!
|
|
```
|
|
|
|
## Complete Example: Word Counter
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
pub fn main() !void {
|
|
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
|
defer _ = gpa.deinit();
|
|
|
|
var counts = std.StringArrayHashMap(u32).init(gpa.allocator());
|
|
defer counts.deinit();
|
|
|
|
const words = [_][]const u8{ "apple", "banana", "apple", "cherry", "banana", "apple" };
|
|
|
|
for (words) |word| {
|
|
const result = try counts.getOrPut(word);
|
|
if (result.found_existing) {
|
|
result.value_ptr.* += 1;
|
|
} else {
|
|
result.value_ptr.* = 1;
|
|
}
|
|
}
|
|
|
|
// Print in insertion order
|
|
var it = counts.iterator();
|
|
while (it.next()) |entry| {
|
|
std.debug.print("{s}: {}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
|
|
}
|
|
// Output (insertion order):
|
|
// apple: 3
|
|
// banana: 2
|
|
// cherry: 1
|
|
}
|
|
```
|
|
|
|
## Comparison with HashMap
|
|
|
|
| Feature | HashMap | ArrayHashMap |
|
|
|---------|---------|--------------|
|
|
| Lookup | O(1) | O(1) |
|
|
| Insert | O(1) amortized | O(1) amortized |
|
|
| swapRemove | O(1) | O(1) |
|
|
| orderedRemove | N/A | O(n) |
|
|
| Iteration order | Undefined | Insertion order |
|
|
| Key/value arrays | No | Yes |
|
|
| Memory layout | Scattered | Contiguous |
|
|
|
|
## Notes
|
|
|
|
- Iteration order equals insertion order
|
|
- `swapRemove` is O(1) but changes order
|
|
- `orderedRemove` preserves order but is O(n)
|
|
- Use `store_hash=true` when `eql` is expensive
|
|
- Keys/values are stored in `MultiArrayList` (cache-friendly)
|
|
- Pointer stability only guaranteed with pre-allocated capacity
|