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 "cx/hash_map.h" universe@550: #include "cx/utils.h" universe@549: universe@709: #include universe@709: #include universe@709: universe@658: struct cx_hash_map_element_s { universe@658: /** A pointer to the next element in the current bucket. */ universe@658: struct cx_hash_map_element_s *next; universe@658: universe@658: /** The corresponding key. */ universe@658: CxHashKey key; universe@658: universe@658: /** The value data. */ universe@658: char data[]; universe@658: }; universe@658: 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@686: // invoke the destructor universe@686: cx_invoke_destructor(map, elem->data); universe@550: // free the key data universe@690: cxFree(map->allocator, (void *) elem->key.data); 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@686: CxAllocator const *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@575: if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && universe@690: memcmp(elm->key.data, key.data, key.len) == 0) { universe@574: // overwrite existing element universe@685: if (map->store_pointer) { universe@658: memcpy(elm->data, &value, sizeof(void *)); universe@658: } else { universe@677: memcpy(elm->data, value, map->item_size); universe@658: } universe@574: } else { universe@575: // allocate new element universe@658: struct cx_hash_map_element_s *e = cxMalloc( universe@658: allocator, universe@677: sizeof(struct cx_hash_map_element_s) + map->item_size universe@658: ); universe@550: if (e == NULL) { universe@550: return -1; universe@550: } universe@550: universe@550: // write the value universe@685: if (map->store_pointer) { universe@658: memcpy(e->data, &value, sizeof(void *)); universe@658: } else { universe@677: memcpy(e->data, value, map->item_size); universe@658: } 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@690: memcpy(kd, key.data, key.len); universe@690: e->key.data = 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: } 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@690: cxFree(hash_map->base.allocator, (void *) elm->key.data); 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@686: * @param destroy flag indicating whether the destructor shall be invoked universe@658: * @return a pointer to 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@686: bool remove, universe@686: bool destroy 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@690: if (memcmp(elm->key.data, key.data, key.len) == 0) { universe@658: void *data = NULL; universe@686: if (destroy) { universe@686: cx_invoke_destructor(map, elm->data); universe@686: } else { universe@686: if (map->store_pointer) { universe@686: data = *(void **) elm->data; universe@686: } else { universe@686: data = elm->data; universe@686: } universe@658: } 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@688: // we can safely cast, because we know the map stays untouched universe@686: return cx_hash_map_get_remove((CxMap *) map, key, false, false); universe@550: } universe@550: universe@550: static void *cx_hash_map_remove( universe@550: CxMap *map, universe@686: CxHashKey key, universe@686: bool destroy universe@550: ) { universe@686: return cx_hash_map_get_remove(map, key, true, destroy); universe@550: } universe@550: universe@630: static void *cx_hash_map_iter_current_entry(void const *it) { universe@630: struct cx_iterator_s const *iter = it; 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@630: static void *cx_hash_map_iter_current_key(void const *it) { universe@630: struct cx_iterator_s const *iter = it; universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: return &elm->key; universe@551: } universe@551: universe@630: static void *cx_hash_map_iter_current_value(void const *it) { universe@630: struct cx_iterator_s const *iter = it; universe@658: struct cx_hash_map_s const *map = iter->src_handle; universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@685: if (map->base.store_pointer) { universe@658: return *(void **) elm->data; universe@658: } else { universe@658: return elm->data; universe@658: } universe@551: } universe@551: universe@630: static bool cx_hash_map_iter_valid(void const *it) { universe@630: struct cx_iterator_s const *iter = it; universe@551: return iter->elem_handle != NULL; universe@551: } universe@551: universe@630: static void cx_hash_map_iter_next(void *it) { universe@630: struct cx_iterator_s *iter = it; universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: universe@551: // remove current element, if asked universe@630: if (iter->base.remove) { universe@630: // obtain mutable pointer to the map universe@630: struct cx_mut_iterator_s *miter = it; universe@630: struct cx_hash_map_s *map = miter->src_handle; universe@630: universe@551: // clear the flag universe@630: iter->base.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@686: // destroy universe@686: cx_invoke_destructor((struct cx_map_s *) map, elm->data); universe@686: 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@630: struct cx_hash_map_s const *map = iter->src_handle; 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@685: if (map->base.store_pointer) { universe@658: iter->kv_data.value = *(void **) elm->data; universe@658: } else { universe@658: iter->kv_data.value = elm->data; universe@658: } universe@551: } universe@551: } universe@551: universe@709: static CxIterator cx_hash_map_iterator( universe@709: CxMap const *map, universe@709: enum cx_map_iterator_type type universe@709: ) { universe@550: CxIterator iter; universe@551: universe@551: iter.src_handle = map; universe@630: iter.base.valid = cx_hash_map_iter_valid; universe@630: iter.base.next = cx_hash_map_iter_next; universe@709: universe@709: switch (type) { universe@709: case CX_MAP_ITERATOR_PAIRS: universe@709: iter.base.current = cx_hash_map_iter_current_entry; universe@709: break; universe@709: case CX_MAP_ITERATOR_KEYS: universe@709: iter.base.current = cx_hash_map_iter_current_key; universe@709: break; universe@709: case CX_MAP_ITERATOR_VALUES: universe@709: iter.base.current = cx_hash_map_iter_current_value; universe@709: break; universe@709: default: universe@709: assert(false); universe@709: } universe@709: universe@630: iter.base.remove = false; universe@630: iter.base.mutating = false; universe@551: universe@551: iter.slot = 0; universe@551: iter.index = 0; 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]; olaf@665: while (elm == NULL) { olaf@665: elm = hash_map->buckets[++iter.slot]; universe@551: } universe@554: iter.elem_handle = elm; universe@554: iter.kv_data.key = &elm->key; universe@685: if (map->store_pointer) { universe@658: iter.kv_data.value = *(void **) elm->data; universe@658: } else { universe@658: iter.kv_data.value = elm->data; universe@658: } 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@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: }; universe@550: universe@550: CxMap *cxHashMapCreate( universe@691: CxAllocator const *allocator, universe@658: size_t itemsize, 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@688: struct cx_hash_map_s *map = cxCalloc(allocator, 1, universe@688: 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@688: map->buckets = cxCalloc(allocator, buckets, universe@688: 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@668: universe@668: if (itemsize > 0) { universe@685: map->base.store_pointer = false; universe@677: map->base.item_size = itemsize; universe@668: } else { universe@685: map->base.store_pointer = true; universe@677: map->base.item_size = sizeof(void *); universe@668: } 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@688: struct cx_hash_map_element_s **new_buckets = cxCalloc( universe@688: map->allocator, universe@688: new_bucket_count, sizeof(struct cx_hash_map_element_s *) universe@688: ); 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@688: while (bucket_next != NULL && universe@688: 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: }