/Users/alexjokela/projects/lattice/src/ds/hashmap.c
Line | Count | Source |
1 | | #include "ds/hashmap.h" |
2 | | #include <stdlib.h> |
3 | | #include <string.h> |
4 | | #include <assert.h> |
5 | | |
6 | 37.1k | #define INITIAL_CAP 16 |
7 | 357k | #define LOAD_FACTOR 70 /* percent */ |
8 | | |
9 | | /* FNV-1a hash */ |
10 | 1.03M | static size_t fnv1a(const char *key) { |
11 | 1.03M | size_t hash = 14695981039346656037ULL; |
12 | 8.36M | for (const char *p = key; *p; p++) { |
13 | 7.33M | hash ^= (size_t)(unsigned char)*p; |
14 | 7.33M | hash *= 1099511628211ULL; |
15 | 7.33M | } |
16 | 1.03M | return hash; |
17 | 1.03M | } |
18 | | |
19 | 45.5k | static void lat_map_alloc_entries(LatMap *m, size_t cap) { |
20 | 45.5k | m->entries = calloc(cap, sizeof(LatMapEntry)); |
21 | | /* Point each entry's value to its inline buffer */ |
22 | 1.58M | for (size_t i = 0; i < cap; i++) |
23 | 1.53M | m->entries[i].value = m->entries[i]._ibuf; |
24 | 45.5k | m->cap = cap; |
25 | 45.5k | m->count = 0; |
26 | 45.5k | m->live = 0; |
27 | 45.5k | } |
28 | | |
29 | 37.1k | LatMap lat_map_new(size_t value_size) { |
30 | 37.1k | assert(value_size <= LAT_MAP_INLINE_MAX); |
31 | 37.1k | LatMap m; |
32 | 37.1k | m.value_size = value_size; |
33 | 37.1k | lat_map_alloc_entries(&m, INITIAL_CAP); |
34 | 37.1k | return m; |
35 | 37.1k | } |
36 | | |
37 | 37.1k | void lat_map_free(LatMap *m) { |
38 | 1.10M | for (size_t i = 0; i < m->cap; i++) { |
39 | 1.06M | if (m->entries[i].state == MAP_OCCUPIED) { |
40 | 342k | free(m->entries[i].key); |
41 | | /* value is inline — no free needed */ |
42 | 342k | } |
43 | 1.06M | } |
44 | 37.1k | free(m->entries); |
45 | 37.1k | m->entries = NULL; |
46 | 37.1k | m->cap = 0; |
47 | 37.1k | m->count = 0; |
48 | 37.1k | m->live = 0; |
49 | 37.1k | } |
50 | | |
51 | 683k | static size_t lat_map_find_slot(const LatMap *m, const char *key) { |
52 | 683k | size_t idx = fnv1a(key) % m->cap; |
53 | 683k | size_t first_tombstone = (size_t)-1; |
54 | 1.24M | for (size_t i = 0; i < m->cap; i++) { |
55 | 1.24M | size_t pos = (idx + i) % m->cap; |
56 | 1.24M | LatMapEntry *e = &m->entries[pos]; |
57 | 1.24M | if (e->state == MAP_EMPTY) { |
58 | 669k | return (first_tombstone != (size_t)-1) ? first_tombstone : pos; |
59 | 669k | } |
60 | 577k | if (e->state == MAP_TOMBSTONE) { |
61 | 1 | if (first_tombstone == (size_t)-1) first_tombstone = pos; |
62 | 1 | continue; |
63 | 1 | } |
64 | | /* MAP_OCCUPIED */ |
65 | 577k | if (strcmp(e->key, key) == 0) { |
66 | 14.6k | return pos; |
67 | 14.6k | } |
68 | 577k | } |
69 | 0 | return (first_tombstone != (size_t)-1) ? first_tombstone : 0; |
70 | 683k | } |
71 | | |
72 | 8.35k | static void lat_map_rehash(LatMap *m) { |
73 | 8.35k | size_t old_cap = m->cap; |
74 | 8.35k | LatMapEntry *old = m->entries; |
75 | | |
76 | 8.35k | lat_map_alloc_entries(m, old_cap * 2); |
77 | | |
78 | 480k | for (size_t i = 0; i < old_cap; i++) { |
79 | 472k | if (old[i].state == MAP_OCCUPIED) { |
80 | 326k | size_t pos = lat_map_find_slot(m, old[i].key); |
81 | 326k | m->entries[pos].key = old[i].key; |
82 | 326k | m->entries[pos].state = MAP_OCCUPIED; |
83 | | /* value pointer already points to _ibuf; copy inline data */ |
84 | 326k | memcpy(m->entries[pos]._ibuf, old[i]._ibuf, m->value_size); |
85 | 326k | m->count++; |
86 | 326k | m->live++; |
87 | 326k | } |
88 | 472k | } |
89 | 8.35k | free(old); |
90 | 8.35k | } |
91 | | |
92 | 357k | bool lat_map_set(LatMap *m, const char *key, const void *value) { |
93 | 357k | if ((m->count + 1) * 100 > m->cap * LOAD_FACTOR) { |
94 | 8.35k | lat_map_rehash(m); |
95 | 8.35k | } |
96 | | |
97 | 357k | size_t pos = lat_map_find_slot(m, key); |
98 | 357k | LatMapEntry *e = &m->entries[pos]; |
99 | | |
100 | 357k | if (e->state == MAP_OCCUPIED) { |
101 | | /* Update existing — value is inline */ |
102 | 14.6k | memcpy(e->value, value, m->value_size); |
103 | 14.6k | return false; |
104 | 14.6k | } |
105 | | |
106 | | /* New entry — value stored inline (e->value already points to e->_ibuf) */ |
107 | 357k | bool was_empty = (e->state == MAP_EMPTY); |
108 | 342k | e->key = strdup(key); |
109 | 342k | memcpy(e->value, value, m->value_size); |
110 | 342k | e->state = MAP_OCCUPIED; |
111 | 342k | if (was_empty) m->count++; |
112 | 342k | m->live++; |
113 | 342k | return true; |
114 | 357k | } |
115 | | |
116 | 372k | void *lat_map_get(const LatMap *m, const char *key) { |
117 | 372k | if (m->live == 0) return NULL; |
118 | 347k | size_t idx = fnv1a(key) % m->cap; |
119 | 836k | for (size_t i = 0; i < m->cap; i++) { |
120 | 836k | size_t pos = (idx + i) % m->cap; |
121 | 836k | LatMapEntry *e = &m->entries[pos]; |
122 | 836k | if (e->state == MAP_EMPTY) return NULL; |
123 | 537k | if (e->state == MAP_OCCUPIED && strcmp(e->key, key) == 0) { |
124 | 48.8k | return e->value; |
125 | 48.8k | } |
126 | 537k | } |
127 | 0 | return NULL; |
128 | 347k | } |
129 | | |
130 | 3.40k | void *lat_map_get_prehashed(const LatMap *m, const char *key, size_t hash) { |
131 | 3.40k | if (m->live == 0) return NULL; |
132 | 3.40k | size_t idx = hash % m->cap; |
133 | 4.78k | for (size_t i = 0; i < m->cap; i++) { |
134 | 4.78k | size_t pos = (idx + i) % m->cap; |
135 | 4.78k | LatMapEntry *e = &m->entries[pos]; |
136 | 4.78k | if (e->state == MAP_EMPTY) return NULL; |
137 | 4.78k | if (e->state == MAP_OCCUPIED && strcmp(e->key, key) == 0) { |
138 | 3.39k | return e->value; |
139 | 3.39k | } |
140 | 4.78k | } |
141 | 0 | return NULL; |
142 | 3.40k | } |
143 | | |
144 | 15 | bool lat_map_remove(LatMap *m, const char *key) { |
145 | 15 | if (m->live == 0) return false; |
146 | 12 | size_t idx = fnv1a(key) % m->cap; |
147 | 12 | for (size_t i = 0; i < m->cap; i++) { |
148 | 12 | size_t pos = (idx + i) % m->cap; |
149 | 12 | LatMapEntry *e = &m->entries[pos]; |
150 | 12 | if (e->state == MAP_EMPTY) return false; |
151 | 12 | if (e->state == MAP_OCCUPIED && strcmp(e->key, key) == 0) { |
152 | 12 | free(e->key); |
153 | 12 | e->key = NULL; |
154 | | /* value is inline, no free needed */ |
155 | 12 | e->state = MAP_TOMBSTONE; |
156 | 12 | m->live--; |
157 | 12 | return true; |
158 | 12 | } |
159 | 12 | } |
160 | 0 | return false; |
161 | 12 | } |
162 | | |
163 | 208 | bool lat_map_contains(const LatMap *m, const char *key) { |
164 | 208 | return lat_map_get(m, key) != NULL; |
165 | 208 | } |
166 | | |
167 | 101 | size_t lat_map_len(const LatMap *m) { |
168 | 101 | return m->live; |
169 | 101 | } |
170 | | |
171 | 75.1k | void lat_map_iter(const LatMap *m, LatMapIterFn fn, void *ctx) { |
172 | 1.72M | for (size_t i = 0; i < m->cap; i++) { |
173 | 1.64M | if (m->entries[i].state == MAP_OCCUPIED) { |
174 | 356k | fn(m->entries[i].key, m->entries[i].value, ctx); |
175 | 356k | } |
176 | 1.64M | } |
177 | 75.1k | } |