AutoHashMap
std.AutoHashMap implements a hash table that automatically creates a hash function for the key type. Its main limitation is that it doesn’t work for keys that contain pointers or slices – see StringHashMap and HashMap for alternatives.
const std = @import("std");
const print = std.debug.print;
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
// AutoHashMap stores the allocator, so we need to
// provide that here.
var map = std.AutoHashMap(
u64, // key type
u64, // value type
).init(allocator);
defer map.deinit();
// Add an entry with 'put'.
try map.put(2, 4);
print("2 is in map? {}\n", .{map.contains(2)});
print("2 => {?}\n", .{map.get(2)});
print("\n", .{});
// 'put' also updates existing entries.
try map.put(2, 8);
print("2 => {?}\n", .{map.get(2)});
print("\n", .{});
print("entry count: {}\n", .{map.count()});
print("\n", .{});
// 'remove' removes a key. It returns a boolean to
// indicate if the key was there before.
const removed = map.remove(2);
print("removed entry? {}\n", .{removed});
print("2 is in map? {}\n", .{map.contains(2)});
print("2 => {?}\n", .{map.get(2)});
}$ zig run autohashmap.zig
2 is in map? true
2 => 4
2 => 8
entry count: 1
removed entry? true
2 is in map? false
2 => null
The get method just returns a value, but some methods
give us pointers to keys and values:
const std = @import("std");
const print = std.debug.print;
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
// We’ll use a map to count how often each character
// occurs in a string.
var map = std.AutoHashMap(u8, u64).init(allocator);
defer map.deinit();
// getOrPutValue adds a key unless it already
// exists, and returns an 'Entry' with pointers to
// the key and the value.
for ("banana") |c| {
const entry = try map.getOrPutValue(c, 0);
entry.value_ptr.* += 1;
}
// The 'iterator' method gives us an iterator that
// also uses 'Entry'.
print("entries:", .{});
var iterator = map.iterator();
while (iterator.next()) |entry|
print(" {c}=>{}", .{
entry.key_ptr.*,
entry.value_ptr.*,
});
print("\n", .{});
// We can also iterate over just the keys.
print("keys:", .{});
var key_iterator = map.keyIterator();
while (key_iterator.next()) |key|
print(" {c}", .{key.*});
print("\n", .{});
// Or just the values.
print("values:", .{});
var value_iterator = map.valueIterator();
while (value_iterator.next()) |value|
print(" {}", .{value.*});
print("\n", .{});
}$ zig run autohashmap-2.zig
entries: b=>1 a=>3 n=>2
keys: b a n
values: 1 3 2
Locking a map
One thing to keep in mind with these methods is that pointers into the map become invalid when the memory for the map is re-allocated. Generally, any methods that add or remove elements invalidate pointers into the map, including iterators.
const std = @import("std");
const print = std.debug.print;
// Zig’s HashMap has a neat feature that can help us
// debug these issues. The idea is to put the map into a
// “locked” state where any method that would invalide
// pointers triggers an assertion.
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
var map = std.AutoHashMap(i8, i8).init(allocator);
defer map.deinit();
try map.put(1, 1);
try map.put(2, 4);
try map.put(3, 9);
printKeys(&map);
try addKeys(&map);
}
// This function iterates over the map, so to be safe
// we’ll lock it at the start. We’ll defer a call to
// unlock it.
fn printKeys(map: *std.AutoHashMap(i8, i8)) void {
map.lockPointers();
defer map.unlockPointers();
var iterator = map.keyIterator();
while (iterator.next()) |ptr| {
const key = ptr.*;
print("{}\n", .{key});
}
print("\n", .{});
}
// This function calls `put` to add elements while
// iterating over the map, triggering an assertion.
fn addKeys(map: *std.AutoHashMap(i8, i8)) !void {
map.lockPointers();
defer map.unlockPointers();
var iterator = map.keyIterator();
while (iterator.next()) |ptr| {
const key = ptr.*;
try map.put(-key, key * key); // uh-oh
}
}$ zig run autohashmap-3.zig
2
1
3
thread 123 panic: reached unreachable code
std/hash_map.zig:1101:21: 0x1078e2c in getOrPutContextAdapted__anon_30358 (locking)
if (!gop.found_existing) {
^
locking.zig:40:20: 0x102753b in addKeys (locking)
try map.put(-key, key * key); // uh-oh
^
std/start.zig:190:5: 0x102575d in _start (locking)
asm volatile (switch (native_arch) {
^
Aborted
Using a map as a set
We can use a hash map with value type void as a set.
const std = @import("std");
const print = std.debug.print;
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
var set = std.AutoHashMap(u8, void).init(allocator);
defer set.deinit();
// {} is a value of type void.
try set.put(3, {});
try set.put(7, {});
try set.put(31, {});
print("3 is in set? {}\n", .{set.contains(3)});
print("4 is in set? {}\n", .{set.contains(4)});
print("items in set:", .{});
var key_iterator = set.keyIterator();
while (key_iterator.next()) |key|
print(" {}", .{key.*});
print("\n", .{});
}$ zig run autohashmap-4.zig
3 is in set? true
4 is in set? false
items in set: 31 7 3
std.hash_map.AutoHashMapUnmanaged
AutoHashMapUnmanaged
is a variant of AutoHashMap that doesn’t store the
allocator:
const std = @import("std");
const print = std.debug.print;
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
// We don’t need to provide an allocator to 'init'.
var map = std.AutoHashMapUnmanaged(u8, u8).empty;
defer map.deinit(allocator);
// But we do for every call to 'put'.
try map.put(allocator, 2, 4);
try map.put(allocator, 3, 9);
try map.put(allocator, 4, 16);
print("entries:", .{});
var iterator = map.iterator();
while (iterator.next()) |entry|
print(" {}=>{}", .{
entry.key_ptr.*,
entry.value_ptr.*,
});
print("\n", .{});
}$ zig run autohashmap-5.zig
entries: 2=>4 4=>16 3=>9
std.array_hash_map.Auto
std.array_hash_map.Auto is an implementation of a hash map that stores keys and values sequentially. It preserves the order of entries, so iteration will produce keys in the order that they were added.
const std = @import("std");
const print = std.debug.print;
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
var map = std.array_hash_map.Auto(u8, u8).empty;
defer map.deinit(allocator);
try map.put(allocator, 2, 4);
try map.put(allocator, 3, 9);
try map.put(allocator, 4, 16);
print("entries:", .{});
var iterator = map.iterator();
while (iterator.next()) |entry|
print(" {}=>{}", .{
entry.key_ptr.*,
entry.value_ptr.*,
});
print("\n", .{});
}$ zig run autohashmap-6.zig
entries: 2=>4 3=>9 4=>16
Next example: StringHashMap.