/* hashmap - a rehashing hash map * Copyright (c) 2003 Michael B. Allen * * The MIT License * * 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 #include #include #include #include #include "mba/msgno.h" #include "mba/iterator.h" #include "mba/allocator.h" #include "mba/suba.h" #include "mba/hashmap.h" #define HAL(h) ((struct allocator *)((h)->al ? (char *)(h) - (ptrdiff_t)(h)->al : NULL)) #define TABLE_SIZES_SIZE 20 #define DELETED 1 const int table_sizes[] = { 0, 17, 31, 61, 131, 257, 509, 1021, 2053, 4099, 8191, 16381, 32771, 65537, 131071, 262147, 524287, 1048573, 2097143, 4194301, 8388617 }; struct entry { unsigned long hash; ref_t key; ref_t data; }; unsigned long hash_str(const void *str, void *context) { unsigned long h = 5381; const unsigned char *s = (const unsigned char *)str; int c; if (context) { /* then str is ref and context is suba */ s = (const unsigned char *)context + (size_t)str; } while ((c = *s++)) { h = ((h << 5) + h) + c; } return h; } unsigned long hash_wcs(const void *wcs, void *context) { unsigned long h = 5381; const wchar_t *s = (const wchar_t *)wcs; wint_t c; if (context) { /* then wcs is ref and context is suba */ s = (const wchar_t *)context + (size_t)wcs; } while ((c = *s++)) { h = ((h << 5) + h) + c; } return h; } static unsigned long hash_ptr(const void *ptr, void *context) { unsigned long h = (unsigned long)ptr; if (context) { h = (unsigned long)((char *)context + (size_t)ptr); } return h; } int cmp_str(const void *object1, const void *object2, void *context) { const unsigned char *s1 = object1; const unsigned char *s2 = object2; const unsigned char *slim; if (context) { s1 = (const unsigned char *)context + (size_t)object1; s2 = (const unsigned char *)context + (size_t)object2; slim = (const unsigned char *)context + ((struct allocator *)context)->size; } else { slim = (const unsigned char *)-1; } while (s1 < slim && s2 < slim) { if (*s1 != *s2) { return *s1 < *s2 ? -1 : 1; } else if (*s1 == '\0') { return 0; } s1++; s2++; } return s2 < slim ? -1 : 1; } int cmp_wcs(const void *object1, const void *object2, void *context) { const wchar_t *s1 = object1; const wchar_t *s2 = object2; const wchar_t *slim; if (context) { s1 = (const wchar_t *)((char *)context + (size_t)object1); s2 = (const wchar_t *)((char *)context + (size_t)object2); slim = (const wchar_t *)((char *)context + ((struct allocator *)context)->size); } else { slim = (const wchar_t *)-1; } while (s1 < slim && s2 < slim) { if (*s1 != *s2) { return *s1 < *s2 ? -1 : 1; } else if (*s1 == '\0') { return 0; } s1++; s2++; } return s2 < slim ? -1 : 1; } int hashmap_init(struct hashmap *h, unsigned int load_factor, hash_fn hash, cmp_fn cmp, void *context, struct allocator *al) { memset(h, 0, sizeof *h); h->table_size_index = 0; h->hash = ALREF(al, hash); h->cmp = ALREF(al, cmp); h->context = ALREF(al, context); h->size = 0; if (load_factor == 0 || load_factor > 100) { h->load_factor_high = 75; h->load_factor_low = 18; } else { h->load_factor_high = load_factor; h->load_factor_low = load_factor / 4; } if (al && al->tail) { /* al is a suba allocator */ h->al = (char *)h - (char *)al; } h->table = 0; return 0; } int hashmap_deinit(struct hashmap *h, del_fn key_del, del_fn data_del, void *context) { int ret = 0; if (h) { struct allocator *al = HAL(h); ret += hashmap_clear(h, key_del, data_del, context); ret += allocator_free(al, ALADR(al, h->table)); } if (ret) { AMSG(""); return -1; } return 0; } struct hashmap * hashmap_new(hash_fn hash, cmp_fn cmp, void *context, struct allocator *al) { struct hashmap *h; if ((h = allocator_alloc(al, sizeof *h, 0)) == NULL || hashmap_init(h, 75, hash, cmp, context, al) == -1) { AMSG(""); allocator_free(al, h); return NULL; } return h; } int hashmap_del(struct hashmap *h, del_fn key_del, del_fn data_del, void *context) { int ret = 0; if (h) { ret += hashmap_deinit(h, key_del, data_del, context); ret += allocator_free(HAL(h), h); } if (ret) { AMSG(""); return -1; } return 0; } int hashmap_clear(struct hashmap *h, del_fn key_del, del_fn data_del, void *context) { int ret = 0; if (h->table) { int idx, table_size; struct allocator *al = HAL(h); struct entry *table = ALADR(al, h->table); table_size = table_sizes[h->table_size_index]; for (idx = 0; idx < table_size; idx++) { struct entry *e = table + idx; if (e->key > DELETED) { void *k = ALADR(al, e->key); if (key_del) ret += key_del(context, k); if (data_del) ret += data_del(context, ALADR(al, e->data)); } } ret += allocator_free(al, table); h->table_size_index = 0; h->size = 0; h->table = 0; } if (ret) { AMSG(""); return -1; } return 0; } int hashmap_clean(struct hashmap *h) { (void)h; /* TODO */ return 0; } static int hashmap_resize(struct hashmap *h, int new_table_size_index) { struct entry *old_table, *oe; int old_table_size, table_size, i; struct allocator *al = HAL(h); if ((oe = allocator_alloc(al, table_sizes[new_table_size_index] * sizeof *oe, 1)) == NULL) { AMSG(""); return -1; } old_table_size = table_sizes[h->table_size_index]; old_table = ALADR(al, h->table); h->table_size_index = new_table_size_index; h->table = ALREF(al, oe); if (old_table == NULL) { return 0; } table_size = table_sizes[h->table_size_index]; for (i = 0; i < old_table_size; i++) { oe = old_table + i; if (oe->key > DELETED) { int idx, rehash_adv; struct entry *e; idx = oe->hash % table_size; rehash_adv = 1 + oe->hash % (table_size - 2); for ( ;; ) { e = (struct entry *)ALADR(al, h->table) + idx; if (e->key == 0) { *e = *oe; break; } idx = (idx + rehash_adv) % table_size; } } } if (allocator_free(al, old_table) == -1) { AMSG(""); return -1; } return 0; } int hashmap_put(struct hashmap *h, void *key, void *data) { unsigned long hash; int idx, rehash_adv, count, table_size; struct entry *e; struct allocator *al = HAL(h); if (h->table_size_index == 0 || ((h->size * 100 / table_sizes[h->table_size_index]) >= h->load_factor_high && h->table_size_index < TABLE_SIZES_SIZE)) { if (hashmap_resize(h, h->table_size_index + 1) == -1) { AMSG(""); return -1; } } hash = h->hash ? ((hash_fn)ALADR(al, h->hash))(key, ALADR(al, h->context)) : hash_ptr(key, ALADR(al, h->context)); table_size = table_sizes[h->table_size_index]; idx = hash % table_size; rehash_adv = 1 + hash % (table_size - 2); count = table_size; do { e = (struct entry *)ALADR(al, h->table) + idx; if (e->key <= DELETED) { break; } else { void *k = ALADR(al, e->key); if (hash == e->hash && (h->cmp ? ((cmp_fn)ALADR(al, h->cmp))(key, k, ALADR(al, h->context)) == 0 : key == k)) { errno = EEXIST; PMNO(errno); return -1; } } idx = (idx + rehash_adv) % table_size; } while(--count); if (!count) { errno = ENOSPC; PMNO(errno); return -1; } e->hash = hash; e->key = ALREF(al, key); e->data = ALREF(al, data); h->size++; return 0; } void * hashmap_get(const struct hashmap *h, const void *key) { if (h->table) { unsigned long hash; int idx, rehash_adv, count, table_size; struct entry *e; struct allocator *al = HAL(h); hash = h->hash ? ((hash_fn)ALADR(al, h->hash))(key, ALADR(al, h->context)) : hash_ptr(key, ALADR(al, h->context)); table_size = table_sizes[h->table_size_index]; idx = hash % table_size; rehash_adv = 1 + hash % (table_size - 2); count = table_size; do { e = (struct entry *)ALADR(al, h->table) + idx; if (e->key == 0) { break; } else if (e->key != DELETED) { void *k = ALADR(al, e->key); if (hash == e->hash && (h->cmp ? ((cmp_fn)ALADR(al, h->cmp))(key, k, ALADR(al, h->context)) == 0 : key == k)) { return ALADR(al, e->data); } } idx = (idx + rehash_adv) % table_size; } while(count--); } return NULL; } int hashmap_remove(struct hashmap *h, void **key, void **data) { if (h->table) { unsigned long hash; int idx, rehash_adv, count, table_size; struct entry *e; struct allocator *al = HAL(h); if (h->table_size_index > 1 && (h->size * 100 / table_sizes[h->table_size_index]) < h->load_factor_low) { if (hashmap_resize(h, h->table_size_index - 1) == -1) { AMSG(""); return -1; } } hash = h->hash ? ((hash_fn)ALADR(al, h->hash))(*key, ALADR(al, h->context)) : hash_ptr(*key, ALADR(al, h->context)); table_size = table_sizes[h->table_size_index]; idx = hash % table_size; rehash_adv = 1 + hash % (table_size - 2); count = table_size; do { e = (struct entry *)ALADR(al, h->table) + idx; if (e->key == 0) { break; } else if (e->key != DELETED) { void *k = ALADR(al, e->key); if (hash == e->hash && (h->cmp ? ((cmp_fn)ALADR(al, h->cmp))(*key, k, ALADR(al, h->context)) == 0 : *key == k)) { *key = k; *data = ALADR(al, e->data); e->key = DELETED; h->size--; return 0; } } idx = (idx + rehash_adv) % table_size; } while(count--); } *data = NULL; errno = ENOENT; PMNO(errno); return -1; } int hashmap_is_empty(struct hashmap *h) { return h == NULL || h->size == 0; } unsigned int hashmap_size(struct hashmap *h) { return h ? h->size : 0; } void hashmap_iterate(void *h, iter_t *iter) { memset(iter, 0, sizeof *iter); (void)h; } void * hashmap_next(void *h0, iter_t *iter) { struct hashmap *h = h0; if (h->table) { int idx, table_size; struct entry *e; struct allocator *al = HAL(h); table_size = table_sizes[h->table_size_index]; idx = iter->i2; while (idx < table_size) { e = (struct entry *)ALADR(al, h->table) + idx++; if (e->key > DELETED) { iter->i2 = idx; return ALADR(al, e->key); } } } return NULL; }