add string hashmap
This commit is contained in:
parent
640046ee1e
commit
2bd12a392a
177
hashtable.c
Normal file
177
hashtable.c
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* hashtable.c
|
||||
*
|
||||
* Created on: 24.10.2017
|
||||
* Author: julian
|
||||
*/
|
||||
|
||||
#include "strhash.h"
|
||||
#include "hashtable.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
int key_compare(entry_key_t one, entry_key_t two) {
|
||||
return strcmp(one, two) == 0;
|
||||
}
|
||||
|
||||
entry_hash_t key_hash(entry_key_t key) {
|
||||
return hash_str(key) + 1; // make hash always nonzero
|
||||
}
|
||||
|
||||
hashtable_iterator_t hashtable_end(struct hashtable * table) {
|
||||
return table->data + table->len;
|
||||
}
|
||||
|
||||
|
||||
hashtable_iterator_t hashtable_next(struct hashtable * table, hashtable_iterator_t current) {
|
||||
if (table == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (current == NULL) {
|
||||
current = table->data;
|
||||
} else {
|
||||
// we want the NEXT pointer :)
|
||||
current++;
|
||||
}
|
||||
|
||||
while(current != hashtable_end(table)) {
|
||||
if (current->hash != 0)
|
||||
break;
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
hashtable_iterator_t hashtable_get_hash(struct hashtable * table, entry_hash_t hash) {
|
||||
size_t index = hash % table->len;
|
||||
|
||||
if (table->data[index].hash == 0) {
|
||||
return hashtable_end(table);
|
||||
}
|
||||
|
||||
return table->data + index;
|
||||
}
|
||||
|
||||
hashtable_iterator_t hashtable_get(struct hashtable * table, entry_key_t key) {
|
||||
entry_hash_t hash = key_hash(key);
|
||||
return hashtable_get_hash(table, hash);
|
||||
}
|
||||
|
||||
void hashtable_clear(struct hashtable * table) {
|
||||
if (table->dealloc_data) {
|
||||
hashtable_iterator_t it = hashtable_next(table, NULL);
|
||||
for(;it != hashtable_end(table); it = hashtable_next(table, it)){
|
||||
table->dealloc_data(it->data);
|
||||
}
|
||||
}
|
||||
|
||||
free(table->data);
|
||||
table->data = NULL;
|
||||
table->count = table->len = 0;
|
||||
}
|
||||
|
||||
int hashtable_resize(struct hashtable * table, size_t newsize) {
|
||||
if (table == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (newsize == 0) {
|
||||
// will fail, correct to 1
|
||||
newsize = 1;
|
||||
}
|
||||
|
||||
struct hashtable temp = *table;
|
||||
table->data = calloc(newsize, sizeof(struct entry));
|
||||
|
||||
if (table->data == NULL) {
|
||||
table->data = temp.data;
|
||||
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// try to reinsert old data
|
||||
table->len = newsize;
|
||||
|
||||
for (size_t i = 0; i < temp.len; i++) {
|
||||
if (temp.data[i].hash && hashtable_add(table, temp.data[i]) == -1) {
|
||||
// abort mission, restore old table
|
||||
free(table->data);
|
||||
*table = temp;
|
||||
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
// delete old table
|
||||
free(temp.data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hashtable_add(struct hashtable * table, struct entry entry) {
|
||||
if (table == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (table->len == 0) {
|
||||
// initial alloc
|
||||
int err = hashtable_resize(table, 1);
|
||||
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
return hashtable_add(table, entry);
|
||||
}
|
||||
|
||||
|
||||
// try to insert into table
|
||||
size_t index = entry.hash % table->len;
|
||||
|
||||
if (table->data->hash && !key_compare(table->data[index].key, entry.key)) {
|
||||
// key collision
|
||||
// make table bigger
|
||||
if (hashtable_resize(table, table->len*2) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return hashtable_add(table, entry);
|
||||
}
|
||||
|
||||
// insert new entry
|
||||
table->data[index] = entry;
|
||||
table->count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct entry hashtable_make_entry(entry_key_t key, void * data) {
|
||||
return (struct entry){ .data = data,
|
||||
.key = key,
|
||||
.hash = key_hash(key),
|
||||
};
|
||||
}
|
||||
|
||||
int hashtable_remove(struct hashtable * table, hashtable_iterator_t it) {
|
||||
if (!it || it->hash == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (table->dealloc_data) {
|
||||
table->dealloc_data(it->data);
|
||||
}
|
||||
|
||||
it->data = NULL;
|
||||
it->hash = 0;
|
||||
table->count--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
117
hashtable.h
Normal file
117
hashtable.h
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* hashtable.h
|
||||
*
|
||||
* Created on: 21.10.2017
|
||||
* Author: julian
|
||||
*/
|
||||
|
||||
#ifndef HASHTABLE_H_
|
||||
#define HASHTABLE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "strhash.h"
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
typedef strhash_t entry_hash_t;
|
||||
typedef const char * entry_key_t;
|
||||
typedef struct entry * hashtable_iterator_t;
|
||||
|
||||
// data deallocator
|
||||
typedef void(*hashtable_dealloc)(void*);
|
||||
// data copy and allocator
|
||||
typedef void(*hashtable_alloc)(void*);
|
||||
|
||||
extern int key_compare(entry_key_t one, entry_key_t two);
|
||||
extern entry_hash_t key_hash(entry_key_t key);
|
||||
|
||||
struct entry {
|
||||
entry_hash_t hash;
|
||||
entry_key_t key;
|
||||
void * data;
|
||||
};
|
||||
|
||||
struct hashtable {
|
||||
struct entry * data;
|
||||
size_t len, count;
|
||||
|
||||
hashtable_alloc alloc_data;
|
||||
hashtable_dealloc dealloc_data;
|
||||
};
|
||||
|
||||
extern hashtable_iterator_t hashtable_end(struct hashtable * table);
|
||||
|
||||
/**
|
||||
* Iterate of the hashmap.
|
||||
*
|
||||
* pass the return value to current to iterate over the entire map
|
||||
* pass NULL to get the beginning
|
||||
*
|
||||
* returns the next set entry in the table from current
|
||||
* returns hashtable_end() when there is nons
|
||||
* example loop:
|
||||
* hashmap_iterator_t current = hashtable_next(table, NULL);
|
||||
* for(;current != hashtable_end(table); current = hashtable_next(table, current)) {}
|
||||
*
|
||||
*/
|
||||
hashtable_iterator_t hashtable_next(struct hashtable * table, hashtable_iterator_t current);
|
||||
|
||||
/**
|
||||
* returns the entry identified by the given hash
|
||||
* returns hashtable_end() if no entry matched
|
||||
*/
|
||||
hashtable_iterator_t hashtable_get_hash(struct hashtable * table, entry_hash_t hash);
|
||||
|
||||
/**
|
||||
* returns the entry identified by the given key
|
||||
* returns hashtable_end() if no entry matched
|
||||
*/
|
||||
hashtable_iterator_t hashtable_get(struct hashtable * table, entry_key_t key);
|
||||
|
||||
/**
|
||||
* delete all entries of the given table
|
||||
*
|
||||
* calls dealloc of the table for every data element, if it was set.
|
||||
*/
|
||||
void hashtable_clear(struct hashtable * table);
|
||||
|
||||
/**
|
||||
* insert a new element in the table
|
||||
*/
|
||||
int hashtable_add(struct hashtable * table, struct entry entry);
|
||||
|
||||
/**
|
||||
* resize the hashtable to a new physical size
|
||||
* can also try to make the hashtable smaller
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on error (sets errno)
|
||||
* returns -2 if hashtable does not fit in new size.
|
||||
*/
|
||||
int hashtable_resize(struct hashtable * table, size_t newsize);
|
||||
|
||||
/**
|
||||
* add new element in hashtable
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on error
|
||||
*/
|
||||
int hashtable_add(struct hashtable * table, struct entry entry);
|
||||
|
||||
/**
|
||||
* make a new entry that can be added out of key and data
|
||||
*/
|
||||
struct entry hashtable_make_entry(entry_key_t key, void * data);
|
||||
|
||||
/**
|
||||
* remove one entry using the key
|
||||
* @return -1 on error
|
||||
* @return 1 on success
|
||||
* @return 0 on not found
|
||||
*/
|
||||
int hashtable_remove(struct hashtable * table, hashtable_iterator_t it);
|
||||
|
||||
#endif /* HASHTABLE_H_ */
|
28
strhash.c
Normal file
28
strhash.c
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* strhash.c
|
||||
*
|
||||
* Created on: 21.10.2017
|
||||
* Author: julian
|
||||
*/
|
||||
|
||||
#include "strhash.h"
|
||||
|
||||
// use rule:
|
||||
// hash(i) = hash(i - 1) * 33 ^ str[i]
|
||||
// see: http://www.cse.yorku.ca/~oz/hash.html
|
||||
strhash_t hash_add(strhash_t hash, char c) {
|
||||
return hash * 33 ^ c;
|
||||
}
|
||||
|
||||
|
||||
strhash_t hash_str(const char * str) {
|
||||
strhash_t hash = 0;
|
||||
while(*str) {
|
||||
hash = hash_add(hash, *str);
|
||||
str++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
23
strhash.h
Normal file
23
strhash.h
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* strhash.h
|
||||
*
|
||||
* Created on: 21.10.2017
|
||||
* Author: julian
|
||||
*/
|
||||
|
||||
#ifndef STRHASH_H_
|
||||
#define STRHASH_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef size_t strhash_t;
|
||||
|
||||
// "rehash" string with new char added to end
|
||||
strhash_t hash_add(strhash_t hash, char c);
|
||||
|
||||
/// hash a given string
|
||||
strhash_t hash_str(const char * str);
|
||||
|
||||
|
||||
|
||||
#endif /* STRHASH_H_ */
|
137
test_hashtable.c
Normal file
137
test_hashtable.c
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* test_hashtable.c
|
||||
*
|
||||
* Created on: 21.10.2017
|
||||
* Author: julian
|
||||
*/
|
||||
|
||||
#include "tests.h"
|
||||
#include "hashtable.h"
|
||||
|
||||
struct hashtable table = {};
|
||||
struct entry nEntry;
|
||||
|
||||
|
||||
void testresize() {
|
||||
init("resize");
|
||||
int err = hashtable_resize(&table, 20);
|
||||
if (err != 0) {
|
||||
fail("resize return code was error %d", err);
|
||||
}
|
||||
if (table.len != 20) {
|
||||
fail("table has wrong size %d", table.len);
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void testadd() {
|
||||
init("add");
|
||||
nEntry = hashtable_make_entry("hi", "20");
|
||||
size_t count = table.count;
|
||||
|
||||
int err = hashtable_add(&table, nEntry);
|
||||
|
||||
if (err != 0) {
|
||||
fail("add gave error %d", err);
|
||||
}
|
||||
if (table.count != count +1) {
|
||||
fail("table has wrong count (has %d, needs %d)", table.count, count+1);
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void testget() {
|
||||
init("get");
|
||||
|
||||
hashtable_iterator_t elem = hashtable_get(&table, "hi");
|
||||
|
||||
if (elem == hashtable_end(&table)) {
|
||||
fail("element was not found");
|
||||
}
|
||||
|
||||
if (elem->data != nEntry.data) {
|
||||
fail("returned wrong element");
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void testiterate() {
|
||||
init("iterate");
|
||||
|
||||
hashtable_iterator_t it = hashtable_next(&table, NULL);
|
||||
|
||||
if (it == hashtable_end(&table)) {
|
||||
fail("table seems empty?");
|
||||
}
|
||||
|
||||
if (strcmp(it->key, "hi")) {
|
||||
fail("wrong entry (smh)");
|
||||
}
|
||||
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
|
||||
volatile int dealloc_called = 0;
|
||||
|
||||
void test_dealloc(void* data) {
|
||||
dealloc_called++;
|
||||
}
|
||||
|
||||
void testremove() {
|
||||
init("remove");
|
||||
dealloc_called = 0;
|
||||
table.dealloc_data = test_dealloc;
|
||||
|
||||
if (hashtable_add(&table, hashtable_make_entry("woop", NULL)) < 0) {
|
||||
fail("could not add");
|
||||
}
|
||||
|
||||
hashtable_iterator_t it = hashtable_get(&table, "woop");
|
||||
int ret = hashtable_remove(&table, it);
|
||||
|
||||
if (ret < 0) {
|
||||
fail("negative return code");
|
||||
}
|
||||
if (!ret) {
|
||||
fail("could not remove");
|
||||
}
|
||||
if (!dealloc_called) {
|
||||
fail("deallocator not called");
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
void testclear() {
|
||||
init("clear");
|
||||
|
||||
size_t count = table.count;
|
||||
table.dealloc_data = test_dealloc;
|
||||
|
||||
hashtable_clear(&table);
|
||||
if (table.count != 0 || table.len != 0 || table.data != NULL) {
|
||||
fail("memory was not freed");
|
||||
}
|
||||
|
||||
if (dealloc_called != count) {
|
||||
fail("dealloc was not called for all the data");
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
int main() {
|
||||
init("hashtable");
|
||||
testresize();
|
||||
testadd();
|
||||
testget();
|
||||
testiterate();
|
||||
testclear();
|
||||
pass();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user