/src/hash_table.c
C | 257 lines | 165 code | 49 blank | 43 comment | 42 complexity | 0f2fbee38a7cdbac67e3034a84f076c5 MD5 | raw file
Possible License(s): GPL-3.0
- /*
- * This file incorporates code by James Routley, under the following
- * license.
- *
- * MIT License
- *
- * Copyright (c) 2017 James Routley
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
- #include <assert.h>
- #include <gc/gc.h>
- #include <string.h>
- #include <math.h>
- #include "hash_table.h"
- /* Definitions */
- typedef struct {
- char* key;
- void* value;
- } ht_item;
- struct _HashTable {
- int base_size;
- int size;
- int count;
- ht_item** items;
- };
- /* Constants */
- static const int HT_INITIAL_BASE_SIZE = 2;
- static const int HT_MIN_LOAD = 30;
- static const int HT_MAX_LOAD = 70;
- static ht_item HT_DELETED_ITEM = {NULL, NULL};
- /* Utilities */
- static int is_prime(const int x) {
- if (x < 2) { return -1; }
- if (x < 4) { return 1; }
- if ((x % 2) == 0) { return 0; }
- for (int i = 3; i <= floor(sqrt((double) x)); i += 2) {
- if ((x % i) == 0) {
- return 0;
- }
- }
- return 1;
- }
- static int next_prime(int x) {
- while (is_prime(x) != 1) {
- x++;
- }
- return x;
- }
- /* Static functions */
- static ht_item* ht_new_item(const char* k, void* v) {
- ht_item* i = GC_MALLOC(sizeof(*i));
- assert(i != NULL);
- size_t k_len = strlen(k);
- /* Copy the key string */
- i->key = GC_MALLOC(k_len + 1);
- assert(i->key != NULL);
- memcpy(i->key, k, k_len + 1);
- i->value = v;
- return i;
- }
- static unsigned int ht_hash(const char *str) {
- /* djb2 */
- unsigned int hash = 5381;
- int c;
- while ((c = *str++)) {
- hash = ((hash << 5) + hash) + c;
- }
- return hash;
- }
- static unsigned int ht_get_hash(const char* s, const int m,
- const int attempt) {
- const unsigned int hash_a = ht_hash(s);
- return (hash_a + attempt) % m;
- }
- static bool ht_under_load(HashTable ht) {
- const int load = ht->count * 100 / ht->size;
- return load < HT_MIN_LOAD;
- }
- static bool ht_over_load(HashTable ht) {
- const int load = (ht->count + 1) * 100 / ht->size;
- return load > HT_MAX_LOAD;
- }
- static HashTable ht_new_sized(const int base_size) {
- HashTable ht = GC_MALLOC(sizeof(*ht));
- assert(ht != NULL);
- ht->base_size = base_size;
- ht->size = next_prime(ht->base_size);
- ht->count = 0;
- ht->items = GC_MALLOC(((size_t)ht->size) * sizeof(ht_item*));
- assert(ht->items != NULL);
- return ht;
- }
- static void ht_resize(HashTable ht, const int base_size) {
- if (base_size < HT_INITIAL_BASE_SIZE) {
- /* Don't resize below the minimum */
- return;
- }
- /* Create a temporary hash table */
- HashTable tmp_ht = ht_new_sized(base_size);
- ht_item* item;
- /* Copy over everything from the old hash table into the temp one */
- for (int jj = 0; jj < ht->size; ++jj) {
- item = ht->items[jj];
- if (item != NULL && item != &HT_DELETED_ITEM) {
- hash_table_insert(tmp_ht, item->key, item->value);
- }
- }
- /* Update the old hash table with the temp one's properties */
- ht->base_size = tmp_ht->base_size;
- ht->size = tmp_ht->size;
- ht->count = tmp_ht->count;
- ht->items = tmp_ht->items;
- }
- static void ht_resize_up(HashTable ht) {
- const int new_size = ht->base_size * 2;
- ht_resize(ht, new_size);
- }
- static void ht_resize_down(HashTable ht) {
- const int new_size = ht->base_size / 2;
- assert(new_size >= ht->count);
- ht_resize(ht, new_size);
- }
- /* Interface functions */
- HashTable make_hash_table() {
- return ht_new_sized(HT_INITIAL_BASE_SIZE);
- }
- void ht_display(HashTable);
- void hash_table_insert(HashTable ht, const char* key, void* value) {
- if (ht_over_load(ht)) {
- ht_resize_up(ht);
- }
- ht_item* item = ht_new_item(key, value);
- unsigned int index = ht_get_hash(item->key, ht->size, 0);
- ht_item* cur_item = ht->items[index];
- int attempt = 1;
- while (cur_item != NULL) {
- if (cur_item == &HT_DELETED_ITEM) {
- /* We can overwrite this "bucket" */
- ht->items[index] = item;
- ht->count++;
- return;
- } else if (strcmp(cur_item->key, key) == 0) {
- /* Existing entry with same key, so we overwrite again but
- * count stays the same */
- ht->items[index] = item;
- return;
- } else {
- index = ht_get_hash(item->key, ht->size, attempt);
- cur_item = ht->items[index];
- attempt++;
- }
- }
- /* New entry */
- ht->items[index] = item;
- ht->count++;
- }
- void* hash_table_search(HashTable ht, const char* key) {
- unsigned int index = ht_get_hash(key, ht->size, 0);
- ht_item* item = ht->items[index];
- int i = 1;
- while (item != NULL) {
- if (item != &HT_DELETED_ITEM && strcmp(item->key, key) == 0) {
- return item->value;
- }
- index = ht_get_hash(key, ht->size, i);
- item = ht->items[index];
- i++;
- }
- return NULL;
- }
- bool hash_table_delete(HashTable ht, const char* key) {
- if (ht_under_load(ht)) {
- ht_resize_down(ht);
- }
- unsigned int index = ht_get_hash(key, ht->size, 0);
- ht_item* item = ht->items[index];
- int i = 1;
- while (item != NULL) {
- if (item != &HT_DELETED_ITEM && strcmp(item->key, key) == 0) {
- ht->items[index] = &HT_DELETED_ITEM;
- ht->count--;
- return true;
- }
- index = ht_get_hash(key, ht->size, i);
- item = ht->items[index];
- i++;
- }
- return false;
- }