Skip to content

Commit

Permalink
Hash fixed, tested, and commented
Browse files Browse the repository at this point in the history
  • Loading branch information
NicoBliss committed Jul 8, 2024
1 parent ec635f9 commit d0aa0ee
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 22 deletions.
50 changes: 40 additions & 10 deletions src/util/hash.c
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#include <stdlib.h>
#include <string.h>
#include "out.h"
#include "hash.h"
#include "out.h"

const uint64_t OFFSET = 14695981039346656037;
const uint64_t FNV_PRIME = 1099511628211;

hashMap* hm_init(int num_buckets) {
hashMap* hm = malloc(sizeof(hashMap));
HashMap* hm_init(int num_buckets) {
HashMap* hm = malloc(sizeof(HashMap));

// bounce if out of memory
if (hm == NULL) {
Expand All @@ -18,7 +18,7 @@ hashMap* hm_init(int num_buckets) {
hm->stored = 0;
hm->capacity = num_buckets;

hm->entries = calloc(hm->capacity, sizeof(hashNode));
hm->entries = calloc(hm->capacity, sizeof(HashNode));

// bounce if out of memory
if (hm->entries == NULL) {
Expand All @@ -30,7 +30,7 @@ hashMap* hm_init(int num_buckets) {
return hm;
}

void hm_free(hashMap *hm) {
void hm_free(HashMap *hm) {
for (int i = 0; i<hm->capacity; i++) {
// free all the chars allocated as keys
free(hm->entries[i].key);
Expand All @@ -49,11 +49,15 @@ uint64_t hash(const char* key) {
return hash;
}

void* hm_lookup(hashMap *hm, const char* key) {
void* hm_lookup(HashMap *hm, const char* key) {
uint64_t ind = hash(key) % hm->capacity;

for (int i = 0; i < hm->capacity-1; i++) {
if (strcmp(key, hm->entries[ind].key) == 0) {
// since removed entries aren't actually cleared, if you run
// into a null key, what you're looking for was never added
if (hm->entries[ind].key == NULL) {
return NULL;
} else if (strcmp(key, hm->entries[ind].key) == 0) {
return hm->entries[ind].value;
}

Expand All @@ -66,12 +70,38 @@ void* hm_lookup(hashMap *hm, const char* key) {
return NULL;
}

int hm_set(hashMap *hm, const char *key, void *value) {
int hm_set(HashMap *hm, const char *key, void *value) {
uint64_t ind = hash(key) % hm->capacity;

for (int i = 0; i < hm->capacity-1; i++) {
if (hm->entries[ind].key == NULL) {
// Values are allowed to be overwritten!
if ((hm->entries[ind].key == NULL) || (strcmp(key, hm->entries[ind].key) == 0)) {
hm->entries[ind].value = value;
hm->entries[ind].key = key;
return 1;
}

ind++;
if (ind >= hm->capacity) {
ind = 0;
}
}

PRINT_WARNING("hashmap full!");
return 0;
}

int hm_remove(HashMap *hm, const char *key) {
uint64_t ind = hash(key) % hm->capacity;

for (int i = 0; i < hm->capacity-1; i++) {
if (hm->entries[ind].key == NULL) {
return 0;
} else if (strcmp(key, hm->entries[ind].key) == 0) {
// It just NULL's out the value--this can be written again.
// that makes hm_lookup return the same thing for a removed
// item as a non-existent one, though, which is the goal
hm->entries[ind].value = NULL;
return 1;
}

Expand All @@ -81,5 +111,5 @@ int hm_set(hashMap *hm, const char *key, void *value) {
}
}

return -1;
return 0;
}
26 changes: 14 additions & 12 deletions src/util/hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,31 @@
typedef struct node {
void* value;
// the key is NULL if the node is empty
char* key;
} hashNode;
const char* key;
} HashNode;

typedef struct {
int stored; // # of items in hashtable
int capacity; // # of entries that can fit in the table
hashNode* entries;// collection of entries
} hashMap;
HashNode* entries;// collection of entries
} HashMap;

// initialize. returns pointer to the hashMap
hashMap* hm_init(int num_buckets);
// initialize. returns pointer to the HashMap
HashMap* hm_init(int num_buckets);

// free memory
void hm_free(hashMap *hm);
void hm_free(HashMap *hm);

// find element
void* hm_lookup(hashMap *hm, const char* key);
void* hm_lookup(HashMap *hm, const char* key);

// add element
int hm_set(hashMap *hm, const char *key, void *value);
// add element. VERY IMPORTANTLY, the hashtable does *not* copy the string
// you give it as a key. If you de-allocate that string, the hashtable gets
// a bunch of undefined behavior (like seg-faulting at free)
int hm_set(HashMap *hm, const char *key, void *value);

// remove element (unimplemented)
void hm_remove(hashMap *hm, const char* key);
// remove element. DOES NOT DEALLOCATE THE SPACE FOR THE REMOVED ELEMENTS
int hm_remove(HashMap *hm, const char* key);

// hashes the keys
uint64_t hash(const char* key);
119 changes: 119 additions & 0 deletions src/util/test_hash.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "test_hash.h"
#include "../testing/tassert.h"
#include "../testing/test_utils.h"
#include "hash.h"
#include "out.h"
#include <stdlib.h>

int test_hash() {
testing_module_setup();

test_hashing();
test_init_and_free();
test_100_inserts();
test_100_removes();

testing_module_cleanup();
return 0;
}

int test_hashing() {
testing_func_setup();

char string1[] = "hello";
char string2[] = "hello";
const char *key1 = &string1;
const char *key2 = &string2;
tassert(key1 != key2);
tassert(hash(key1) == hash(key2));

return 0;
}

int test_init_and_free() {
testing_func_setup();

HashMap* table = hm_init(10000);
tassert(table->capacity == 10000);
hm_free(table);

return 0;
}

int test_minimal_case() {
testing_func_setup();

HashMap* table = hm_init(1);
const char *key = "hello";
void *value = (void *)128;

hm_set(table, key, value);
tassert(hm_lookup(table, key) == (void *)128);

hm_free(table);
return 0;
}

int test_100_inserts() {
testing_func_setup();

// Allocate the table and string array--it's important that the keys are owned
// OUTSIDE the hashtable, as it does not copy them.
HashMap* table = hm_init(100);
char **string_array = malloc(100*sizeof(char*));
for(int i = 0; i<100; i++) {
string_array[i] = malloc(sizeof(char));
}

// Add all the elements into the table
for(int i = 0; i<100; i++) {
string_array[i][0] = (char)i;
hm_set(table, string_array[i], (void *)i);
}

// Check to see if all of the elements can still be found
for(int i = 0; i<100; i++) {
tassert(hm_lookup(table, string_array[i]) == (void *)i);
}

// Free. This also frees the string_array
hm_free(table);
return 0;
}

int test_100_removes() {
testing_func_setup();

// Allocate the table and string array--it's important that the keys are owned
// OUTSIDE the hashtable, as it does not copy them.
HashMap* table = hm_init(100);
char **string_array = malloc(101*sizeof(char*));
for(int i = 0; i<101; i++) {
string_array[i] = malloc(sizeof(char));
string_array[i][0] = (char)i;
}

// Add all the elements into the table
for(int i = 0; i<100; i++) {
hm_set(table, string_array[i], (void *)i);
}

// Remove all the elements from the table
for(int i = 0; i<100; i++) {
hm_remove(table, string_array[i]);
}

// Check to see if all of the elements are removed
for(int i = 0; i<100; i++) {
tassert(hm_lookup(table, string_array[i]) == NULL);
}

// check to see that the table is still filled
tassert(hm_set(table, string_array[100], NULL) == 0);

// Free. This also frees the string_array
free(string_array[100]);
hm_free(table);

return 0;
}
14 changes: 14 additions & 0 deletions src/util/test_hash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once
#include "hash.h"

int test_hash();

int test_hashing();

int test_init_and_free();

int test_minimal_case();

int test_100_inserts();

int test_100_removes();

0 comments on commit d0aa0ee

Please sign in to comment.