/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright © 2010, Zed A. Shaw. * Copyright © 2020 rsiddharth */ #undef NDEBUG #include #include #include #include #include static int default_compare(void *a, void *b) { return bstrcmp((bstring) a, (bstring) b); } /** * Simple Bob Jenkin's hash algorithm taken from the wikipedia * description. */ static uint32_t default_hash(void *a) { size_t len = blength((bstring) a); char *key = bdata((bstring) a); uint32_t hash = 0; uint32_t i = 0; for (hash = i = 0; i < len; ++i) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /** * Fucked up version of FNV hash */ uint32_t fnv_hash(void *a) { size_t len = blength((bstring) a); char *key = bdata((bstring) a); uint32_t hash = 0; uint32_t i = 0; uint32_t fnv_prime = 16777619; uint32_t fnv_offset_basis = 2166136261; hash = fnv_offset_basis; for (i = 0; i < len; ++i) { hash *= fnv_prime; hash ^= key[i]; } return hash; } Hashmap *Hashmap_create(Hashmap_compare compare, Hashmap_hash hash) { Hashmap *map = calloc(1, sizeof(Hashmap)); check_mem(map); map->compare = compare == NULL ? default_compare : compare; map->hash = hash == NULL ? default_hash : hash; map->salt = rand() >> 16; map->buckets = DArray_create( sizeof(DArray *), DEFAULT_NUMBER_OF_BUCKETS); map->buckets->end = map->buckets->max; // fake out expanding it. check_mem(map->buckets); return map; error: if (map) { Hashmap_destroy(map); } return NULL; } void Hashmap_destroy(Hashmap *map) { int i = 0; int j = 0; if (map) { if (map->buckets) { for (i = 0; i < DArray_count(map->buckets); i++) { DArray *bucket = DArray_get(map->buckets, i); if (bucket) { for (j = 0; j < DArray_count(bucket); j++) { free(DArray_get(bucket, j)); } DArray_destroy(bucket); } } DArray_destroy(map->buckets); } free(map); } } static inline HashmapNode *Hashmap_node_create(int hash, void *key, void *data) { HashmapNode *node = calloc(1, sizeof(HashmapNode)); check_mem(node); node->key = key; node->data = data; node->hash = hash; return node; error: return NULL; } static inline DArray *Hashmap_find_bucket(Hashmap *map, void *key, int create, uint32_t *hash_out) { uint32_t hash = map->hash(key) + map->salt; int bucket_n = hash % DEFAULT_NUMBER_OF_BUCKETS; check(bucket_n >= 0, "Invalid bucket found %d", bucket_n); // store it for the return so the caller can use it *hash_out = hash; DArray *bucket = DArray_get(map->buckets, bucket_n); if (!bucket && create) { // new bucket, set it up bucket = DArray_create( sizeof(void *), DEFAULT_NUMBER_OF_BUCKETS); check_mem(bucket); DArray_set(map->buckets, bucket_n, bucket); } return bucket; error: return NULL; } int Hashmap_delete_bucket(Hashmap *map, uint32_t hash) { int bucket_n = hash % DEFAULT_NUMBER_OF_BUCKETS; check(bucket_n >= 0, "Invalid bucket found %d", bucket_n); // Get bucket. DArray *bucket = DArray_get(map->buckets, bucket_n); if(bucket) { // Remove bucket. DArray_clear_destroy(bucket); DArray_remove(map->buckets, bucket_n); } return 0; error: return -1; } int Hashmap_set(Hashmap *map, void *key, void *data) { uint32_t hash = 0; DArray *bucket = Hashmap_find_bucket(map, key, 1, &hash); check(bucket, "Error can't create bucket"); HashmapNode *node = Hashmap_node_create(hash, key, data); check_mem(node); DArray_push(bucket, node); // Sort the bucket. int rc = DArray_heapsort(bucket, (DArray_compare) map->compare); check(rc == 0, "Error sorting bucket"); return 0; error: return -1; } static inline int Hashmap_get_node(Hashmap *map, uint32_t hash, DArray *bucket, void *key) { int i = 0; for (i = 0; i < DArray_end(bucket); i++) { debug("TRY: %d", i); HashmapNode *node = DArray_get(bucket, i); if (node->hash == hash && map->compare(node->key, key) == 0) { return i; } } return -1; } void *Hashmap_get(Hashmap *map, void *key) { uint32_t hash = 0; DArray *bucket = Hashmap_find_bucket(map, key, 0, &hash); if (!bucket) return NULL; int i = Hashmap_get_node(map, hash, bucket, key); if (i == -1) return NULL; HashmapNode *node = DArray_get(bucket, i); check(node != NULL, "Failed to get node from bucket when it should exist."); return node->data; error: return NULL; } /** * Sets key <-> data iff key is not already present in Hashmap. */ int Hashmap_set_fucked(Hashmap *map, void *key, void *data) { uint32_t hash = 0; DArray *bucket = Hashmap_find_bucket(map, key, 1, &hash); check(bucket, "Error can't create bucket"); int i = Hashmap_get_node(map, hash, bucket, key); if (i >= 0) { // Element with `key` already exists. Do nothing. return 0; } // Element with `key` does not exist. Add it to Hashmap. HashmapNode *node = Hashmap_node_create(hash, key, data); check_mem(node); DArray_push(bucket, node); // Sort the bucket. int rc = DArray_heapsort(bucket, (DArray_compare) map->compare); check(rc == 0, "Error sorting bucket"); return 1; error: return -1; } int Hashmap_traverse(Hashmap *map, Hashmap_traverse_cb traverse_cb) { int i = 0; int j = 0; int rc = 0; for (i = 0; i < DArray_count(map->buckets); i++) { DArray *bucket = DArray_get(map->buckets, i); if (bucket) { for (j = 0; j < DArray_count(bucket); j++) { HashmapNode *node = DArray_get(bucket, j); rc = traverse_cb(node); if (rc != 0) return rc; } } } return 0; } /** * Returns all keys of the hashmap as a DArray. * * Use DArray_destroy on the 'keys' returned by this function after * use. */ DArray *Hashmap_keys(Hashmap *map) { check(map != NULL, "map is NULL"); DArray *keys = DArray_create(sizeof(void *), DEFAULT_NUMBER_OF_KEYS); check(keys != NULL, "Unable to initialize keys"); int i = 0; int j = 0; for (i = 0; i < DArray_count(map->buckets); i++) { DArray *bucket = DArray_get(map->buckets, i); if (bucket) { for (j = 0; j < DArray_count(bucket); j++) { HashmapNode *node = DArray_get(bucket, j); if (node) { DArray_push(keys, node->key); } } } } return keys; error: return NULL; } void *Hashmap_delete(Hashmap *map, void *key) { uint32_t hash = 0; DArray *bucket = Hashmap_find_bucket(map, key, 0, &hash); if (!bucket) return NULL; int i = Hashmap_get_node(map, hash, bucket, key); if (i == -1) return NULL; HashmapNode *node = DArray_get(bucket, i); void *data = node->data; free(node); HashmapNode *ending = DArray_pop(bucket); if (ending != node) { // alright looks like it's not the last one, swap it. DArray_set(bucket, i, ending); } else { // alright looks like it's the last one, destroy bucket. check(Hashmap_delete_bucket(map, hash) == 0, "Error destroy bucket"); } return data; error: return NULL; }