/* Copyright (C) 2014-2015 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ #include #include #include "hash_table.h" #include "../util.h" #define INITIAL_BUCKETS 127 #define GET_FIELD_(obj, offset, type) (*((type*) (((char*) obj) + offset))) #define NODE_KEY(tab, node) GET_FIELD_(node, tab->key_offset, char*) #define NEXT_NODE(tab, node) GET_FIELD_(node, tab->next_offset, HashNode*) /* Internal structs */ struct HashNode { char *key; HashNode *next; }; /* Hash a string key into a hash table bucket index. This uses the djb2 algorithm: http://www.cse.yorku.ca/~oz/hash.html */ static inline size_t hash_key( const HashTable *table, const char *key, ssize_t size) { size_t hash = 5381; while ((size < 0 || size-- > 0) && *key) hash = ((hash << 5) + hash) + *(key++); return hash % table->buckets; } /* Return true if the two key strings are equal, or false if they aren't. */ static inline bool keyeq(const char *s1, const char *s2, ssize_t size) { return !(size >= 0 ? strncmp(s1, s2, size) : strcmp(s1, s2)); } /* Create and return a new HashTable. These HashTables are designed to be generic, a sort of poor-man's C++ template. They can store any kind of node data as long as they are structs with a char* field (for the node key), and a pointer to its own type (to implement separate chaining). key_offset is the (byte) offset of the key field, and next_offset is the offset of the self-pointer. The callback function is called on a node when it is removed from the table. Typically, it should free() the node's members and then free() the node itself. The hash_table_NEW macro can be used to call this function more easily. */ HashTable* hash_table_new( size_t key_offset, size_t next_offset, HashFreeCallback callback) { HashTable *table = cr_malloc(sizeof(HashTable)); table->nodes = cr_calloc(INITIAL_BUCKETS, sizeof(HashNode*)); table->buckets = INITIAL_BUCKETS; table->key_offset = key_offset; table->next_offset = next_offset; table->free = callback; return table; } /* Deallocate a HashTable. This function does nothing if the table is NULL. */ void hash_table_free(HashTable *table) { if (!table) return; for (size_t bucket = 0; bucket < table->buckets; bucket++) { HashNode *node = table->nodes[bucket]; while (node) { HashNode *temp = NEXT_NODE(table, node); table->free(node); node = temp; } } free(table); } /* Search for a key in the hash table. Return the corresponding node on success and NULL on failure. If the key is null-terminated, pass size as -1. */ const HashNode* hash_table_find( const HashTable *table, const char *key, ssize_t size) { HashNode *node = table->nodes[hash_key(table, key, size)]; while (node) { if (keyeq(key, NODE_KEY(table, node), size)) return node; node = NEXT_NODE(table, node); } return NULL; } /* Insert a node into the table. This doesn't prevent inserting duplicate keys. If a duplicate key is inserted, the table acts like a stack; the new one will shadow the old one, and hash_table_remove() will remove the most-recently inserted key to reveal the second-most recent. */ void hash_table_insert(HashTable *table, HashNode *node) { size_t index = hash_key(table, NODE_KEY(table, node), -1); NEXT_NODE(table, node) = table->nodes[index]; table->nodes[index] = node; } /* (Try to) remove a node with the given key from the table. Return true if the node was removed, or false if it was not found. If the key is null-terminated, pass size as -1. */ bool hash_table_remove(HashTable *table, const char *key, ssize_t size) { size_t index = hash_key(table, key, size); HashNode *node = table->nodes[index], *prev = NULL, *next; while (node) { next = NEXT_NODE(table, node); if (keyeq(key, NODE_KEY(table, node), size)) { if (prev) NEXT_NODE(table, prev) = next; else table->nodes[index] = next; table->free(node); return true; } prev = node; node = next; } return false; }