data-structures-c
Loading...
Searching...
No Matches
fhashtable.h
Go to the documentation of this file.
1/* fhashtable.h
2 *
3 * Copyright (C) 2023 abxh
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 * See the file LICENSE included with this distribution for more
10 * information. */
11
41// macro definitions: {{{
42
43#ifndef FHASHTABLE_H
44#define FHASHTABLE_H
45
46#include "fnvhash.h" // fnvhash_32, fnvhash_32_str
47#include "is_pow2.h" // is_pow2
48#include "murmurhash.h" // murmur_32
49#include "paste.h" // PASTE, XPASTE, JOIN
50#include "round_up_pow2.h" // round_up_pow2_32
51
52#include <assert.h>
53#include <stdint.h>
54#include <stdio.h>
55#include <stdlib.h>
56
61#define FHASHTABLE_EMPTY_SLOT_OFFSET (UINT32_MAX)
62
75#define fhashtable_for_each(self, index, key_, value_) \
76 for ((index) = 0; (index) < (self)->capacity; (index)++) \
77 if ((self)->slots[(index)].offset != FHASHTABLE_EMPTY_SLOT_OFFSET \
78 && ((key_) = (self)->slots[(index)].key, (value_) = (self)->slots[(index)].value, true))
79
90#define fhashtable_calc_sizeof(fhashtable_name, capacity) \
91 (uint32_t)(offsetof(struct fhashtable_name, slots) + capacity * sizeof(((struct fhashtable_name *)0)->slots[0]))
92
103#define fhashtable_calc_sizeof_overflows(fhashtable_name, capacity) \
104 (capacity \
105 > (UINT32_MAX - offsetof(struct fhashtable_name, slots)) / sizeof(((struct fhashtable_name *)0)->slots[0]))
106
107#endif // FHASHTABLE_H
108
116#ifndef NAME
117#error "Must define NAME."
118#define NAME fhashtable
119#else
120#define FHASHTABLE_NAME NAME
121#endif
122
130#ifndef KEY_TYPE
131#error "Must define KEY_TYPE."
132#define KEY_TYPE int
133#endif
134
142#ifndef VALUE_TYPE
143#error "Must define VALUE_TYPE."
144#define VALUE_TYPE int
145#endif
146
164#ifndef KEY_IS_EQUAL
165#error "Must define KEY_IS_EQUAL."
166#define KEY_IS_EQUAL(a, b) ((a) == (b))
167#endif
168
179#ifndef HASH_FUNCTION
180#error "Must define HASH_FUNCTION."
181#define HASH_FUNCTION(key) (murmur3_32((uint8_t *)&(key), sizeof(KEY_TYPE), 0))
182#endif
183
185#define FHASHTABLE_TYPE struct FHASHTABLE_NAME
186#define FHASHTABLE_SLOT_TYPE struct JOIN(FHASHTABLE_NAME, slot)
187#define FHASHTABLE_SLOT JOIN(FHASHTABLE_NAME, slot)
188#define FHASHTABLE_INIT JOIN(FHASHTABLE_NAME, init)
189#define FHASHTABLE_IS_FULL JOIN(FHASHTABLE_NAME, is_full)
190#define FHASHTABLE_CONTAINS_KEY JOIN(FHASHTABLE_NAME, contains_key)
191#define FHASHTABLE_CALC_SIZEOF JOIN(FHASHTABLE_NAME, calc_sizeof)
192#define FHASHTABLE_SWAP_SLOTS JOIN(internal, JOIN(FHASHTABLE_NAME, swap_slots))
193#define FHASHTABLE_BACKSHIFT JOIN(internal, JOIN(FHASHTABLE_NAME, backshift))
195
196// }}}
197
198// type definitions: {{{
199
204struct JOIN(FHASHTABLE_NAME, slot) {
205 uint32_t offset;
208};
209
214struct FHASHTABLE_NAME {
215 uint32_t count;
216 uint32_t capacity;
217 FHASHTABLE_SLOT_TYPE slots[];
218};
219
220// }}}
221
222// function definitions: {{{
223
230static inline FHASHTABLE_TYPE *JOIN(FHASHTABLE_NAME, init)(FHASHTABLE_TYPE *self, const uint32_t pow2_capacity)
231{
232 assert(self);
233 assert(is_pow2(pow2_capacity));
234
235 self->count = 0;
236 self->capacity = pow2_capacity;
237
238 for (uint32_t i = 0; i < self->capacity; i++) {
239 self->slots[i].offset = FHASHTABLE_EMPTY_SLOT_OFFSET;
240 }
241
242 return self;
243}
244
256static inline FHASHTABLE_TYPE *JOIN(FHASHTABLE_NAME, create)(const uint32_t min_capacity)
257{
258 if (min_capacity == 0 || min_capacity > UINT32_MAX / 2 + 1) {
259 return NULL;
260 }
261
262 const uint32_t capacity = round_up_pow2_32(min_capacity);
263
264 if (fhashtable_calc_sizeof_overflows(FHASHTABLE_NAME, capacity)) {
265 return NULL;
266 }
267
268 const uint32_t size = fhashtable_calc_sizeof(FHASHTABLE_NAME, capacity);
269
270 FHASHTABLE_TYPE *self = (FHASHTABLE_TYPE *)calloc(1, size);
271
272 if (!self) {
273 return NULL;
274 }
275
276 FHASHTABLE_INIT(self, capacity);
277
278 return self;
279}
280
289static inline void JOIN(FHASHTABLE_NAME, destroy)(FHASHTABLE_TYPE *self)
290{
291 assert(self);
292
293 free(self);
294}
295
303static inline bool JOIN(FHASHTABLE_NAME, is_empty)(const FHASHTABLE_TYPE *self)
304{
305 assert(self != NULL);
306
307 return self->count == 0;
308}
309
317static inline bool JOIN(FHASHTABLE_NAME, is_full)(const FHASHTABLE_TYPE *self)
318{
319 assert(self != NULL);
320
321 return self->count == self->capacity;
322}
323
332static inline bool JOIN(FHASHTABLE_NAME, contains_key)(const FHASHTABLE_TYPE *self, const KEY_TYPE key)
333{
334 assert(self != NULL);
335
336 const uint32_t key_hash = HASH_FUNCTION(key);
337 const uint32_t index_mask = self->capacity - 1;
338
339 uint32_t index = key_hash & index_mask;
340 uint32_t max_possible_offset = 0;
341
342 while (true) {
343 const bool not_empty = self->slots[index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
344
345 const bool below_max = max_possible_offset <= self->slots[index].offset;
346
347 if (!(not_empty && below_max)) {
348 break;
349 }
350
351 if (KEY_IS_EQUAL(self->slots[index].key, key)) {
352 return true;
353 }
354
355 index++;
356 index &= index_mask;
357 max_possible_offset++;
358 }
359 return false;
360}
361
375static inline VALUE_TYPE *JOIN(FHASHTABLE_NAME, get_value_mut)(FHASHTABLE_TYPE *self, const KEY_TYPE key)
376{
377 assert(self != NULL);
378
379 const uint32_t key_hash = HASH_FUNCTION(key);
380 const uint32_t index_mask = self->capacity - 1;
381
382 uint32_t index = key_hash & index_mask;
383 uint32_t max_possible_offset = 0;
384
385 while (true) {
386 const bool not_empty = self->slots[index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
387
388 const bool below_max = max_possible_offset <= self->slots[index].offset;
389
390 if (!(not_empty && below_max)) {
391 break;
392 }
393
394 if (KEY_IS_EQUAL(self->slots[index].key, key)) {
395 return &self->slots[index].value;
396 }
397
398 index++;
399 index &= index_mask;
400 max_possible_offset++;
401 }
402 return NULL;
403}
404
417static inline VALUE_TYPE JOIN(FHASHTABLE_NAME, get_value)(const FHASHTABLE_TYPE *self, const KEY_TYPE key,
418 VALUE_TYPE default_value)
419{
420 assert(self != NULL);
421
422 const uint32_t key_hash = HASH_FUNCTION(key);
423 const uint32_t index_mask = self->capacity - 1;
424
425 uint32_t index = key_hash & index_mask;
426 uint32_t max_possible_offset = 0;
427
428 while (true) {
429 const bool not_empty = self->slots[index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
430
431 const bool below_max = max_possible_offset <= self->slots[index].offset;
432
433 if (!(not_empty && below_max)) {
434 break;
435 }
436
437 if (KEY_IS_EQUAL(self->slots[index].key, key)) {
438 return self->slots[index].value;
439 }
440
441 index++;
442 index &= index_mask;
443 max_possible_offset++;
444 }
445 return default_value;
446}
447
461static inline VALUE_TYPE *JOIN(FHASHTABLE_NAME, search)(FHASHTABLE_TYPE *self, const KEY_TYPE key)
462{
463 return JOIN(FHASHTABLE_NAME, get_value_mut)(self, key);
464}
465
467static inline void JOIN(internal, JOIN(FHASHTABLE_NAME, swap_slots))(FHASHTABLE_SLOT_TYPE *a, FHASHTABLE_SLOT_TYPE *b)
468{
469 FHASHTABLE_SLOT_TYPE temp = *a;
470 *a = *b;
471 *b = temp;
472}
474
483static inline void JOIN(FHASHTABLE_NAME, insert)(FHASHTABLE_TYPE *self, KEY_TYPE key, VALUE_TYPE value)
484{
485 assert(self != NULL);
486 assert(FHASHTABLE_CONTAINS_KEY(self, key) == false);
487
488 const uint32_t index_mask = self->capacity - 1;
489 const uint32_t key_hash = HASH_FUNCTION(key);
490
491 uint32_t index = key_hash & index_mask;
492 FHASHTABLE_SLOT_TYPE current_slot = {.offset = 0, .key = key, .value = value};
493
494 while (true) {
495 const bool not_empty = self->slots[index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
496
497 if (!not_empty) {
498 break;
499 }
500
501 if (current_slot.offset > self->slots[index].offset) {
502 FHASHTABLE_SWAP_SLOTS(&self->slots[index], &current_slot);
503 }
504
505 index++;
506 index &= index_mask;
507 current_slot.offset++;
508 }
509 self->slots[index] = current_slot;
510 self->count++;
511}
512
521static inline void JOIN(FHASHTABLE_NAME, update)(FHASHTABLE_TYPE *self, KEY_TYPE key, VALUE_TYPE value)
522{
523 assert(self != NULL);
524
525 const uint32_t index_mask = self->capacity - 1;
526 const uint32_t key_hash = HASH_FUNCTION(key);
527
528 uint32_t index = key_hash & index_mask;
529 FHASHTABLE_SLOT_TYPE current_slot = {.offset = 0, .key = key, .value = value};
530
531 while (true) {
532 const bool not_empty = self->slots[index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
533
534 if (!not_empty) {
535 break;
536 }
537
538 const bool offset_is_same = current_slot.offset == self->slots[index].offset;
539
540 const bool key_is_equal = KEY_IS_EQUAL(current_slot.key, self->slots[index].key);
541
542 if (offset_is_same && key_is_equal) {
543 self->slots[index].value = current_slot.value;
544 return;
545 }
546
547 if (current_slot.offset > self->slots[index].offset) {
548 FHASHTABLE_SWAP_SLOTS(&current_slot, &self->slots[index]);
549 }
550
551 index++;
552 index &= index_mask;
553 current_slot.offset++;
554 }
555
556 self->slots[index] = current_slot;
557 self->count++;
558}
559
561static inline void JOIN(internal, JOIN(FHASHTABLE_NAME, backshift))(FHASHTABLE_TYPE *self, const uint32_t index_mask,
562 uint32_t index)
563{
564 assert(self);
565
566 uint32_t next_index = (index + 1) & index_mask;
567
568 while (true) {
569 const bool not_empty = self->slots[next_index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
570
571 const bool offset_is_non_zero = self->slots[next_index].offset > 0;
572
573 if (!(not_empty && offset_is_non_zero)) {
574 break;
575 }
576
577 self->slots[index] = self->slots[next_index];
578 self->slots[index].offset--;
579
580 self->slots[next_index].offset = FHASHTABLE_EMPTY_SLOT_OFFSET;
581
582 index = next_index;
583 next_index = (index + 1) & index_mask;
584 }
585}
587
597static inline bool JOIN(FHASHTABLE_NAME, delete)(FHASHTABLE_TYPE *self, const KEY_TYPE key)
598{
599 assert(self != NULL);
600
601 const uint32_t index_mask = self->capacity - 1;
602 const uint32_t key_hash = HASH_FUNCTION(key);
603
604 uint32_t index = key_hash & index_mask;
605 uint32_t max_possible_offset = 0;
606
607 while (true) {
608 const bool not_empty = self->slots[index].offset != FHASHTABLE_EMPTY_SLOT_OFFSET;
609
610 const bool below_max = max_possible_offset <= self->slots[index].offset;
611
612 if (!(not_empty && below_max)) {
613 break;
614 }
615
616 const bool key_is_equal = KEY_IS_EQUAL(key, self->slots[index].key);
617
618 if (!key_is_equal) {
619 index = (index + 1) & index_mask;
620 max_possible_offset++;
621 continue;
622 }
623
624 self->slots[index].offset = FHASHTABLE_EMPTY_SLOT_OFFSET;
625 self->count--;
626
627 FHASHTABLE_BACKSHIFT(self, index_mask, index);
628
629 return true;
630 }
631 return false;
632}
633
639static inline void JOIN(FHASHTABLE_NAME, clear)(FHASHTABLE_TYPE *self)
640{
641 assert(self != NULL);
642
643 for (uint32_t i = 0; i < self->capacity; i++) {
644 self->slots[i].offset = FHASHTABLE_EMPTY_SLOT_OFFSET;
645 }
646 self->count = 0;
647}
648
655static inline void JOIN(FHASHTABLE_NAME, copy)(FHASHTABLE_TYPE *restrict dest_ptr,
656 const FHASHTABLE_TYPE *restrict src_ptr)
657{
658 assert(src_ptr != NULL);
659 assert(dest_ptr != NULL);
660 assert(src_ptr->capacity <= dest_ptr->capacity);
661 assert(dest_ptr->count == 0);
662
663 for (uint32_t i = 0; i < src_ptr->capacity; i++) {
664 dest_ptr->slots[i] = src_ptr->slots[i];
665 }
666
667 dest_ptr->count = src_ptr->count;
668}
669
670// }}}
671
672// macro undefs: {{{
673
674#undef NAME
675#undef KEY_TYPE
676#undef VALUE_TYPE
677#undef KEY_IS_EQUAL
678#undef HASH_FUNCTION
679
680#undef FHASHTABLE_TYPE
681#undef FHASHTABLE_SLOT_TYPE
682#undef FHASHTABLE_INIT
683#undef FHASHTABLE_IS_FULL
684#undef FHASHTABLE_CONTAINS_KEY
685#undef FHASHTABLE_CALC_SIZEOF
686#undef FHASHTABLE_SWAP_SLOTS
687#undef FHASHTABLE_BACKSHIFT
688
689// }}}
690
691// vim: ft=c fdm=marker
#define HASH_FUNCTION(key)
Used to compute indicies of keys. This must be manually defined before including this header file.
Definition fhashtable.h:181
#define fhashtable_calc_sizeof_overflows(fhashtable_name, capacity)
Check for a given capacity, if the equivalent size of the hashtable struct overflows.
Definition fhashtable.h:103
#define KEY_IS_EQUAL(a, b)
Used to compare two keys This must be manually defined before including this header file.
Definition fhashtable.h:166
#define FHASHTABLE_EMPTY_SLOT_OFFSET
Offset constant used to flag empty slots.
Definition fhashtable.h:61
#define VALUE_TYPE
The value type. This must be manually defined before including this header file.
Definition fhashtable.h:144
#define KEY_TYPE
The key type. This must be manually defined before including this header file.
Definition fhashtable.h:132
#define fhashtable_calc_sizeof(fhashtable_name, capacity)
Calculate the size of the hashtable struct. No overflow checks.
Definition fhashtable.h:90
FNV-1a hashing function.
Check if a number is a power of two.
static size_t is_pow2(const size_t x)
Check if a number is a power of two.
Definition is_pow2.h:34
Murmur3 hash hashing function.
C preprocessor macros for pasting tokens together.
#define JOIN(a, b)
First expand tokens, then paste them together with a _ in between.
Definition paste.h:35
Round up to the next power of two.
static uint32_t round_up_pow2_32(uint32_t x)
Definition round_up_pow2.h:39
uint32_t offset
Offset from the ideal slot index.
Definition fhashtable.h:205
VALUE_TYPE value
The value in this slot.
Definition fhashtable.h:207
KEY_TYPE key
The key in this slot.
Definition fhashtable.h:206
uint32_t count
Number of non-empty slots.
Definition fhashtable.h:215
uint32_t capacity
Number of slots.
Definition fhashtable.h:216