authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2016-05-08 16:05:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2016-05-08 16:05:41-07:00
log9e905ab3646ebb41f834d04707fd0fc3857d7834
treeea26301e7891a4fd882161ed86c1bf4472169776
parent0c32b0b4ad32549193b46d33026d72b5e4a17bd9

not-yet-working implementation of generic hash map


3 files changed, 267 insertions(+), 2 deletions(-)

std/hash_map.zig created+262
...@@ -0,0 +1,262 @@
1const assert = @import("index.zig").assert;
2const math = @import("math.zig");
3const mem = @import("mem.zig");
4const Allocator = mem.Allocator;
5
6const want_modification_safety = !@compile_var("is_release");
7const debug_u32 = if (want_modification_safety) void else u32;
8
9pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)->bool) {
10 entries: []Entry,
11 size: isize,
12 max_distance_from_start_index: isize,
13 allocator: &Allocator,
14 // this is used to detect bugs where a hashtable is edited while an iterator is running.
15 modification_count: debug_u32,
16
17 const Self = HashMap(K, V, hash, eql);
18
19 pub struct Entry {
20 used: bool,
21 distance_from_start_index: isize,
22 key: K,
23 value: V,
24 }
25
26 pub struct Iterator {
27 hm: &Self,
28 // how many items have we returned
29 count: isize,
30 // iterator through the entry array
31 index: isize,
32 // used to detect concurrent modification
33 initial_modification_count: debug_u32,
34
35 pub fn next(it: &Iterator) -> ?&Entry {
36 if (want_modification_safety) {
37 assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification
38 }
39 if (it.count >= it.hm.size) return null;
40 while (it.index < it.hm.entries.len; it.index += 1) {
41 const entry = &it.hm.entries[it.index];
42 if (entry.used) {
43 it.index += 1;
44 it.count += 1;
45 return entry;
46 }
47 }
48 unreachable{} // no next item
49 }
50 };
51
52 pub fn init(hm: &Self, allocator: &Allocator, capacity: isize) {
53 assert(capacity > 0);
54 hm.allocator = allocator;
55 hm.init_capacity(capacity);
56 }
57
58 pub fn deinit(hm: &Self) {
59 free(_entries);
60 }
61
62 pub fn clear(hm: &Self) {
63 for (hm.entries) |*entry| {
64 entry.used = false;
65 }
66 hm.size = 0;
67 hm.max_distance_from_start_index = 0;
68 hm.increment_modification_count();
69 }
70
71 pub fn put(hm: &Self, key: K, value: V) {
72 hm.increment_modification_count();
73 hm.internal_put(key, value);
74
75 // if we get too full (60%), double the capacity
76 if (hm.size * 5 >= hm.entries.len * 3) {
77 const old_entries = hm.entries;
78 hm.init_capacity(hm.entries.len * 2);
79 // dump all of the old elements into the new table
80 for (old_entries) |*old_entry| {
81 if (old_entry.used) {
82 hm.internal_put(old_entry.key, old_entry.value);
83 }
84 }
85 hm.allocator.free(hm.allocator, ([]u8)(old_entries));
86 }
87 }
88
89 pub fn get(hm: &Self, key: K) {
90 return internal_get(key);
91 }
92
93 pub fn remove(hm: &Self, key: K) {
94 hm.increment_modification_count();
95 const start_index = hm.key_to_index(key);
96 {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {
97 const index = (start_index + roll_over) % hm.entries.len;
98 const entry = &hm.entries[index];
99
100 assert(entry.used); // key not found
101
102 if (!eql(entry.key, key)) continue;
103
104 while (roll_over < hm.entries.len; roll_over += 1) {
105 const next_index = (start_index + roll_over + 1) % hm.entries.len;
106 const next_entry = &hm.entries[next_index];
107 if (!next_entry.used || next_entry.distance_from_start_index == 0) {
108 entry.used = false;
109 hm.size -= 1;
110 return;
111 }
112 *entry = *next_entry;
113 entry.distance_from_start_index -= 1;
114 entry = next_entry;
115 }
116 unreachable{} // shifting everything in the table
117 }}
118 unreachable{} // key not found
119 }
120
121 pub fn entry_iterator(hm: &Self) -> Iterator {
122 return Iterator {
123 .hm = hm,
124 .count = 0,
125 .index = 0,
126 .initial_modification_count = hm.modification_count,
127 };
128 }
129
130 fn init_capacity(hm: &Self, capacity: isize) {
131 hm.capacity = capacity;
132 hm.entries = ([]Entry)(hm.allocator.alloc(hm.allocator, capacity * @sizeof(Entry)));
133 hm.size = 0;
134 hm.max_distance_from_start_index = 0;
135 for (hm.entries) |*entry| {
136 entry.used = false;
137 }
138 }
139
140 fn increment_modification_count(hm: &Self) {
141 if (want_modification_safety) {
142 hm.modification_count += 1;
143 }
144 }
145
146 fn internal_put(hm: &Self, K orig_key, V orig_value) {
147 var key = orig_key;
148 var value = orig_value;
149 const start_index = key_to_index(key);
150 var roll_over: isize = 0;
151 var distance_from_start_index: isize = 0;
152 while (roll_over < hm.entries.len; {roll_over += 1; distance_from_start_index += 1}) {
153 const index = (start_index + roll_over) % hm.entries.len;
154 const entry = &hm.entries[index];
155
156 if (entry.used && !eql(entry.key, key)) {
157 if (entry.distance_from_start_index < distance_from_start_index) {
158 // robin hood to the rescue
159 const tmp = *entry;
160 hm.max_distance_from_start_index = math.max(isize)(
161 hm.max_distance_from_start_index, distance_from_start_index);
162 *entry = Entry {
163 .used = true,
164 .distance_from_start_index = distance_from_start_index,
165 .key = key,
166 .value = value,
167 };
168 key = tmp.key;
169 value = tmp.value;
170 distance_from_start_index = tmp.distance_from_start_index;
171 }
172 continue;
173 }
174
175 if (!entry.used) {
176 // adding an entry. otherwise overwriting old value with
177 // same key
178 hm.size += 1;
179 }
180
181 hm.max_distance_from_start_index = math.max(isize)(distance_from_start_index, hm.max_distance_from_start_index);
182 *entry = {
183 .used = true,
184 .distance_from_start_index = distance_from_start_index,
185 .key = key,
186 .value = value,
187 };
188 return;
189 }
190 unreachable{} // put into a full map
191 }
192
193 fn internal_get(hm: &Self, key: K) -> ?&Entry {
194 const start_index = key_to_index(key);
195 {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {
196 const index = (start_index + roll_over) % hm.entries.len;
197 const entry = &hm.entries[index];
198
199 if (!entry.used) return null;
200 if (eql(entry.key, key)) return entry;
201 }}
202 return null;
203 }
204
205 Entry *internal_get(const K &key) const {
206 int start_index = key_to_index(key);
207 for (int roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) {
208 int index = (start_index + roll_over) % _capacity;
209 Entry *entry = &_entries[index];
210
211 if (!entry->used)
212 return NULL;
213
214 if (EqualFn(entry->key, key))
215 return entry;
216 }
217 return NULL;
218 }
219
220 fn key_to_index(hm: &Self, key: K) -> isize {
221 return isize(hash(key)) % hm.entries.len;
222 }
223}
224
225var global_allocator = Allocator {
226 .alloc = global_alloc,
227 .realloc = global_realloc,
228 .free = global_free,
229 .context = null,
230};
231
232var some_mem: [200]u8 = undefined;
233var some_mem_index: isize = 0;
234
235fn global_alloc(self: &Allocator, n: isize) -> %[]u8 {
236 const result = some_mem[some_mem_index ... some_mem_index + n];
237 some_mem_index += n;
238 return result;
239}
240
241fn global_realloc(self: &Allocator, old_mem: []u8, new_size: isize) -> %[]u8 {
242 const result = %return global_alloc(self, new_size);
243 @memcpy(result.ptr, old_mem.ptr, old_mem.len);
244 return result;
245}
246
247fn global_free(self: &Allocator, old_mem: []u8) {
248}
249
250#attribute("test")
251fn basic_hash_map_test() {
252 var map: HashMap(i32, i32, hash_i32, eql_i32);
253 map.init(&global_allocator, 4);
254 defer map.deinit();
255}
256
257fn hash_i32(x: i32) -> u32 {
258 *(&u32)(&x)
259}
260fn eql_i32(a: i32, b: i32) -> bool {
261 a == b
262}
std/io.zig+1-1
...@@ -69,7 +69,7 @@ pub struct OutStream {...@@ -69,7 +69,7 @@ pub struct OutStream {
69 const dest_space_left = os.buffer.len - os.index;69 const dest_space_left = os.buffer.len - os.index;
7070
71 while (src_bytes_left > 0) {71 while (src_bytes_left > 0) {
72 const copy_amt = math.min_isize(dest_space_left, src_bytes_left);72 const copy_amt = math.min(isize)(dest_space_left, src_bytes_left);
73 @memcpy(&os.buffer[os.index], &bytes[src_index], copy_amt);73 @memcpy(&os.buffer[os.index], &bytes[src_index], copy_amt);
74 os.index += copy_amt;74 os.index += copy_amt;
75 if (os.index == os.buffer.len) {75 if (os.index == os.buffer.len) {
std/math.zig+4-1
...@@ -26,7 +26,10 @@ pub fn f64_is_inf(f: f64) -> bool {...@@ -26,7 +26,10 @@ pub fn f64_is_inf(f: f64) -> bool {
26 f == f64_get_neg_inf() || f == f64_get_pos_inf()26 f == f64_get_neg_inf() || f == f64_get_pos_inf()
27}27}
2828
29pub fn min_isize(x: isize, y: isize) -> isize {29pub fn min(T: type)(x: T, y: T) -> T {
30 if (x < y) x else y30 if (x < y) x else y
31}31}
3232
33pub fn max(T: type)(x: T, y: T) -> T {
34 if (x > y) x else y
35}