bunkum

an old and silly c99 web server with some fun features
Log | Files | Refs | README

hashmap.c (5917B)


      1 #include <stdlib.h>
      2 #include <string.h>
      3 #include <stdio.h>
      4 #include <stdint.h>
      5 #include <stdbool.h>
      6 
      7 #include "log.h"
      8 #include "hashmap.h"
      9 
     10 #define HASHMAP_INIT_SIZE 16
     11 
     12 // djb2 hash
     13 // http://www.cse.yorku.ca/~oz/hash.html
     14 size_t hash(char* p, size_t sz) {
     15     unsigned long hash = 5381;
     16     int c;
     17 
     18     for (int i = 0; i < sz; i++) {
     19         c = *p++;
     20         hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
     21     }
     22     return hash;
     23 }
     24 
     25 // Check if arbitrary block of memory is zero
     26 bool iszero(void *p, size_t len) {
     27     bool iszero = true;
     28     for (int i = 0; i < len; i++){
     29         if (*(char*)p != 0) {
     30             iszero = false;
     31             break;
     32         }
     33         p++;
     34     }
     35     return iszero;
     36 }
     37 
     38 // Initialize the hashmap pointed to by h.
     39 hashmap_t hashmap_new(size_t ksize, size_t vsize) {
     40     hashmap_t h;
     41     // Keys and vals are in different arrays so unnecessary things aren't in cache
     42     h.keys = calloc(HASHMAP_INIT_SIZE, ksize);
     43     h.vals = malloc(vsize * HASHMAP_INIT_SIZE);
     44     h.k_sz = ksize;
     45     h.v_sz = vsize;
     46     h.len = HASHMAP_INIT_SIZE;
     47     h.vark = false;
     48     h.filled = 0; // Store # of filled keys for load factor calculation
     49     return h;
     50 }
     51 
     52 int hashmap_kcmp(hashmap_t* h, void* a, void* b) {
     53     if (h->vark) return strcmp(a, b);
     54     return memcmp(a, b, h->k_sz);
     55 }
     56 
     57 void* hashmap_kcpy(hashmap_t* h, void* a, void* b) {
     58     if (h->vark) return strcpy(a, b);
     59     return memcpy(a, b, h->k_sz);
     60 }
     61 
     62 void hashmap_resize(hashmap_t* h) {
     63     void* tempk = h->keys;
     64     void* tempv = h->vals;
     65     h->len *= 2;
     66     // Simple to use calloc here because keys need to be initialized to zero.
     67     h->keys = calloc(h->len, h->k_sz);
     68     h->vals = malloc(h->len * h->v_sz);
     69     // Copy all keys and values
     70     for (size_t off = 0; off < (h->len / 2); off++) {
     71         // Don't bother copying entries with empty keys
     72         if (!iszero(tempk + (off*h->k_sz), h->k_sz)) {
     73             hashmap_set(h, tempk + (off*h->k_sz), tempv + (off*h->v_sz));
     74             h->filled--;
     75         }
     76     }
     77     free(tempk);
     78     free(tempv);
     79 }
     80 
     81 void hashmap_dump(hashmap_t* h) {
     82     for (size_t off = 0; off < h->len; off+=1) {
     83         char* k = h->keys + (off*h->k_sz);
     84         char* v = h->vals + (off*h->v_sz);
     85         log_debug("'%s' (%p) -> '%s'", k, k, v == NULL ? "NULL" : v);
     86     }
     87 }
     88 
     89 int hashmap_set(hashmap_t* h, void* k, void* v) {
     90     // Check if load factor too high
     91     if ((float)h->filled / h->len > (float)2/3) hashmap_resize(h);
     92     size_t index = hash(k, h->vark ? strlen(k) : h->k_sz) % h->len;
     93     int looped_once = 0;
     94     // Start at the hashed index, iterate until own key or empty key is found
     95     for (size_t off = index; off < h->len; off+=1) {
     96         // Check if stored key at hashed index is equal to key: if so, replace value
     97         if (hashmap_kcmp(h, h->keys + (off*h->k_sz), k) == 0) {
     98             memcpy(h->vals + (off*h->v_sz), v, h->v_sz);
     99             return 0x0;
    100         }
    101         // Check if stored key is zero: if so, copy key and value over
    102         if (iszero(h->keys + (off*h->k_sz), h->k_sz)) {
    103             h->filled++;
    104             memcpy(h->vals + (off*h->v_sz), v, h->v_sz);
    105             hashmap_kcpy(h, h->keys + (off*h->k_sz), k);
    106             return 0x0;
    107         }
    108         if (off == h->len-1 && looped_once == 0) {
    109             off = 0;
    110             looped_once = 1;
    111         }
    112     }
    113     // Hashmap is full (this shouldn't ever happen unless default size is really low)
    114     return 0x1;
    115 }
    116 
    117 // Returns address of value if it exists, otherwise returns 0x0 for no value or 0x1 for full table.
    118 void* hashmap_get(hashmap_t* h, void* k) {
    119     size_t index = hash(k, h->vark ? strlen(k) : h->k_sz) % h->len;
    120     int looped_once = 0;
    121     // Start at the hashed index, iterate until wanted key or empty key is found
    122     for (size_t off = index; off < h->len; off+=1) {
    123         // Check if entry has correct key
    124         if (hashmap_kcmp(h, h->keys + (off * h->k_sz), k) == 0) {
    125             return h->vals + (off * h->v_sz);
    126         }
    127         // Empty entry means this key doesn't exist: return not found
    128         if (iszero(h->keys + (off * h->k_sz), h->k_sz)) {
    129             return 0x0;
    130         }
    131         // If we hit the end, wrap back around the hashmap. Only do this once.
    132         if (off == h->len-1 && looped_once == 0) {
    133             off = 0;
    134             looped_once = 1;
    135         }
    136     }
    137     return 0x0; // unreachable?
    138 }
    139 
    140 int hashmap_del(hashmap_t* h, void* k) {
    141     size_t index = hash(k, h->vark ? strlen(k) : h->k_sz) % h->len;
    142     int looped_once = 0;
    143     for (size_t off = index; off < h->len; off+=1) {
    144         if (hashmap_kcmp(h, h->keys + (off * h->k_sz), k) == 0) {
    145         memset(h->keys + (off * h->k_sz), 0, h->k_sz);
    146             // No reason to delete value, will just be overwritten next time.
    147             return 0;
    148         }
    149         if (iszero(h->keys + (off * h->k_sz), h->k_sz)) return 1;
    150         if (off == h->len-1 && looped_once == 0) {
    151             off = 0;
    152             looped_once = 1;
    153         }
    154     }
    155     return 1;
    156 }
    157 
    158 void hashmap_free(hashmap_t* h) {
    159     free(h->keys);
    160     free(h->vals);
    161 }
    162 
    163 #ifdef TEST
    164 #include "../test.h"
    165 
    166 void test_hashmap_sanity() {
    167     hashmap_t h = hashmap_new(16, 64);
    168     hashmap_set(&h, "whoo", "This is a test.");
    169     hashmap_set(&h, "Accept", "image/png,text/plain;q=0.5,image/jpeg");
    170     hashmap_resize(&h); // make sure it doesn't explode
    171     hashmap_set(&h, "Blah blah", "This is (another) test.");
    172     assert_str_eq(hashmap_get(&h, "whoo"), "This is a test.");
    173     assert_str_eq(hashmap_get(&h, "Accept"), "image/png,text/plain;q=0.5,image/jpeg");
    174     assert_str_eq(hashmap_get(&h, "Blah blah"), "This is (another) test.");
    175     hashmap_free(&h);
    176 }
    177 
    178 void test_hashmap_del() {
    179     hashmap_t h = hashmap_new(16, 64);
    180     assert_int_eq(hashmap_del(&h, "abc"), 1);
    181     assert_int_eq(hashmap_set(&h, "abc", "whoo"), 0);
    182     assert_int_eq(hashmap_del(&h, "abc"), 0);
    183     assert_int_eq((long)hashmap_get(&h, "abc"), 0);
    184 }
    185 
    186 #endif