universe@549: /* universe@549: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. universe@549: * universe@549: * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. universe@549: * universe@549: * Redistribution and use in source and binary forms, with or without universe@549: * modification, are permitted provided that the following conditions are met: universe@549: * universe@549: * 1. Redistributions of source code must retain the above copyright universe@549: * notice, this list of conditions and the following disclaimer. universe@549: * universe@549: * 2. Redistributions in binary form must reproduce the above copyright universe@549: * notice, this list of conditions and the following disclaimer in the universe@549: * documentation and/or other materials provided with the distribution. universe@549: * universe@549: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" universe@549: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE universe@549: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE universe@549: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE universe@549: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR universe@549: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF universe@549: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS universe@549: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN universe@549: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) universe@549: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE universe@549: * POSSIBILITY OF SUCH DAMAGE. universe@549: */ universe@549: universe@549: #include universe@549: #include "cx/hash_map.h" universe@550: #include "cx/utils.h" universe@549: universe@550: static void cx_hash_map_clear(struct cx_map_s *map) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: cx_for_n(i, hash_map->bucket_count) { universe@550: struct cx_hash_map_element_s *elem = hash_map->buckets[i]; universe@550: if (elem != NULL) { universe@550: do { universe@550: struct cx_hash_map_element_s *next = elem->next; universe@550: // free the key data universe@563: cxFree(map->allocator, elem->key.data.obj); universe@550: // free the node universe@550: cxFree(map->allocator, elem); universe@550: // proceed universe@550: elem = next; universe@550: } while (elem != NULL); universe@550: universe@550: // do not leave a dangling pointer universe@550: hash_map->buckets[i] = NULL; universe@550: } universe@550: } universe@550: map->size = 0; universe@550: } universe@550: universe@550: static void cx_hash_map_destructor(struct cx_map_s *map) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: universe@550: // free the buckets universe@550: cx_hash_map_clear(map); universe@550: cxFree(map->allocator, hash_map->buckets); universe@550: universe@550: // free the map structure universe@550: cxFree(map->allocator, map); universe@550: } universe@550: universe@550: static int cx_hash_map_put( universe@550: CxMap *map, universe@563: CxHashKey key, universe@550: void *value universe@550: ) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: CxAllocator *allocator = map->allocator; universe@550: universe@563: unsigned hash = key.hash; universe@563: if (hash == 0) { universe@563: cx_hash_murmur(&key); universe@563: hash = key.hash; universe@563: } universe@550: universe@550: size_t slot = hash % hash_map->bucket_count; universe@550: struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; universe@550: struct cx_hash_map_element_s *prev = NULL; universe@550: universe@550: while (elm != NULL && elm->key.hash < hash) { universe@550: prev = elm; universe@550: elm = elm->next; universe@550: } universe@550: universe@550: if (elm == NULL || elm->key.hash != hash) { universe@550: struct cx_hash_map_element_s *e = cxMalloc(allocator, sizeof(struct cx_hash_map_element_s)); universe@550: if (e == NULL) { universe@550: return -1; universe@550: } universe@550: universe@550: // write the value universe@550: // TODO: depending on future map features, we may want to copy here universe@550: e->data = value; universe@550: universe@550: // copy the key universe@550: void *kd = cxMalloc(allocator, key.len); universe@550: if (kd == NULL) { universe@550: return -1; universe@550: } universe@563: memcpy(kd, key.data.obj, key.len); universe@563: e->key.data.obj = kd; universe@550: e->key.len = key.len; universe@550: e->key.hash = hash; universe@550: universe@550: // insert the element into the linked list universe@550: if (prev == NULL) { universe@550: hash_map->buckets[slot] = e; universe@550: } else { universe@550: prev->next = e; universe@550: } universe@550: e->next = elm; universe@550: universe@550: // increase the size universe@550: map->size++; universe@550: } else { universe@550: // (elem != NULL && elem->key.hash == hash) - overwrite value of existing element universe@550: elm->data = value; universe@550: } universe@550: universe@550: return 0; universe@550: } universe@549: universe@551: static void cx_hash_map_unlink( universe@551: struct cx_hash_map_s *hash_map, universe@551: size_t slot, universe@551: struct cx_hash_map_element_s *prev, universe@551: struct cx_hash_map_element_s *elm universe@551: ) { universe@551: // unlink universe@551: if (prev == NULL) { universe@551: hash_map->buckets[slot] = elm->next; universe@551: } else { universe@551: prev->next = elm->next; universe@551: } universe@551: // free element universe@563: cxFree(hash_map->base.allocator, elm->key.data.obj); universe@551: cxFree(hash_map->base.allocator, elm); universe@551: // decrease size universe@551: hash_map->base.size--; universe@551: } universe@551: universe@549: /** universe@550: * Helper function to avoid code duplication. universe@549: * universe@550: * @param map the map universe@550: * @param key the key to look up universe@550: * @param remove flag indicating whether the looked up entry shall be removed universe@550: * @return the value corresponding to the key or \c NULL universe@549: */ universe@550: static void *cx_hash_map_get_remove( universe@550: CxMap *map, universe@563: CxHashKey key, universe@550: bool remove universe@550: ) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: universe@563: unsigned hash = key.hash; universe@563: if (hash == 0) { universe@563: cx_hash_murmur(&key); universe@563: hash = key.hash; universe@563: } universe@550: universe@550: size_t slot = hash % hash_map->bucket_count; universe@550: struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; universe@550: struct cx_hash_map_element_s *prev = NULL; universe@550: while (elm && elm->key.hash <= hash) { universe@550: if (elm->key.hash == hash && elm->key.len == key.len) { universe@563: if (memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) { universe@550: void *data = elm->data; universe@550: if (remove) { universe@551: cx_hash_map_unlink(hash_map, slot, prev, elm); universe@550: } universe@550: return data; universe@550: } universe@550: } universe@550: prev = elm; universe@550: elm = prev->next; universe@550: } universe@550: universe@550: return NULL; universe@549: } universe@550: universe@550: static void *cx_hash_map_get( universe@550: CxMap const *map, universe@563: CxHashKey key universe@550: ) { universe@550: // we can safely cast, because we know when remove=false, the map stays untouched universe@550: return cx_hash_map_get_remove((CxMap *) map, key, false); universe@550: } universe@550: universe@550: static void *cx_hash_map_remove( universe@550: CxMap *map, universe@563: CxHashKey key universe@550: ) { universe@550: return cx_hash_map_get_remove(map, key, true); universe@550: } universe@550: universe@551: static void *cx_hash_map_iter_current_entry(CxIterator const *iter) { universe@551: // struct has to have a compatible signature universe@573: return (struct cx_map_entry_s *) &(iter->kv_data); universe@551: } universe@551: universe@551: static void *cx_hash_map_iter_current_key(CxIterator const *iter) { universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: return &elm->key; universe@551: } universe@551: universe@551: static void *cx_hash_map_iter_current_value(CxIterator const *iter) { universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: // TODO: return a pointer to data if this map is storing copies universe@551: return elm->data; universe@551: } universe@551: universe@551: static bool cx_hash_map_iter_valid(CxIterator const *iter) { universe@551: return iter->elem_handle != NULL; universe@551: } universe@551: universe@551: static void cx_hash_map_iter_next(CxIterator *iter) { universe@551: struct cx_hash_map_s *map = iter->src_handle; universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: universe@551: // remove current element, if asked universe@551: if (iter->remove) { universe@551: // clear the flag universe@551: iter->remove = false; universe@551: universe@551: // determine the next element universe@551: struct cx_hash_map_element_s *next = elm->next; universe@551: universe@551: // search the previous element universe@551: struct cx_hash_map_element_s *prev = NULL; universe@551: if (map->buckets[iter->slot] != elm) { universe@551: prev = map->buckets[iter->slot]; universe@551: while (prev->next != elm) { universe@551: prev = prev->next; universe@551: } universe@551: } universe@551: universe@551: // unlink universe@551: cx_hash_map_unlink(map, iter->slot, prev, elm); universe@551: universe@551: // advance universe@551: elm = next; universe@551: } else { universe@551: // just advance universe@551: elm = elm->next; universe@560: iter->index++; universe@551: } universe@551: universe@560: // search the next bucket, if required universe@560: while (elm == NULL && ++iter->slot < map->bucket_count) { universe@560: elm = map->buckets[iter->slot]; universe@551: } universe@551: universe@560: // fill the struct with the next element universe@551: iter->elem_handle = elm; universe@551: if (elm == NULL) { universe@551: iter->kv_data.key = NULL; universe@551: iter->kv_data.value = NULL; universe@551: } else { universe@551: iter->kv_data.key = &elm->key; universe@551: // TODO: pointer to data if this map is storing copies universe@551: iter->kv_data.value = elm->data; universe@551: } universe@551: } universe@551: universe@551: static CxIterator cx_hash_map_iterator(CxMap *map) { universe@550: CxIterator iter; universe@551: universe@551: iter.src_handle = map; universe@551: iter.valid = cx_hash_map_iter_valid; universe@551: iter.next = cx_hash_map_iter_next; universe@551: iter.current = cx_hash_map_iter_current_entry; universe@551: universe@551: iter.slot = 0; universe@551: iter.index = 0; universe@551: iter.remove = false; universe@551: universe@551: if (map->size > 0) { universe@551: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@560: struct cx_hash_map_element_s *elm = hash_map->buckets[0]; universe@554: for (; elm == NULL; iter.slot++) { universe@554: elm = hash_map->buckets[iter.slot]; universe@551: } universe@554: iter.elem_handle = elm; universe@554: iter.kv_data.key = &elm->key; universe@554: // TODO: pointer to data if this map is storing copies universe@554: iter.kv_data.value = elm->data; universe@551: } else { universe@551: iter.elem_handle = NULL; universe@551: iter.kv_data.key = NULL; universe@551: iter.kv_data.value = NULL; universe@551: } universe@551: universe@550: return iter; universe@550: } universe@550: universe@551: static CxIterator cx_hash_map_iterator_keys(CxMap *map) { universe@551: CxIterator iter = cx_hash_map_iterator(map); universe@551: iter.current = cx_hash_map_iter_current_key; universe@550: return iter; universe@550: } universe@550: universe@551: static CxIterator cx_hash_map_iterator_values(CxMap *map) { universe@551: CxIterator iter = cx_hash_map_iterator(map); universe@551: iter.current = cx_hash_map_iter_current_value; universe@550: return iter; universe@550: } universe@550: universe@550: static cx_map_class cx_hash_map_class = { universe@550: cx_hash_map_destructor, universe@550: cx_hash_map_clear, universe@550: cx_hash_map_put, universe@550: cx_hash_map_get, universe@550: cx_hash_map_remove, universe@550: cx_hash_map_iterator, universe@550: cx_hash_map_iterator_keys, universe@550: cx_hash_map_iterator_values, universe@550: }; universe@550: universe@550: CxMap *cxHashMapCreate( universe@550: CxAllocator *allocator, universe@550: size_t buckets universe@550: ) { universe@550: if (buckets == 0) { universe@550: // implementation defined default universe@550: buckets = 16; universe@550: } universe@550: universe@550: struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s)); universe@550: if (map == NULL) return NULL; universe@550: universe@550: // initialize hash map members universe@550: map->bucket_count = buckets; universe@550: map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *)); universe@550: if (map->buckets == NULL) { universe@550: cxFree(allocator, map); universe@550: return NULL; universe@550: } universe@550: universe@550: // initialize base members universe@550: map->base.cl = &cx_hash_map_class; universe@550: map->base.allocator = allocator; universe@550: map->base.size = 0; universe@550: universe@550: return (CxMap *) map; universe@550: } universe@562: universe@562: int cxMapRehash(CxMap *map) { universe@562: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@562: if (map->size > ((hash_map->bucket_count * 3) >> 2)) { universe@562: universe@562: size_t new_bucket_count = (map->size * 5) >> 1; universe@562: struct cx_hash_map_element_s **new_buckets = cxCalloc(map->allocator, universe@562: new_bucket_count, sizeof(struct cx_hash_map_element_s *)); universe@562: universe@562: if (new_buckets == NULL) { universe@562: return 1; universe@562: } universe@562: universe@562: // iterate through the elements and assign them to their new slots universe@562: cx_for_n(slot, hash_map->bucket_count) { universe@562: struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; universe@562: while (elm != NULL) { universe@562: struct cx_hash_map_element_s *next = elm->next; universe@562: size_t new_slot = elm->key.hash % new_bucket_count; universe@562: universe@562: // find position where to insert universe@562: struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot]; universe@562: struct cx_hash_map_element_s *bucket_prev = NULL; universe@562: while (bucket_next != NULL && bucket_next->key.hash < elm->key.hash) { universe@562: bucket_prev = bucket_next; universe@562: bucket_next = bucket_next->next; universe@562: } universe@562: universe@562: // insert universe@562: if (bucket_prev == NULL) { universe@562: elm->next = new_buckets[new_slot]; universe@562: new_buckets[new_slot] = elm; universe@562: } else { universe@562: bucket_prev->next = elm; universe@562: elm->next = bucket_next; universe@562: } universe@562: universe@562: // advance universe@562: elm = next; universe@562: } universe@562: } universe@562: universe@562: // assign result to the map universe@562: hash_map->bucket_count = new_bucket_count; universe@562: cxFree(map->allocator, hash_map->buckets); universe@562: hash_map->buckets = new_buckets; universe@562: } universe@562: return 0; universe@562: }