I am still working on basic data structures for C. Now I came up with a hash map:
unordered_map.h:
#ifndef UNORDERED_MAP_H
#define UNORDERED_MAP_H
#include <stdlib.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct unordered_map_t unordered_map_t;
typedef struct unordered_map_iterator_t unordered_map_iterator_t;
/***************************************************************************
* Allocates a new, empty map with given comparator function. *
***************************************************************************/
unordered_map_t* unordered_map_t_alloc
(size_t initial_capacity,
float load_factor,
size_t (*p_hash_function)(void*),
bool (*p_equals_function)(void*, void*));
/***************************************************************************
* If p_map contains the key p_key, associates it with value p_value and *
* returns the old value of that key. *
***************************************************************************/
void* unordered_map_t_put (unordered_map_t* p_map,
void* p_key,
void* p_value);
/***************************************************************************
* Returns a positive value if p_key is mapped to some value in this map. *
***************************************************************************/
bool unordered_map_t_contains_key (unordered_map_t* p_map,
void* p_key);
/***************************************************************************
* Returns the value associated with the p_key, or NULL if p_key is not *
* mapped in the map. *
***************************************************************************/
void* unordered_map_t_get (unordered_map_t* p_map,
void* p_key);
/***************************************************************************
* If p_key is mapped in the map, removes the mapping and returns the value *
* of that mapping. If the map did not contain the mapping, return NULL. *
***************************************************************************/
void* unordered_map_t_remove (unordered_map_t* p_map,
void* p_key);
/***************************************************************************
* Removes all the contents of the map. *
***************************************************************************/
void unordered_map_t_clear (unordered_map_t* p_map);
/***************************************************************************
* Returns the size of the map, or namely, the amount of key/value mappings *
* in the map. *
***************************************************************************/
int unordered_map_t_size (unordered_map_t* p_map);
/***************************************************************************
* Checks that the map maintains the AVL-tree invariant. *
***************************************************************************/
bool unordered_map_t_is_healthy (unordered_map_t* p_map);
/***************************************************************************
* Deallocates the entire map. Only the map and its nodes are deallocated. *
* The user is responsible to deallocate the actual data stored in the map. *
***************************************************************************/
void unordered_map_t_free (unordered_map_t* p_map);
/***************************************************************************
* Returns the iterator over the map. The entries are iterated in order. *
***************************************************************************/
unordered_map_iterator_t* unordered_map_iterator_t_alloc
(unordered_map_t* p_map);
/***************************************************************************
* Returns the number of keys not yet iterated over. *
***************************************************************************/
int unordered_map_iterator_t_has_next(unordered_map_iterator_t* p_iterator);
/***************************************************************************
* Returns the next entry in the iteration order. *
***************************************************************************/
bool unordered_map_iterator_t_next(unordered_map_iterator_t* p_iterator,
void** pp_key,
void** pp_value);
/***************************************************************************
* Returns a positive integer if the map was modified during the iteration. *
***************************************************************************/
bool unordered_map_iterator_t_is_disturbed
(unordered_map_iterator_t* p_iterator);
/***************************************************************************
* Deallocates the map iterator. *
***************************************************************************/
void unordered_map_iterator_t_free(unordered_map_iterator_t* p_iterator);
#ifdef __cplusplus
}
#endif
#endif /* UNORDERED_MAP_H */
unordered_map.c:
#include "unordered_map.h"
#include <stdbool.h>
#include <stdlib.h>
typedef struct unordered_map_entry_t {
void* p_key;
void* p_value;
struct unordered_map_entry_t* p_chain_next;
struct unordered_map_entry_t* p_prev;
struct unordered_map_entry_t* p_next;
} unordered_map_entry_t;
typedef struct unordered_map_t {
unordered_map_entry_t** p_table;
unordered_map_entry_t* p_head;
unordered_map_entry_t* p_tail;
size_t (*p_hash_function)(void*);
bool (*p_equals_function)(void*, void*);
size_t mod_count;
size_t table_capacity;
size_t size;
size_t mask;
float load_factor;
} unordered_map_t;
typedef struct unordered_map_iterator_t {
unordered_map_t* p_map;
unordered_map_entry_t* p_next_entry;
size_t iterated_count;
size_t expected_mod_count;
} unordered_map_iterator_t;
static unordered_map_entry_t* unordered_map_entry_t_alloc(void* p_key,
void* p_value)
{
unordered_map_entry_t* p_ret = malloc(sizeof(*p_ret));
if (!p_ret) return NULL;
p_ret->p_key = p_key;
p_ret->p_value = p_value;
p_ret->p_chain_next = NULL;
p_ret->p_next = NULL;
p_ret->p_prev = NULL;
return p_ret;
}
static const float MINIMUM_LOAD_FACTOR = 0.2f;
static const size_t MINIMUM_INITIAL_CAPACITY = 16;
static float maxf(float a, float b)
{
return a < b ? b : a;
}
static int maxi(int a, int b)
{
return a < b ? b : a;
}
/*******************************************************************************
* Makes sure that the load factor is no less than a minimum threshold. *
*******************************************************************************/
static float fix_load_factor(float load_factor)
{
return maxf(load_factor, MINIMUM_LOAD_FACTOR);
}
/*******************************************************************************
* Makes sure that the initial capacity is no less than a minimum allowed and *
* is a power of two. *
*******************************************************************************/
static size_t fix_initial_capacity(size_t initial_capacity)
{
size_t ret;
initial_capacity = maxi(initial_capacity, MINIMUM_INITIAL_CAPACITY);
ret = 1;
while (ret < initial_capacity) ret <<= 1;
return ret;
}
unordered_map_t* unordered_map_t_alloc(size_t initial_capacity,
float load_factor,
size_t (*p_hash_function)(void*),
bool (*p_equals_function)(void*, void*))
{
unordered_map_t* p_ret;
if (!p_hash_function) return NULL;
if (!p_equals_function) return NULL;
p_ret = malloc(sizeof(*p_ret));
if (!p_ret) return NULL;
load_factor = fix_load_factor(load_factor);
initial_capacity = fix_initial_capacity(initial_capacity);
p_ret->load_factor = load_factor;
p_ret->table_capacity = initial_capacity;
p_ret->size = 0;
p_ret->mod_count = 0;
p_ret->p_head = NULL;
p_ret->p_tail = NULL;
p_ret->p_table = calloc(initial_capacity,
sizeof(unordered_map_entry_t*));
p_ret->p_hash_function = p_hash_function;
p_ret->p_equals_function = p_equals_function;
p_ret->mask = initial_capacity - 1;
return p_ret;
}
static void ensure_capacity(unordered_map_t* p_map)
{
size_t new_capacity;
size_t new_mask;
size_t index;
unordered_map_entry_t* p_entry;
unordered_map_entry_t** p_new_table;
if (p_map->size <= p_map->load_factor * p_map->table_capacity) return;
new_capacity = 2 * p_map->table_capacity;
new_mask = new_capacity - 1;
p_new_table = calloc(new_capacity, sizeof(unordered_map_entry_t*));
if (!p_new_table) return;
/* Rehash the entries. */
for (p_entry = p_map->p_head; p_entry; p_entry = p_entry->p_next)
{
index = p_map->p_hash_function(p_entry->p_key) & new_mask;
p_entry->p_chain_next = p_new_table[index];
p_new_table[index] = p_entry;
}
free(p_map->p_table);
p_map->p_table = p_new_table;
p_map->table_capacity = new_capacity;
p_map->mask = new_mask;
}
void* unordered_map_t_put(unordered_map_t* p_map, void* p_key, void* p_value)
{
size_t index;
void* p_old_value;
unordered_map_entry_t* p_entry;
if (!p_map) return NULL;
index = p_map->p_hash_function(p_key) & p_map->mask;
for (p_entry = p_map->p_table[index];
p_entry;
p_entry = p_entry->p_chain_next)
{
if (p_map->p_equals_function(p_entry->p_key, p_key))
{
p_old_value = p_entry->p_value;
p_entry->p_value = p_value;
return p_old_value;
}
}
ensure_capacity(p_map);
/* Recompute the index since it is possibly changed by 'ensure_capacity' */
index = p_map->p_hash_function(p_key) & p_map->mask;
p_entry = unordered_map_entry_t_alloc(p_key, p_value);
p_entry->p_chain_next = p_map->p_table[index];
p_map->p_table[index] = p_entry;
/* Link the new entry to the tail of the list. */
if (!p_map->p_tail)
{
p_map->p_head = p_entry;
p_map->p_tail = p_entry;
}
else
{
p_map->p_tail->p_next = p_entry;
p_entry->p_prev = p_map->p_tail;
p_map->p_tail = p_entry;
}
p_map->size++;
p_map->mod_count++;
return NULL;
}
bool unordered_map_t_contains_key(unordered_map_t* p_map, void* p_key)
{
size_t index;
unordered_map_entry_t* p_entry;
if (!p_map) return false;
index = p_map->p_hash_function(p_key) & p_map->mask;
for (p_entry = p_map->p_table[index];
p_entry;
p_entry = p_entry->p_chain_next)
{
if (p_map->p_equals_function(p_key, p_entry->p_key)) return true;
}
return false;
}
void* unordered_map_t_get(unordered_map_t* p_map, void* p_key)
{
size_t index;
unordered_map_entry_t* p_entry;
if (!p_map) return NULL;
index = p_map->p_hash_function(p_key) & p_map->mask;
for (p_entry = p_map->p_table[index];
p_entry;
p_entry = p_entry->p_chain_next)
{
if (p_map->p_equals_function(p_key, p_entry->p_key))
return p_entry->p_value;
}
return NULL;
}
void* unordered_map_t_remove(unordered_map_t* p_map, void* p_key)
{
void* p_ret;
size_t index;
unordered_map_entry_t* p_prev_entry;
unordered_map_entry_t* p_current_entry;
if (!p_map) return NULL;
index = p_map->p_hash_function(p_key) & p_map->mask;
p_prev_entry = NULL;
for (p_current_entry = p_map->p_table[index];
p_current_entry;
p_current_entry = p_current_entry->p_chain_next)
{
if (p_map->p_equals_function(p_key, p_current_entry->p_key))
{
if (p_prev_entry)
{
/* Omit the 'p_current_entry' in the collision chain. */
p_prev_entry->p_chain_next = p_current_entry->p_chain_next;
}
else
{
// Here?
p_map->p_table[index] = p_current_entry->p_chain_next;
}
/* Unlink from the global iteration chain. */
if (p_current_entry->p_prev && p_current_entry->p_next)
{
/* Once here, the current entry has both next and previous. */
p_current_entry->p_prev->p_next = p_current_entry->p_next;
p_current_entry->p_next->p_prev = p_current_entry->p_prev;
}
else if (!p_current_entry->p_prev && !p_current_entry->p_next)
{
/* Once here, the current entry
is the only entry in the chain. */
p_map->p_head = NULL;
p_map->p_tail = NULL;
}
else if (p_current_entry->p_next)
{
/* Once here, the current entry is the head of the chain. */
p_map->p_head = p_current_entry->p_next;
p_map->p_head->p_prev = NULL;
}
else
{
/* Once here, the current entry is the tail of the chain. */
p_map->p_tail = p_current_entry->p_prev;
p_map->p_tail->p_next = NULL;
}
p_ret = p_current_entry->p_value;
p_map->size--;
p_map->mod_count++;
free(p_current_entry);
return p_ret;
}
p_prev_entry = p_current_entry;
}
return NULL;
}
void unordered_map_t_clear(unordered_map_t* p_map)
{
unordered_map_entry_t* p_entry;
unordered_map_entry_t* p_next_entry;
size_t index;
if (!p_map) return;
p_entry = p_map->p_head;
while (p_entry)
{
index = p_map->p_hash_function(p_entry->p_key) & p_map->mask;
p_next_entry = p_entry->p_next;
free(p_entry);
p_entry = p_next_entry;
if (p_map->p_table[index])
{
p_map->p_table[index] = p_map->p_table[index]->p_chain_next;
}
}
p_map->mod_count += p_map->size;
p_map->size = 0;
p_map->p_head = NULL;
p_map->p_tail = NULL;
}
int unordered_map_t_size(unordered_map_t* p_map)
{
return p_map ? p_map->size : -1;
}
bool unordered_map_t_is_healthy(unordered_map_t* p_map)
{
size_t counter;
unordered_map_entry_t* p_entry;
if (!p_map) return false;
counter = 0;
p_entry = p_map->p_head;
if (p_entry && p_entry->p_prev) return false;
for (; p_entry; p_entry = p_entry->p_next)
{
counter++;
}
return counter == p_map->size;
}
void unordered_map_t_free(unordered_map_t* p_map)
{
if (!p_map) return;
unordered_map_t_clear(p_map);
free(p_map->p_table);
free(p_map);
}
unordered_map_iterator_t*
unordered_map_iterator_t_alloc(unordered_map_t* p_map)
{
unordered_map_iterator_t* p_ret;
if (!p_map) return NULL;
p_ret = malloc(sizeof(*p_ret));
if (!p_ret) return NULL;
p_ret->p_map = p_map;
p_ret->iterated_count = 0;
p_ret->p_next_entry = p_map->p_head;
p_ret->expected_mod_count = p_map->mod_count;
return p_ret;
}
int unordered_map_iterator_t_has_next(unordered_map_iterator_t* p_iterator)
{
if (!p_iterator) return 0;
if (unordered_map_iterator_t_is_disturbed(p_iterator)) return 0;
return p_iterator->p_map->size - p_iterator->iterated_count;
}
bool unordered_map_iterator_t_next(unordered_map_iterator_t* p_iterator,
void** pp_key,
void** pp_value)
{
if (!p_iterator) return false;
if (!p_iterator->p_next_entry) return false;
if (unordered_map_iterator_t_is_disturbed(p_iterator)) return false;
*pp_key = p_iterator->p_next_entry->p_key;
*pp_value = p_iterator->p_next_entry->p_value;
p_iterator->iterated_count++;
p_iterator->p_next_entry = p_iterator->p_next_entry->p_next;
return true;
}
bool
unordered_map_iterator_t_is_disturbed(unordered_map_iterator_t* p_iterator)
{
if (!p_iterator) false;
return p_iterator->expected_mod_count != p_iterator->p_map->mod_count;
}
void unordered_map_iterator_t_free(unordered_map_iterator_t* p_iterator)
{
if (!p_iterator) return;
p_iterator->p_map = NULL;
p_iterator->p_next_entry = NULL;
free(p_iterator);
}
Demonstration driver with other stuff may be found in coderodde.c.utils.
Is there anything I could do in order to improve the data structure?