346 lines
9.8 KiB
C
346 lines
9.8 KiB
C
#ifndef OA_HASH_H
|
|
#define OA_HASH_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif /* __cplusplus */
|
|
|
|
#ifdef OA_HASH_STATIC
|
|
#define OA_HASH_API static
|
|
#else
|
|
#define OA_HASH_API extern
|
|
#endif /* OA_HASH_STATIC */
|
|
|
|
#include <stddef.h>
|
|
|
|
/** @brief Hash table entry state */
|
|
enum oa_hash_entry_state {
|
|
OA_HASH_ENTRY_EMPTY = 0, /**< empty entry */
|
|
OA_HASH_ENTRY_OCCUPIED, /**< occupied entry */
|
|
OA_HASH_ENTRY_DELETED /**< deleted entry */
|
|
};
|
|
|
|
/** @brief Entry holding key-value pair in hash table */
|
|
struct oa_hash_entry {
|
|
enum oa_hash_entry_state state; /**< entry state */
|
|
struct {
|
|
const char *buf; /**< key buffer */
|
|
size_t length; /**< key length */
|
|
} key;
|
|
void *value; /**< value pointer */
|
|
};
|
|
|
|
#define __OA_HASH_ATTRS_const \
|
|
const size_t length; /**< amount of entries */ \
|
|
const size_t capacity; /**< total buckets capacity */ \
|
|
const struct oa_hash_entry *buckets /**< entries array */
|
|
#define __OA_HASH_ATTRS_mut \
|
|
size_t length; /**< amount of entries */ \
|
|
size_t capacity; /**< total buckets capacity */ \
|
|
struct oa_hash_entry *buckets /**< entries array */
|
|
/** @brief can be used to cast to struct oa_hash */
|
|
#define OA_HASH_ATTRS(_qualifier) __OA_HASH_ATTRS_##_qualifier
|
|
|
|
/** @brief Open addressing hash table */
|
|
struct oa_hash {
|
|
OA_HASH_ATTRS(mut);
|
|
};
|
|
|
|
/**
|
|
* @brief Initialize hash table with given buckets array
|
|
*
|
|
* @param[out] ht the hash table to be initialized
|
|
* @param[out] buckets pre-allocated array of entries
|
|
* @param[in] capacity amount of buckets
|
|
*/
|
|
OA_HASH_API void oa_hash_init(struct oa_hash *ht,
|
|
struct oa_hash_entry buckets[],
|
|
const size_t capacity);
|
|
|
|
/**
|
|
* @brief Clean up hash table entries and struct
|
|
*
|
|
* @param[out] ht the hash table to be cleaned
|
|
*/
|
|
OA_HASH_API void oa_hash_cleanup(struct oa_hash *ht);
|
|
|
|
/**
|
|
* @brief Retrieve entry by key
|
|
*
|
|
* @param[in] ht the hash table
|
|
* @param[in] key the key to search for
|
|
* @param[in] len the key length
|
|
* @return entry if found, NULL otherwise
|
|
*/
|
|
OA_HASH_API const struct oa_hash_entry *oa_hash_get_entry(
|
|
const struct oa_hash *ht, const char key[], const size_t len);
|
|
|
|
/**
|
|
* @brief Retrieve value by key (wrapper around oa_hash_get_entry)
|
|
*
|
|
* @param[in] ht the hash table
|
|
* @param[in] key the key to search for
|
|
* @param[in] len the key length
|
|
* @return value if found, NULL otherwise
|
|
*/
|
|
OA_HASH_API void *oa_hash_get(const struct oa_hash *ht,
|
|
const char key[],
|
|
const size_t len);
|
|
|
|
/**
|
|
* @brief Insert or update entry
|
|
*
|
|
* @param[in,out] ht the hash table
|
|
* @param[in] key the key to insert/update
|
|
* @param[in] len the key length
|
|
* @param[in] value the value to be assigned
|
|
* @return entry if successful, or NULL if no space left, in which case
|
|
* oa_hash_rehash() should be called
|
|
*/
|
|
OA_HASH_API const struct oa_hash_entry *oa_hash_set_entry(struct oa_hash *ht,
|
|
const char key[],
|
|
const size_t len,
|
|
void *value);
|
|
|
|
/**
|
|
* @brief Insert or update entry (wrapper around oa_hash_set_entry)
|
|
*
|
|
* @param[in,out] ht the hash table
|
|
* @param[in] key the key to insert/update
|
|
* @param[in] len the key length
|
|
* @param[in] value the value to be assigned
|
|
* @return value if successful, or NULL if no space left, in which case
|
|
* oa_hash_rehash() should be called
|
|
*/
|
|
OA_HASH_API void *oa_hash_set(struct oa_hash *ht,
|
|
const char key[],
|
|
const size_t len,
|
|
void *value);
|
|
|
|
/**
|
|
* @brief Remove entry by key
|
|
*
|
|
* @param[in,out] ht the hash table
|
|
* @param[in] key the key to be removed
|
|
* @param[in] len the key length
|
|
* @return 1 if found and removed, 0 otherwise
|
|
*/
|
|
OA_HASH_API int oa_hash_remove(struct oa_hash *ht,
|
|
const char key[],
|
|
const size_t len);
|
|
|
|
/**
|
|
* @brief Rehash table to new buckets array
|
|
*
|
|
* @param[in,out] ht the hash table
|
|
* @param[in,out] new_buckets the new buckets array
|
|
* @param[in] new_capacity the new buckets capacity
|
|
* @return pointer to old (now unused) bucket if successful, or NULL otherwise
|
|
*/
|
|
OA_HASH_API struct oa_hash_entry *oa_hash_rehash(
|
|
struct oa_hash *ht,
|
|
struct oa_hash_entry new_buckets[],
|
|
const size_t new_capacity);
|
|
|
|
#ifndef OA_HASH_HEADER
|
|
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
|
|
OA_HASH_API void
|
|
oa_hash_init(struct oa_hash *ht,
|
|
struct oa_hash_entry buckets[],
|
|
const size_t capacity)
|
|
{
|
|
memset(buckets, 0, sizeof(struct oa_hash_entry) * capacity);
|
|
ht->buckets = buckets;
|
|
ht->length = 0;
|
|
ht->capacity = capacity;
|
|
}
|
|
|
|
OA_HASH_API void
|
|
oa_hash_cleanup(struct oa_hash *ht)
|
|
{
|
|
if (!ht) return;
|
|
|
|
ht->length = 0;
|
|
ht->capacity = 0;
|
|
ht->buckets = NULL;
|
|
}
|
|
|
|
static size_t
|
|
_oa_hash_genhash(const char key[], size_t len, const size_t capacity)
|
|
{
|
|
const unsigned char *str = (const unsigned char *)key;
|
|
unsigned long hash = 5381; /* DJB2 initial value */
|
|
|
|
if (!key || !capacity) return 0;
|
|
|
|
while (len--) {
|
|
hash = ((hash & 0x7fffffff) << 5) + hash + *str++;
|
|
}
|
|
return hash % capacity;
|
|
}
|
|
|
|
OA_HASH_API const struct oa_hash_entry *
|
|
oa_hash_get_entry(const struct oa_hash *ht, const char key[], const size_t len)
|
|
{
|
|
const size_t start_slot = _oa_hash_genhash(key, len, ht->capacity);
|
|
size_t slot = start_slot;
|
|
|
|
if (!len || !ht->capacity) return NULL;
|
|
|
|
do {
|
|
struct oa_hash_entry *entry = &ht->buckets[slot];
|
|
|
|
if (entry->state == OA_HASH_ENTRY_EMPTY) {
|
|
return NULL;
|
|
}
|
|
|
|
if (entry->state == OA_HASH_ENTRY_OCCUPIED && len == entry->key.length
|
|
&& 0 == memcmp(entry->key.buf, key, len))
|
|
{
|
|
return entry;
|
|
}
|
|
|
|
slot = (slot + 1) % ht->capacity;
|
|
} while (slot != start_slot);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
OA_HASH_API void *
|
|
oa_hash_get(const struct oa_hash *ht, const char key[], const size_t len)
|
|
{
|
|
const struct oa_hash_entry *entry = oa_hash_get_entry(ht, key, len);
|
|
return entry ? entry->value : NULL;
|
|
}
|
|
|
|
OA_HASH_API const struct oa_hash_entry *
|
|
oa_hash_set_entry(struct oa_hash *ht,
|
|
const char key[],
|
|
const size_t len,
|
|
void *value)
|
|
{
|
|
const size_t start_slot = _oa_hash_genhash(key, len, ht->capacity);
|
|
size_t slot = start_slot;
|
|
size_t first_deleted = SIZE_MAX;
|
|
|
|
if (!len || !ht->capacity) return NULL;
|
|
|
|
do {
|
|
struct oa_hash_entry *entry = &ht->buckets[slot];
|
|
|
|
if (entry->state != OA_HASH_ENTRY_OCCUPIED) {
|
|
if (first_deleted == SIZE_MAX
|
|
&& entry->state == OA_HASH_ENTRY_DELETED)
|
|
{
|
|
first_deleted = slot;
|
|
}
|
|
if (entry->state == OA_HASH_ENTRY_EMPTY) {
|
|
slot = (first_deleted != SIZE_MAX) ? first_deleted : slot;
|
|
entry = &ht->buckets[slot];
|
|
entry->key.buf = (char *)key;
|
|
entry->key.length = len;
|
|
entry->value = value;
|
|
entry->state = OA_HASH_ENTRY_OCCUPIED;
|
|
ht->length++;
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
if (entry->state == OA_HASH_ENTRY_OCCUPIED && len == entry->key.length
|
|
&& 0 == memcmp(entry->key.buf, key, len))
|
|
{
|
|
entry->value = value;
|
|
return entry;
|
|
}
|
|
|
|
slot = (slot + 1) % ht->capacity;
|
|
} while (slot != start_slot);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
OA_HASH_API void *
|
|
oa_hash_set(struct oa_hash *ht,
|
|
const char key[],
|
|
const size_t len,
|
|
void *value)
|
|
{
|
|
const struct oa_hash_entry *entry = oa_hash_set_entry(ht, key, len, value);
|
|
return entry ? entry->value : NULL;
|
|
}
|
|
|
|
OA_HASH_API int
|
|
oa_hash_remove(struct oa_hash *ht, const char key[], const size_t len)
|
|
{
|
|
const size_t start_slot = _oa_hash_genhash(key, len, ht->capacity);
|
|
size_t slot = start_slot;
|
|
|
|
if (!len || !ht->capacity) return 0;
|
|
|
|
do {
|
|
struct oa_hash_entry *entry = &ht->buckets[slot];
|
|
|
|
if (entry->state == OA_HASH_ENTRY_EMPTY) {
|
|
return 0;
|
|
}
|
|
|
|
if (entry->state == OA_HASH_ENTRY_OCCUPIED && len == entry->key.length
|
|
&& 0 == memcmp(entry->key.buf, key, len))
|
|
{
|
|
entry->state = OA_HASH_ENTRY_DELETED;
|
|
ht->length--;
|
|
return 1;
|
|
}
|
|
|
|
slot = (slot + 1) % ht->capacity;
|
|
} while (slot != start_slot);
|
|
|
|
return 0;
|
|
}
|
|
|
|
OA_HASH_API struct oa_hash_entry *
|
|
oa_hash_rehash(struct oa_hash *ht,
|
|
struct oa_hash_entry new_buckets[],
|
|
const size_t new_capacity)
|
|
{
|
|
struct oa_hash_entry *old_buckets = ht->buckets;
|
|
const size_t old_capacity = ht->capacity;
|
|
const size_t old_length = ht->length;
|
|
size_t i;
|
|
|
|
if (!new_buckets || new_capacity <= old_capacity) return 0;
|
|
|
|
memset(new_buckets, 0, sizeof(struct oa_hash_entry) * new_capacity);
|
|
|
|
/* temporarily switch to new buckets */
|
|
ht->buckets = new_buckets;
|
|
ht->capacity = new_capacity;
|
|
ht->length = 0;
|
|
|
|
for (i = 0; i < old_capacity; ++i) {
|
|
if (old_buckets[i].state == OA_HASH_ENTRY_OCCUPIED
|
|
&& !oa_hash_set_entry(ht, old_buckets[i].key.buf,
|
|
old_buckets[i].key.length,
|
|
old_buckets[i].value))
|
|
{
|
|
/* restore original state on failure */
|
|
ht->buckets = old_buckets;
|
|
ht->capacity = old_capacity;
|
|
ht->length = old_length;
|
|
return NULL;
|
|
}
|
|
}
|
|
return old_buckets;
|
|
}
|
|
|
|
#endif /* OA_HASH_HEADER */
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif /* __cplusplus */
|
|
|
|
#endif /* OA_HASH_H */
|