Download
203 lines 5.9 KiBCModified 2022-10-26 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
// hashmap.c - C Hash Map Library
// Copyright 2022 Bruce Hill
// Provided under the MIT license with the Commons Clause
// See included LICENSE for details.

// Hash Map (aka Dictionary) Implementation
// Keys are pointers, and entries are stored in an array.
// If you want to use strings as pointers, you can intern the strings so
// that each unique string has a unique pointer.
// If you want to use numbers as pointers, you can cast them to pointers.
// The hash insertion/lookup implementation is based on Lua's tables,
// which use a chained scatter with Brent's variation.
// See README.md for more details.

#include <stdlib.h>
#include <string.h>

#include "bhash.h"

static void *(*custom_alloc)(size_t) = malloc;
static void (*custom_free)(void*) = free;

void hashmap_set_allocator(void *(*alloc)(size_t), void (*free)(void*))
{
    custom_alloc = alloc;
    custom_free = free;
}

static inline size_t hash_pointer(const void *p)
{
    size_t s = (size_t)p;
    if (s == 0) return 1234567;
    return (s >> 5) | (s << (sizeof(void*) - 5));
}

static void hashmap_resize(hashmap_t *h, int new_size)
{
    hashmap_t old = *h;
    size_t size = (size_t)new_size*sizeof(hashmap_entry_t);
    h->entries = custom_alloc(size);
    h->fallback = old.fallback;
    memset(h->entries, 0, size);
    h->capacity = new_size;
    h->count = 0;
    h->lastfree = &h->entries[new_size - 1];
    if (old.entries) {
        // Rehash:
        for (int i = 0; i < old.capacity; i++)
            if (old.entries[i].key)
                (void)hashmap_set(h, old.entries[i].key, old.entries[i].value);
        if (custom_free) custom_free(old.entries);
    }
}

hashmap_t *hashmap_new(void)
{
    hashmap_t *h = custom_alloc(sizeof(hashmap_t));
    if (!h) return h;
    memset(h, 0, sizeof(hashmap_t));
    return h;
}

hashmap_t *hashmap_copy(hashmap_t *h)
{
    hashmap_t *copy = hashmap_new();
    if (!copy) return copy;

    int capacity = h->capacity;
    hashmap_entry_t *entries = h->entries;
    for (int i = 0; i < capacity; i++)
        if (entries[i].key)
            (void)hashmap_set(copy, entries[i].key, entries[i].value);
    copy->fallback = h->fallback;
    return copy;
}

size_t hashmap_length(hashmap_t *h)
{
    return (size_t)h->count;
}

void hashmap_clear(hashmap_t *h)
{
    if (h->capacity == 0) return;
    if (custom_free) custom_free(h->entries);
    h->entries = NULL;
    h->lastfree = NULL;
    h->capacity = 0;
    h->count = 0;
}

void *hashmap_get(hashmap_t *h, const void *key)
{
    if (h->capacity > 0) {
        int i = (int)(hash_pointer(key) & (size_t)(h->capacity-1));
        for (hashmap_entry_t *e = &h->entries[i]; e && e->key; e = e->next) {
            if (e->key == key)
                return e->value;
        }
    }
    if (h->fallback) return hashmap_get(h->fallback, key);
    return NULL;
}

void *hashmap_set(hashmap_t *h, const void *key, const void *value)
{
    if (key == NULL) return NULL;

    if (h->capacity == 0) hashmap_resize(h, 16);

  retry:;
    int i = (int)(hash_pointer(key) & (size_t)(h->capacity-1));
    hashmap_entry_t *collision = &h->entries[i];
    if (!collision->key) { // Found empty slot
        if (!value) return NULL;
        collision->key = key;
        collision->value = (void*)value;
        collision->next = NULL;
        ++h->count;
        return NULL;
    }

    int i2 = (int)(hash_pointer(collision->key) & (size_t)(h->capacity-1));
    if (i2 == i) { // Hit a node in the correct place
        // Check for update to existing key:
        for (hashmap_entry_t *e = collision; e && e->key; e = e->next) {
            if (e->key == key) { // Update value
                void *old_value = e->value;
                h->count += (value ? 1 : 0) + (old_value ? -1 : 0);
                e->value = (void*)value;
                return old_value;
            }
        }
    }

    // Find a free space to insert:
    while (h->lastfree >= h->entries && h->lastfree->key)
        --h->lastfree;

    // No spaces left, gotta resize and try again:
    if (h->lastfree < h->entries) {
        int newsize = h->capacity;
        if (h->count + 1 > newsize) newsize *= 2;
        else if (h->count + 1 <= newsize/2) newsize /= 2;
        hashmap_resize(h, newsize);
        goto retry;
    }

    if (i2 == i) {
        // Put new node in a free slot
        h->lastfree->key = key;
        h->lastfree->value = (void*)value;
        // Put it between the colliding node and the second node in the chain
        h->lastfree->next = collision->next;
        collision->next = h->lastfree;
    } else { // Hit the middle of a chain for some other hash value
        // Rearrange from prevcollision..collision@i..nextcollision, NULL@nextfree
        // to: (key:value)@i, prevcollision..collision@nextfree..nextcollision
        hashmap_entry_t *prev = &h->entries[i2];
        while (prev->next != collision)
            prev = prev->next;

        // Scootch collider to new space
        memcpy(h->lastfree, collision, sizeof(hashmap_entry_t));
        prev->next = h->lastfree;

        collision->key = key;
        collision->value = (void*)value;
        collision->next = NULL;
    }
    ++h->count;
    return NULL;
}

const void *hashmap_next(hashmap_t *h, const void *key)
{
    if (h->capacity == 0) return NULL;
    hashmap_entry_t *e = &h->entries[0];
    if (key) {
        // Find entry in the hash table
        int i = (int)(hash_pointer(key) & (size_t)(h->capacity-1));
        e = &h->entries[i];
        if (!e->key) return NULL;
        while (e && e->key != key)
            e = e->next;
        if (!e) return NULL;
        // Then start looking for the next free entry after it
        ++e;
    }
    for (; e < &h->entries[h->capacity]; e++)
        if (e->key && e->value) return e->key;

    return NULL;
}

void hashmap_free(hashmap_t **h)
{
    if (*h == NULL || !custom_free) return;
    if ((*h)->entries) custom_free((*h)->entries);
    custom_free(*h);
    *h = NULL;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1