/* * NetCosm - a MUD server * Copyright (C) 2016 Franklin Wei * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "hash.h" #include "util.h" // for error() #include #include #include /** * @file * @brief A simple, chained hash table */ struct hash_node { const void *key; const void *data; struct hash_node *next; }; struct hash_map { char sentinel; /* for avoiding hash/multihash confusion */ unsigned (*hash)(const void *data); int (*compare)(const void *a, const void *b); struct hash_node **table; size_t table_sz; void (*free_key)(void *key); void (*free_data)(void *data); void* (*dup_data)(void *data); size_t n_entries; }; #define CHECK_SENTINEL(map) do{if(map && ((struct hash_map*)map)->sentinel!=HASH_SENTINEL)error("hash/multimap mixing");}while(0); unsigned hash_djb(const void *ptr) { const char *str = ptr; unsigned hash = 5381; char c; while((c = *str++)) { hash = ((hash << 5) + hash) ^ c; } return hash; } /* wrappers to suppress warnings */ int compare_strings(const void *a, const void *b) { return strcmp(a,b); } int compare_strings_nocase(const void *a, const void *b) { return strcasecmp(a,b); } void *hash_init(size_t sz, unsigned (*hash_fn)(const void*), int (*compare_keys)(const void*, const void*)) { struct hash_map *ret = calloc(sizeof(struct hash_map), 1); if(!sz) sz = 1; ret->sentinel = HASH_SENTINEL; ret->table = calloc(sz, sizeof(struct hash_node*)); ret->table_sz = sz; ret->hash = hash_fn; ret->compare = compare_keys; ret->n_entries = 0; return ret; } void hash_setfreekey_cb(void *ptr, void (*cb)(void *key)) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); map->free_key = cb; } } void hash_setfreedata_cb(void *ptr, void (*cb)(void *data)) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); map->free_data = cb; } } void hash_free(void *ptr) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); for(unsigned i = 0; i < map->table_sz; ++i) { struct hash_node *node = map->table[i]; while(node) { struct hash_node *next = node->next; if(map->free_data) map->free_data((void*)node->data); if(map->free_key) map->free_key((void*)node->key); free(node); node = next; } } free(map->table); free(map); } } void *hash_iterate(void *map, void **saveptr, void **keyptr) { struct iterstate_t { struct hash_map *map; struct hash_node *node; unsigned bucket; }; struct iterstate_t *saved; if(map) { *saveptr = malloc(sizeof(struct iterstate_t)); saved = *saveptr; saved->map = map; CHECK_SENTINEL(map); saved->bucket = 0; saved->node = NULL; } else saved = *saveptr; for(;saved->bucket < saved->map->table_sz; ++(saved->bucket)) { do { if(!saved->node) saved->node = saved->map->table[saved->bucket]; else saved->node = saved->node->next; if(saved->node) { if(keyptr) *keyptr = (void*)saved->node->key; return (void*)saved->node->data; } } while(saved->node); } free(saved); return NULL; } void hash_overwrite(void *ptr, const void *key, const void *data) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); unsigned hash = map->hash(key) % map->table_sz; struct hash_node *iter = map->table[hash]; struct hash_node *last = NULL; while(iter) { if(map->compare(key, iter->key) == 0) { if(map->free_data) map->free_data((void*)iter->data); if(map->free_key) map->free_key((void*)iter->key); iter->key = key; iter->data = data; return; } last = iter; iter = iter->next; } /* insert */ struct hash_node *new = calloc(sizeof(struct hash_node), 1); new->key = key; new->data = data; new->next = NULL; if(!last) map->table[hash] = new; else last->next = new; ++map->n_entries; } } void *hash_insert(void *ptr, const void *key, const void *data) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); unsigned hash = map->hash(key) % map->table_sz; struct hash_node *iter = map->table[hash]; struct hash_node *last = NULL; while(iter) { if(map->compare(key, iter->key) == 0) return (void*)(iter->data); last = iter; iter = iter->next; } /* insert */ struct hash_node *new = calloc(sizeof(struct hash_node), 1); new->key = key; new->data = data; new->next = NULL; if(!last) map->table[hash] = new; else last->next = new; ++map->n_entries; /* fall through */ } return NULL; } void *hash_lookup(void *ptr, const void *key) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); unsigned hash = map->hash(key) % map->table_sz; struct hash_node *iter = map->table[hash]; while(iter) { if(map->compare(key, iter->key) == 0) return (void*)(iter->data); iter = iter->next; } /* fall through */ } return NULL; } void hash_insert_pairs(void *ptr, const struct hash_pair *pairs, size_t pairsize, size_t n) { CHECK_SENTINEL(ptr); const char *iter = (const char*)pairs; for(unsigned i = 0; i < n; ++i) { const struct hash_pair *pair = (const struct hash_pair*)iter; hash_insert(ptr, pair->key, pair); iter += pairsize; } } bool hash_remove(void *ptr, const void *key) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); unsigned hash = map->hash(key) % map->table_sz; struct hash_node *iter = map->table[hash], *last = NULL; while(iter) { if(map->compare(key, iter->key) == 0) { if(last) last->next = iter->next; else map->table[hash] = iter->next; if(map->free_key) map->free_key((void*)iter->key); if(map->free_data) map->free_data((void*)iter->data); --map->n_entries; free(iter); return true; } last = iter; iter = iter->next; } /* fall through */ } return false; } void *hash_getkeyptr(void *ptr, const void *key) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); unsigned hash = map->hash(key) % map->table_sz; struct hash_node *iter = map->table[hash]; while(iter) { if(map->compare(key, iter->key) == 0) return (void*)(iter->key); iter = iter->next; } /* fall through */ } return NULL; } size_t hash_size(void *ptr) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); return map->n_entries; } else return 0; } void *hash_dup(void *ptr) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); struct hash_map *ret = calloc(1, sizeof(struct hash_map)); memcpy(ret, map, sizeof(*ret)); ret->table = calloc(ret->table_sz, sizeof(struct hash_node*)); ret->n_entries = 0; void *save = NULL; while(1) { void *key; void *data = hash_iterate(ptr, &save, &key); if(!data) break; ptr = NULL; if(map->dup_data) data = map->dup_data(data); hash_insert(ret, key, data); } return ret; } else return NULL; } void hash_setdupdata_cb(void *ptr, void *(*cb)(void*)) { if(ptr) { struct hash_map *map = ptr; CHECK_SENTINEL(map); map->dup_data = cb; } }