summaryrefslogtreecommitdiff
path: root/src/comptime_map.zig
diff options
context:
space:
mode:
authorGravatar Sam Atman2026-02-04 18:01:36 -0500
committerGravatar Sam Atman2026-02-04 18:01:36 -0500
commitba5d9081b479e95ffa7f3baf751beedd370cec14 (patch)
treec12041d8aab9f9ff68b25a2e2c9042073c3d5f61 /src/comptime_map.zig
parentConvert Words module to no-allocation (diff)
downloadzg-ba5d9081b479e95ffa7f3baf751beedd370cec14.tar.gz
zg-ba5d9081b479e95ffa7f3baf751beedd370cec14.tar.xz
zg-ba5d9081b479e95ffa7f3baf751beedd370cec14.zip
Normalization and case folding
Both of which deserve some further attention.
Diffstat (limited to 'src/comptime_map.zig')
-rw-r--r--src/comptime_map.zig176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/comptime_map.zig b/src/comptime_map.zig
new file mode 100644
index 0000000..b2c4d11
--- /dev/null
+++ b/src/comptime_map.zig
@@ -0,0 +1,176 @@
1//! A copypasta of Vexu's comptime hashmap:
2//!
3//! https://github.com/Vexu/comptime_hash_map
4//!
5//! (License at base of file, thanks Vexu!)
6//!
7
8/// A comptime hashmap constructed with automatically selected hash and eql functions.
9pub fn AutoComptimeHashMap(comptime K: type, comptime V: type, comptime values: anytype) type {
10 return ComptimeHashMap(K, V, hash_map.AutoContext(K), values);
11}
12
13/// Builtin hashmap for strings as keys.
14pub fn ComptimeStringHashMap(comptime V: type, comptime values: anytype) type {
15 return ComptimeHashMap([]const u8, V, hash_map.StringContext, values);
16}
17
18/// A hashmap which is constructed at compile time from constant values.
19/// Intended to be used as a faster lookup table.
20pub fn ComptimeHashMap(comptime K: type, comptime V: type, comptime ctx: type, comptime values: anytype) type {
21 std.debug.assert(values.len != 0);
22 @setEvalBranchQuota(1000 * values.len);
23
24 const Entry = struct {
25 key: K = undefined,
26 val: V = undefined,
27 used: bool = false,
28 };
29
30 // ensure that the hash map will be at most 60% full
31 const size = if (@import("builtin").is_test) values.len else math.ceilPowerOfTwo(usize, values.len * 5 / 3) catch unreachable;
32 comptime var slots = [1]Entry{.{}} ** size;
33 comptime var distance: [size]usize = .{0} ** size;
34
35 comptime var max_distance_from_start_index = 0;
36
37 slot_loop: for (values) |kv| {
38 var key: K = kv.@"0";
39 var value: V = kv.@"1";
40
41 const start_index = @as(usize, ctx.hash(undefined, key)) & (size - 1);
42
43 var roll_over = 0;
44 var distance_from_start_index = 0;
45 while (roll_over < size) : ({
46 roll_over += 1;
47 distance_from_start_index += 1;
48 }) {
49 const index = (start_index + roll_over) & (size - 1);
50 const entry = &slots[index];
51
52 if (entry.used and !ctx.eql(undefined, entry.key, key)) {
53 if (distance[index] < distance_from_start_index) {
54 // robin hood to the rescue
55 const tmp = slots[index];
56 max_distance_from_start_index = @max(max_distance_from_start_index, distance_from_start_index);
57 entry.* = .{
58 .used = true,
59 .key = key,
60 .val = value,
61 };
62 const tmp_distance = distance[index];
63 distance[index] = distance_from_start_index;
64 key = tmp.key;
65 value = tmp.val;
66 distance_from_start_index = tmp_distance;
67 }
68 continue;
69 }
70
71 max_distance_from_start_index = @max(distance_from_start_index, max_distance_from_start_index);
72 entry.* = .{
73 .used = true,
74 .key = key,
75 .val = value,
76 };
77 distance[index] = distance_from_start_index;
78 continue :slot_loop;
79 }
80 unreachable; // put into a full map
81 }
82
83 return struct {
84 const entries = slots;
85
86 pub fn has(key: K) bool {
87 return get(key) != null;
88 }
89
90 pub fn get(key: K) ?*const V {
91 const start_index = @as(usize, ctx.hash(undefined, key)) & (size - 1);
92 {
93 var roll_over: usize = 0;
94 while (roll_over <= max_distance_from_start_index) : (roll_over += 1) {
95 const index = (start_index + roll_over) & (size - 1);
96 const entry = &entries[index];
97
98 if (!entry.used) return null;
99 if (ctx.eql(undefined, entry.key, key)) return &entry.val;
100 }
101 }
102 return null;
103 }
104 };
105}
106
107test "basic usage" {
108 const map = ComptimeStringHashMap(usize, .{
109 .{ "foo", 1 },
110 .{ "bar", 2 },
111 .{ "baz", 3 },
112 .{ "quux", 4 },
113 .{ "Foo", 1 },
114 .{ "Bar", 2 },
115 .{ "Baz", 3 },
116 .{ "Quux", 4 },
117 });
118
119 try testing.expect(map.has("foo"));
120 try testing.expect(map.has("bar"));
121 try testing.expect(map.has("Foo"));
122 try testing.expect(map.has("Bar"));
123 try testing.expect(!map.has("zig"));
124 try testing.expect(!map.has("ziguana"));
125
126 try testing.expect(map.get("baz").?.* == 3);
127 try testing.expect(map.get("quux").?.* == 4);
128 try testing.expect(map.get("nah") == null);
129 try testing.expect(map.get("...") == null);
130}
131
132test "auto comptime hash map" {
133 const map = AutoComptimeHashMap(usize, []const u8, .{
134 .{ 1, "foo" },
135 .{ 2, "bar" },
136 .{ 3, "baz" },
137 .{ 45, "quux" },
138 });
139
140 try testing.expect(map.has(1));
141 try testing.expect(map.has(2));
142 try testing.expect(!map.has(4));
143 try testing.expect(!map.has(1_000_000));
144
145 try testing.expectEqualStrings("foo", map.get(1).?.*);
146 try testing.expectEqualStrings("bar", map.get(2).?.*);
147 try testing.expect(map.get(4) == null);
148 try testing.expect(map.get(4_000_000) == null);
149}
150
151const std = @import("std");
152const hash_map = std.hash_map;
153const testing = std.testing;
154const math = std.math;
155
156// MIT License
157//
158// Copyright (c) 2020 Veikka Tuominen
159//
160// Permission is hereby granted, free of charge, to any person obtaining a copy
161// of this software and associated documentation files (the "Software"), to deal
162// in the Software without restriction, including without limitation the rights
163// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
164// copies of the Software, and to permit persons to whom the Software is
165// furnished to do so, subject to the following conditions:
166//
167// The above copyright notice and this permission notice shall be included in all
168// copies or substantial portions of the Software.
169//
170// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
171// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
172// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
173// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
174// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
175// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
176// SOFTWARE.