summaryrefslogblamecommitdiffstats
path: root/src/hashmap.c
blob: b42c48ef5373c7cbe27c263106af754dae7ca3df (plain) (tree)
1
2
3
4
5
6





                                                    



































                                                              



















                                           


































































































































































                                                                              




























                                                                    





















                                                                   


































                                                                   





























                                                                
/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * Copyright © 2010, Zed A. Shaw.
 * Copyright © 2020 rsiddharth <s@ricketyspace.net>
 */

#undef NDEBUG
#include <stdint.h>
#include <darray_algos.h>
#include <hashmap.h>
#include <dbg.h>
#include <bstrlib.h>

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;
}