|
|
/* -*- coding: utf-8 -*-
* ---------------------------------------------------------------------- * Copyright © 2011-2014, RedJack, LLC. * All rights reserved. * * Please see the COPYING file in this distribution for license details. * ---------------------------------------------------------------------- */
#include <stdlib.h>
#include <string.h>
#include "libcork/core/callbacks.h"
#include "libcork/core/hash.h"
#include "libcork/core/types.h"
#include "libcork/ds/dllist.h"
#include "libcork/ds/hash-table.h"
#include "libcork/helpers/errors.h"
#ifndef CORK_HASH_TABLE_DEBUG
#define CORK_HASH_TABLE_DEBUG 0
#endif
#if CORK_HASH_TABLE_DEBUG
#include <stdio.h>
#define DEBUG(...) \
do { \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ } while (0) #else
#define DEBUG(...) /* nothing */
#endif
/*-----------------------------------------------------------------------
* Hash tables */
struct cork_hash_table_entry_priv { struct cork_hash_table_entry public; struct cork_dllist_item in_bucket; struct cork_dllist_item insertion_order; };
struct cork_hash_table { struct cork_dllist *bins; struct cork_dllist insertion_order; size_t bin_count; size_t bin_mask; size_t entry_count; void *user_data; cork_free_f free_user_data; cork_hash_f hash; cork_equals_f equals; cork_free_f free_key; cork_free_f free_value; };
static cork_hash cork_hash_table__default_hash(void *user_data, const void *key) { return (cork_hash) (uintptr_t) key; }
static bool cork_hash_table__default_equals(void *user_data, const void *key1, const void *key2) { return key1 == key2; }
/* The default initial number of bins to allocate in a new table. */ #define CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE 8
/* The default number of entries per bin to allow before increasing the
* number of bins. */ #define CORK_HASH_TABLE_MAX_DENSITY 5
/* Return a power-of-2 bin count that's at least as big as the given requested
* size. */ static inline size_t cork_hash_table_new_size(size_t desired_count) { size_t v = desired_count; size_t r = 1; while (v >>= 1) { r <<= 1; } if (r != desired_count) { r <<= 1; } return r; }
#define bin_index(table, hash) ((hash) & (table)->bin_mask)
/* Allocates a new bins array in a hash table. We overwrite the old
* array, so make sure to stash it away somewhere safe first. */ static void cork_hash_table_allocate_bins(struct cork_hash_table *table, size_t desired_count) { size_t i;
table->bin_count = cork_hash_table_new_size(desired_count); table->bin_mask = table->bin_count - 1; DEBUG("Allocate %zu bins", table->bin_count); table->bins = cork_calloc(table->bin_count, sizeof(struct cork_dllist)); for (i = 0; i < table->bin_count; i++) { cork_dllist_init(&table->bins[i]); } }
static struct cork_hash_table_entry_priv * cork_hash_table_new_entry(struct cork_hash_table *table, cork_hash hash, void *key, void *value) { struct cork_hash_table_entry_priv *entry = cork_new(struct cork_hash_table_entry_priv); cork_dllist_add(&table->insertion_order, &entry->insertion_order); entry->public.hash = hash; entry->public.key = key; entry->public.value = value; return entry; }
static void cork_hash_table_free_entry(struct cork_hash_table *table, struct cork_hash_table_entry_priv *entry) { if (table->free_key != NULL) { table->free_key(entry->public.key); } if (table->free_value != NULL) { table->free_value(entry->public.value); } cork_dllist_remove(&entry->insertion_order); cork_delete(struct cork_hash_table_entry_priv, entry); }
struct cork_hash_table * cork_hash_table_new(size_t initial_size, unsigned int flags) { struct cork_hash_table *table = cork_new(struct cork_hash_table); table->entry_count = 0; table->user_data = NULL; table->free_user_data = NULL; table->hash = cork_hash_table__default_hash; table->equals = cork_hash_table__default_equals; table->free_key = NULL; table->free_value = NULL; cork_dllist_init(&table->insertion_order); if (initial_size < CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE) { initial_size = CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE; } cork_hash_table_allocate_bins(table, initial_size); return table; }
void cork_hash_table_clear(struct cork_hash_table *table) { size_t i; struct cork_dllist_item *curr; struct cork_dllist_item *next;
DEBUG("(clear) Remove all entries"); for (curr = cork_dllist_start(&table->insertion_order); !cork_dllist_is_end(&table->insertion_order, curr); curr = next) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, insertion_order); next = curr->next; cork_hash_table_free_entry(table, entry); } cork_dllist_init(&table->insertion_order);
DEBUG("(clear) Clear bins"); for (i = 0; i < table->bin_count; i++) { DEBUG(" Bin %zu", i); cork_dllist_init(&table->bins[i]); }
table->entry_count = 0; }
void cork_hash_table_free(struct cork_hash_table *table) { cork_hash_table_clear(table); cork_cfree(table->bins, table->bin_count, sizeof(struct cork_dllist)); cork_delete(struct cork_hash_table, table); }
size_t cork_hash_table_size(const struct cork_hash_table *table) { return table->entry_count; }
void cork_hash_table_set_user_data(struct cork_hash_table *table, void *user_data, cork_free_f free_user_data) { table->user_data = user_data; table->free_user_data = free_user_data; }
void cork_hash_table_set_hash(struct cork_hash_table *table, cork_hash_f hash) { table->hash = hash; }
void cork_hash_table_set_equals(struct cork_hash_table *table, cork_equals_f equals) { table->equals = equals; }
void cork_hash_table_set_free_key(struct cork_hash_table *table, cork_free_f free) { table->free_key = free; }
void cork_hash_table_set_free_value(struct cork_hash_table *table, cork_free_f free) { table->free_value = free; }
void cork_hash_table_ensure_size(struct cork_hash_table *table, size_t desired_count) { if (desired_count > table->bin_count) { struct cork_dllist *old_bins = table->bins; size_t old_bin_count = table->bin_count;
cork_hash_table_allocate_bins(table, desired_count);
if (old_bins != NULL) { size_t i; for (i = 0; i < old_bin_count; i++) { struct cork_dllist *bin = &old_bins[i]; struct cork_dllist_item *curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket); struct cork_dllist_item *next = curr->next;
size_t bin_index = bin_index(table, entry->public.hash); DEBUG(" Rehash %p from bin %zu to bin %zu", entry, i, bin_index); cork_dllist_add(&table->bins[bin_index], curr);
curr = next; } }
cork_cfree(old_bins, old_bin_count, sizeof(struct cork_dllist)); } } }
static void cork_hash_table_rehash(struct cork_hash_table *table) { DEBUG(" Reached maximum density; rehash"); cork_hash_table_ensure_size(table, table->bin_count + 1); }
struct cork_hash_table_entry * cork_hash_table_get_entry_hash(const struct cork_hash_table *table, cork_hash hash, const void *key) { size_t bin_index; struct cork_dllist *bin; struct cork_dllist_item *curr;
if (table->bin_count == 0) { DEBUG("(get) Empty table when searching for key %p " "(hash 0x%08" PRIx32 ")", key, hash); return NULL; }
bin_index = bin_index(table, hash); DEBUG("(get) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index);
bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket);
DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Match"); return &entry->public; }
curr = curr->next; }
DEBUG(" Entry not found"); return NULL; }
struct cork_hash_table_entry * cork_hash_table_get_entry(const struct cork_hash_table *table, const void *key) { cork_hash hash = table->hash(table->user_data, key); return cork_hash_table_get_entry_hash(table, hash, key); }
void * cork_hash_table_get_hash(const struct cork_hash_table *table, cork_hash hash, const void *key) { struct cork_hash_table_entry *entry = cork_hash_table_get_entry_hash(table, hash, key); if (entry == NULL) { return NULL; } else { DEBUG(" Extract value pointer %p", entry->value); return entry->value; } }
void * cork_hash_table_get(const struct cork_hash_table *table, const void *key) { struct cork_hash_table_entry *entry = cork_hash_table_get_entry(table, key); if (entry == NULL) { return NULL; } else { DEBUG(" Extract value pointer %p", entry->value); return entry->value; } }
struct cork_hash_table_entry * cork_hash_table_get_or_create_hash(struct cork_hash_table *table, cork_hash hash, void *key, bool *is_new) { struct cork_hash_table_entry_priv *entry; size_t bin_index;
if (table->bin_count > 0) { struct cork_dllist *bin; struct cork_dllist_item *curr;
bin_index = bin_index(table, hash); DEBUG("(get_or_create) Search for key %p " "(hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index);
bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket);
DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Match"); DEBUG(" Return value pointer %p", entry->public.value); *is_new = false; return &entry->public; }
curr = curr->next; }
/* create a new entry */ DEBUG(" Entry not found");
if ((table->entry_count / table->bin_count) > CORK_HASH_TABLE_MAX_DENSITY) { cork_hash_table_rehash(table); bin_index = bin_index(table, hash); } } else { DEBUG("(get_or_create) Search for key %p (hash 0x%08" PRIx32 ")", key, hash); DEBUG(" Empty table"); cork_hash_table_rehash(table); bin_index = bin_index(table, hash); }
DEBUG(" Allocate new entry"); entry = cork_hash_table_new_entry(table, hash, key, NULL); DEBUG(" Created new entry %p", entry);
DEBUG(" Add entry into bin %zu", bin_index); cork_dllist_add(&table->bins[bin_index], &entry->in_bucket);
table->entry_count++; *is_new = true; return &entry->public; }
struct cork_hash_table_entry * cork_hash_table_get_or_create(struct cork_hash_table *table, void *key, bool *is_new) { cork_hash hash = table->hash(table->user_data, key); return cork_hash_table_get_or_create_hash(table, hash, key, is_new); }
void cork_hash_table_put_hash(struct cork_hash_table *table, cork_hash hash, void *key, void *value, bool *is_new, void **old_key, void **old_value) { struct cork_hash_table_entry_priv *entry; size_t bin_index;
if (table->bin_count > 0) { struct cork_dllist *bin; struct cork_dllist_item *curr;
bin_index = bin_index(table, hash); DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index);
bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket);
DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Found existing entry; overwriting"); DEBUG(" Return old key %p", entry->public.key); if (old_key != NULL) { *old_key = entry->public.key; } DEBUG(" Return old value %p", entry->public.value); if (old_value != NULL) { *old_value = entry->public.value; } DEBUG(" Copy key %p into entry", key); entry->public.key = key; DEBUG(" Copy value %p into entry", value); entry->public.value = value; if (is_new != NULL) { *is_new = false; } return; }
curr = curr->next; }
/* create a new entry */ DEBUG(" Entry not found"); if ((table->entry_count / table->bin_count) > CORK_HASH_TABLE_MAX_DENSITY) { cork_hash_table_rehash(table); bin_index = bin_index(table, hash); } } else { DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ")", key, hash); DEBUG(" Empty table"); cork_hash_table_rehash(table); bin_index = bin_index(table, hash); }
DEBUG(" Allocate new entry"); entry = cork_hash_table_new_entry(table, hash, key, value); DEBUG(" Created new entry %p", entry);
DEBUG(" Add entry into bin %zu", bin_index); cork_dllist_add(&table->bins[bin_index], &entry->in_bucket);
table->entry_count++; if (old_key != NULL) { *old_key = NULL; } if (old_value != NULL) { *old_value = NULL; } if (is_new != NULL) { *is_new = true; } }
void cork_hash_table_put(struct cork_hash_table *table, void *key, void *value, bool *is_new, void **old_key, void **old_value) { cork_hash hash = table->hash(table->user_data, key); cork_hash_table_put_hash (table, hash, key, value, is_new, old_key, old_value); }
void cork_hash_table_delete_entry(struct cork_hash_table *table, struct cork_hash_table_entry *ventry) { struct cork_hash_table_entry_priv *entry = cork_container_of(ventry, struct cork_hash_table_entry_priv, public); cork_dllist_remove(&entry->in_bucket); table->entry_count--; cork_hash_table_free_entry(table, entry); }
bool cork_hash_table_delete_hash(struct cork_hash_table *table, cork_hash hash, const void *key, void **deleted_key, void **deleted_value) { size_t bin_index; struct cork_dllist *bin; struct cork_dllist_item *curr;
if (table->bin_count == 0) { DEBUG("(delete) Empty table when searching for key %p " "(hash 0x%08" PRIx32 ")", key, hash); return false; }
bin_index = bin_index(table, hash); DEBUG("(delete) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index);
bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket);
DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Match"); if (deleted_key != NULL) { *deleted_key = entry->public.key; } if (deleted_value != NULL) { *deleted_value = entry->public.value; }
DEBUG(" Remove entry from hash bin %zu", bin_index); cork_dllist_remove(curr); table->entry_count--;
DEBUG(" Free entry %p", entry); cork_hash_table_free_entry(table, entry); return true; }
curr = curr->next; }
DEBUG(" Entry not found"); return false; }
bool cork_hash_table_delete(struct cork_hash_table *table, const void *key, void **deleted_key, void **deleted_value) { cork_hash hash = table->hash(table->user_data, key); return cork_hash_table_delete_hash (table, hash, key, deleted_key, deleted_value); }
void cork_hash_table_map(struct cork_hash_table *table, void *user_data, cork_hash_table_map_f map) { struct cork_dllist_item *curr; DEBUG("Map across hash table");
curr = cork_dllist_start(&table->insertion_order); while (!cork_dllist_is_end(&table->insertion_order, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, insertion_order); struct cork_dllist_item *next = curr->next; enum cork_hash_table_map_result result;
DEBUG(" Apply function to entry %p", entry); result = map(user_data, &entry->public);
if (result == CORK_HASH_TABLE_MAP_ABORT) { return; } else if (result == CORK_HASH_TABLE_MAP_DELETE) { DEBUG(" Delete requested"); cork_dllist_remove(curr); cork_dllist_remove(&entry->in_bucket); table->entry_count--; cork_hash_table_free_entry(table, entry); }
curr = next; } }
void cork_hash_table_iterator_init(struct cork_hash_table *table, struct cork_hash_table_iterator *iterator) { DEBUG("Iterate through hash table"); iterator->table = table; iterator->priv = cork_dllist_start(&table->insertion_order); }
struct cork_hash_table_entry * cork_hash_table_iterator_next(struct cork_hash_table_iterator *iterator) { struct cork_hash_table *table = iterator->table; struct cork_dllist_item *curr = iterator->priv; struct cork_hash_table_entry_priv *entry;
if (cork_dllist_is_end(&table->insertion_order, curr)) { return NULL; }
entry = cork_container_of (curr, struct cork_hash_table_entry_priv, insertion_order); DEBUG(" Return entry %p", entry); iterator->priv = curr->next; return &entry->public; }
/*-----------------------------------------------------------------------
* Built-in key types */
static cork_hash string_hash(void *user_data, const void *vk) { const char *k = vk; size_t len = strlen(k); return cork_hash_buffer(0, k, len); }
static bool string_equals(void *user_data, const void *vk1, const void *vk2) { const char *k1 = vk1; const char *k2 = vk2; return strcmp(k1, k2) == 0; }
struct cork_hash_table * cork_string_hash_table_new(size_t initial_size, unsigned int flags) { struct cork_hash_table *table = cork_hash_table_new(initial_size, flags); cork_hash_table_set_hash(table, string_hash); cork_hash_table_set_equals(table, string_equals); return table; }
struct cork_hash_table * cork_pointer_hash_table_new(size_t initial_size, unsigned int flags) { return cork_hash_table_new(initial_size, flags); }
|