Skip to content

Commit

Permalink
Allow a custom comparer to be used.
Browse files Browse the repository at this point in the history
  • Loading branch information
sheredom committed Mar 21, 2023
1 parent a3ed0ef commit f3cfd9a
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 33 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ The current supported platforms are Linux, macOS and Windows.
### Fundamental Design

The hashmap is made to work with any arbitrary data keys - you just provide a
pointer and size, and it'll hash that data. Comparison is done using `memcmp`,
so zeroing out padding data is advised in structs.
pointer and size, and it'll hash that data. The default hasher is a crc32
variant using hardware intrinsics if possible, and the default comparer just
uses `memcmp`, so zeroing out any padding in struct keys is advisable.

### Create a Hashmap

Expand Down Expand Up @@ -46,6 +47,9 @@ memset(&options, 0, sizeof(options));
// You can set a custom hasher that the hashmap should use.
options.hasher = &my_hasher;

// You can set a custom comparer that the hashmap should for comparing keys.
options.comparer = &my_comparer;

// You can also specify the initial capacity of the hashmap.
options.initial_capacity = 42;

Expand Down
76 changes: 46 additions & 30 deletions hashmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,25 @@ typedef struct hashmap_element_s {
void *data;
} hashmap_element_t;

typedef hashmap_uint32_t (*hashmap_hasher_t)(hashmap_uint32_t seed,
const void *key,
hashmap_uint32_t key_len);
typedef int (*hashmap_comparer_t)(const void *a, hashmap_uint32_t a_len,
const void *b, hashmap_uint32_t b_len);

typedef struct hashmap_s {
hashmap_uint32_t log2_capacity;
hashmap_uint32_t size;
hashmap_uint32_t (*hasher)(hashmap_uint32_t seed, const void *key,
hashmap_uint32_t key_len);
hashmap_hasher_t hasher;
hashmap_comparer_t comparer;
struct hashmap_element_s *data;
} hashmap_t;

#define HASHMAP_LINEAR_PROBE_LENGTH (8)

typedef struct hashmap_create_options_s {
hashmap_uint32_t (*hasher)(hashmap_uint32_t seed, const void *key,
hashmap_uint32_t key_len);
hashmap_hasher_t hasher;
hashmap_comparer_t comparer;
hashmap_uint32_t initial_capacity;
hashmap_uint32_t _;
} hashmap_create_options_t;
Expand Down Expand Up @@ -243,16 +249,17 @@ hashmap_capacity(const struct hashmap_s *const hashmap);
/// @param hashmap The hashmap to destroy.
HASHMAP_WEAK void hashmap_destroy(struct hashmap_s *const hashmap);

HASHMAP_WEAK hashmap_uint32_t hashmap_crc32_hasher(const hashmap_uint32_t seed,
const void *const s,
const hashmap_uint32_t len);
static hashmap_uint32_t hashmap_crc32_hasher(const hashmap_uint32_t seed,
const void *const s,
const hashmap_uint32_t len);
static int hashmap_memcmp_comparer(const void *const a,
const hashmap_uint32_t a_len,
const void *const b,
const hashmap_uint32_t b_len);
HASHMAP_ALWAYS_INLINE hashmap_uint32_t hashmap_hash_helper_int_helper(
const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len);
HASHMAP_ALWAYS_INLINE int
hashmap_match_helper(const struct hashmap_element_s *const element,
const void *const key, const hashmap_uint32_t len);
HASHMAP_ALWAYS_INLINE int
hashmap_hash_helper(const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len,
hashmap_uint32_t *const out_index);
Expand Down Expand Up @@ -297,6 +304,10 @@ int hashmap_create_ex(struct hashmap_create_options_s options,
options.hasher = &hashmap_crc32_hasher;
}

if (HASHMAP_NULL == options.comparer) {
options.comparer = &hashmap_memcmp_comparer;
}

out_hashmap->data = HASHMAP_CAST(
struct hashmap_element_s *,
calloc(options.initial_capacity + HASHMAP_LINEAR_PROBE_LENGTH,
Expand All @@ -305,6 +316,7 @@ int hashmap_create_ex(struct hashmap_create_options_s options,
out_hashmap->log2_capacity = 31 - hashmap_clz(options.initial_capacity);
out_hashmap->size = 0;
out_hashmap->hasher = options.hasher;
out_hashmap->comparer = options.comparer;

return 0;
}
Expand Down Expand Up @@ -351,9 +363,11 @@ void *hashmap_get(const struct hashmap_s *const m, const void *const key,

/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
if (m->data[curr + i].in_use) {
if (hashmap_match_helper(&m->data[curr + i], key, len)) {
return m->data[curr + i].data;
const hashmap_uint32_t index = curr + i;

if (m->data[index].in_use) {
if (m->comparer(m->data[index].key, m->data[index].key_len, key, len)) {
return m->data[index].data;
}
}
}
Expand All @@ -374,10 +388,12 @@ int hashmap_remove(struct hashmap_s *const m, const void *const key,

/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
if (m->data[curr + i].in_use) {
if (hashmap_match_helper(&m->data[curr + i], key, len)) {
const hashmap_uint32_t index = curr + i;

if (m->data[index].in_use) {
if (m->comparer(m->data[index].key, m->data[index].key_len, key, len)) {
/* Blank out the fields including in_use */
memset(&m->data[curr + i], 0, sizeof(struct hashmap_element_s));
memset(&m->data[index], 0, sizeof(struct hashmap_element_s));

/* Reduce the size */
m->size--;
Expand All @@ -403,14 +419,14 @@ const void *hashmap_remove_and_return_key(struct hashmap_s *const m,

/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
if (m->data[curr + i].in_use) {
if (hashmap_match_helper(&m->data[curr + i], key, len)) {
const void *const stored_key = m->data[curr + i].key;
const hashmap_uint32_t index = curr + i;

if (m->data[index].in_use) {
if (m->comparer(m->data[index].key, m->data[index].key_len, key, len)) {
const void *const stored_key = m->data[index].key;

/* Blank out the fields */
m->data[curr + i].in_use = 0;
m->data[curr + i].data = HASHMAP_NULL;
m->data[curr + i].key = HASHMAP_NULL;
memset(&m->data[index], 0, sizeof(struct hashmap_element_s));

/* Reduce the size */
m->size--;
Expand Down Expand Up @@ -581,18 +597,17 @@ hashmap_uint32_t hashmap_crc32_hasher(const hashmap_uint32_t seed,
return crc32val;
}

int hashmap_memcmp_comparer(const void *const a, const hashmap_uint32_t a_len,
const void *const b, const hashmap_uint32_t b_len) {
return (a_len == b_len) && (0 == memcmp(a, b, a_len));
}

HASHMAP_ALWAYS_INLINE hashmap_uint32_t
hashmap_hash_helper_int_helper(const struct hashmap_s *const m,
const void *const k, const hashmap_uint32_t l) {
return (m->hasher(~0u, k, l) * 2654435769u) >> (32u - m->log2_capacity);
}

HASHMAP_ALWAYS_INLINE int
hashmap_match_helper(const struct hashmap_element_s *const element,
const void *const key, const hashmap_uint32_t len) {
return (element->key_len == len) && (0 == memcmp(element->key, key, len));
}

HASHMAP_ALWAYS_INLINE int
hashmap_hash_helper(const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len,
Expand All @@ -615,8 +630,9 @@ hashmap_hash_helper(const struct hashmap_s *const m, const void *const key,

if (!m->data[index].in_use) {
first_free = (first_free < index) ? first_free : index;
} else if (hashmap_match_helper(&m->data[index], key, len)) {
*out_index = curr + i;
} else if (m->comparer(m->data[index].key, m->data[index].key_len, key,
len)) {
*out_index = index;
return 1;
}
}
Expand Down
19 changes: 18 additions & 1 deletion test/test.inc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#define NOEXCEPT
#endif

#include <string.h>

MY_TEST_WRAPPER(create) {
struct hashmap_s hashmap;
ASSERT_EQ(0, hashmap_create(1, &hashmap));
Expand Down Expand Up @@ -44,18 +46,33 @@ static hashmap_uint32_t custom_hasher(const hashmap_uint32_t seed,
HASHMAP_CAST(hashmap_uint32_t, cs) ^ len;
}

static int custom_comparer(const void *const a, const hashmap_uint32_t a_len,
const void *const b, const hashmap_uint32_t b_len) {
return (a_len == b_len) &&
0 == strncmp(HASHMAP_PTR_CAST(const char *, a),
HASHMAP_PTR_CAST(const char *, b), a_len);
}

MY_TEST_WRAPPER(create_ex) {
int thing = 42;
const char *const key = "foo";
struct hashmap_s hashmap;
struct hashmap_create_options_s options;
memset(&options, 0, sizeof(options));
options.initial_capacity = 42;
options.hasher = &custom_hasher;
options.comparer = &custom_comparer;

ASSERT_EQ(0, hashmap_create_ex(options, &hashmap));
ASSERT_LE(42u, hashmap_capacity(&hashmap));

ASSERT_EQ(0, hashmap_put(&hashmap, &thing, sizeof(thing), HASHMAP_NULL));
ASSERT_EQ(0, hashmap_put(&hashmap, key, HASHMAP_CAST(unsigned, strlen(key)),
&thing));

ASSERT_EQ(&thing,
HASHMAP_PTR_CAST(const int *,
hashmap_get(&hashmap, key,
HASHMAP_CAST(unsigned, strlen(key)))));

ASSERT_EQ(1u, hashmap_num_entries(&hashmap));

Expand Down

0 comments on commit f3cfd9a

Please sign in to comment.