From edea2a350da4abc3cf035b92e43cb4f87ff91011 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Nov 2023 13:53:08 -0500 Subject: [PATCH] Add hash map routines --- libXmd/include/Xmd/Buffer.h | 3 +- libXmd/include/Xmd/Launcher.h | 10 --- libXmd/include/Xmd/Map.h | 39 ++++++++ libXmd/src/Map.c | 161 ++++++++++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 11 deletions(-) delete mode 100644 libXmd/include/Xmd/Launcher.h create mode 100644 libXmd/include/Xmd/Map.h create mode 100644 libXmd/src/Map.c diff --git a/libXmd/include/Xmd/Buffer.h b/libXmd/include/Xmd/Buffer.h index a89f7c3..4898877 100644 --- a/libXmd/include/Xmd/Buffer.h +++ b/libXmd/include/Xmd/Buffer.h @@ -36,7 +36,8 @@ void *XmdBufferBreak (XmdBuffer *buffer); /* XmdBufferLength returns the amount of elements stored in a buffer. */ Cardinal XmdBufferLength (XmdBuffer *buffer); -/* XmdBufferFree frees the buffer and any data associated with it. */ +/* XmdBufferFree frees the buffer and any data associated with it. Note that if + the buffer contains pointers, the values they point to will not be freed. */ void XmdBufferFree (XmdBuffer *buffer); #endif diff --git a/libXmd/include/Xmd/Launcher.h b/libXmd/include/Xmd/Launcher.h deleted file mode 100644 index 5129cfa..0000000 --- a/libXmd/include/Xmd/Launcher.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef _XmdLauncher_h -#define _XmdLauncher_h - -#include -#include -#include - -/* TODO */ - -#endif diff --git a/libXmd/include/Xmd/Map.h b/libXmd/include/Xmd/Map.h new file mode 100644 index 0000000..06c6cc5 --- /dev/null +++ b/libXmd/include/Xmd/Map.h @@ -0,0 +1,39 @@ +#ifndef _XmdMap_h +#define _XmdMap_h + +#include + +/* XmdMapKey is used to key XmdMap. */ +typedef long unsigned int XmdMapKey; + +/* XmdMap is a hash map that is keyed by an integer and can store any type of + element. */ +typedef struct _XmdMap XmdMap; + +/* XmdMapIterator is a function that can iterate over a map. */ +typedef Bool (*XmdMapIterator) (XmdMap *map, XmdMapKey key, void *value); + +/* XmdMapNew creates a new map.*/ +XmdMap *XmdMapNew (); + +/* XmdMapGet retrieves a value from a map. */ +void *XmdMapGet (XmdMap *map, XmdMapKey key); + +/* XmdMapSet sets a value in a map. The previous value associated with the key, + if it exists, is overwritten by the new value. If the value is set to NULL, + the key/value combination is deleted from the map. This function returns + the previous value associated with the key so it can be free'd if necessary. + */ +void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value); + +/* XmdMapIterate calls iterator for each key/value pair in a map. */ +void XmdMapIterate (XmdMap *map, XmdMapIterator iterator); + +/* XmdMapLength returns the number of key/value entries stored in a map. */ +Cardinal XmdMapLength (XmdMap *map); + +/* XmdMapFree frees the map and any data associated with it. Note that this will + not free any of the map's values. */ +void XmdMapFree (XmdMap *map); + +#endif diff --git a/libXmd/src/Map.c b/libXmd/src/Map.c new file mode 100644 index 0000000..b29ea7b --- /dev/null +++ b/libXmd/src/Map.c @@ -0,0 +1,161 @@ +#include +#include + +#define XmdMAP_GROWTH_FACTOR 2 +#define XmdMAP_INITIAL_CAPACITY 16 + +typedef struct _XmdMapElement { + XmdMapKey key; + void *value; + struct _XmdMapElement *sister; +} XmdMapElement; + +struct _XmdMap { + Cardinal length; + Cardinal capacity; + XmdMapElement **data; +}; + +static void XmdMapResizeIfNeeded (XmdMap *map); +static Cardinal XmdMapHash (XmdMap *map, XmdMapKey key); + +XmdMap *XmdMapNew () { + XmdMap *map = XtNew(XmdMap); + if (map == NULL) goto fail; + + map->length = 0; + map->capacity = XmdMAP_INITIAL_CAPACITY; + map->data = (XmdMapElement **) ( + XtCalloc(map->capacity, sizeof(XmdMapElement *))); + if (map->data == NULL) goto fail; + + return map; + + fail: + XmdMapFree(map); + return NULL; +} + +void *XmdMapGet (XmdMap *map, XmdMapKey key) { + XmdMapElement *element = map->data[XmdMapHash(map, key)]; + while (element != NULL) { + if (element->key == key) return element->value; + element = element->sister; + } + return NULL; +} + +void *XmdMapSetInternal (XmdMap *map, XmdMapKey key, void *value) { + /* find bucket */ + XmdMapElement **destination = &map->data[XmdMapHash(map, key)]; + /* find position in bucket */ + while (*destination != NULL) { + if ((*destination)->key == key) { + /* element exists already */ + void *previous = (*destination)->value; + if (value == NULL) { + /* if setting to NULL, free old element */ + XmdMapElement *element = *destination; + *destination = element->sister; + XtFree((char *)(element)); + } else { + /* otherwise, replace value */ + (*destination)->value = value; + } + return previous; + } + destination = &((*destination)->sister); + } + /* element does not exist already */ + if (value != NULL) { + /* allocate new element and append */ + *destination = XtNew(XmdMapElement); + (*destination)->key = key; + (*destination)->value = value; + (*destination)->sister = NULL; + } + return NULL; +} + +void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value) { + void *previous = XmdMapSetInternal(map, key, value); + if (value == NULL) { + if (previous != NULL) map->length --; + } else { + if (previous == NULL) map->length ++; + } + XmdMapResizeIfNeeded(map); + return previous; +} + +void XmdMapIterate (XmdMap *map, XmdMapIterator iterator) { + for (Cardinal index = 0; index < map->capacity; index ++) { + XmdMapElement *element = map->data[index]; + while (element != NULL) { + if(!iterator(map, element->key, element->value)) break; + element = element->sister; + } + } +} + +Cardinal XmdMaplength (XmdMap *map) { + return map->length; +} + +void XmdMapFree (XmdMap *map) { + if (map == NULL) return; + XtFree((char *)(map->data)); + XtFree((char *)(map)); +} + +void XmdMapResizeIfNeeded (XmdMap *map) { + Cardinal oldCapacity = map->capacity; + XmdMapElement **oldData = map->data; + + /* figure out what the new capacity should be. if the map length is + very small compared to the map capacity, shrink the map to save + memory. if the map length is getting large compared to the map + capacity, grow the map to reduce collisions. if the map length is + neither too big nor too small, do nothing because rehashing is an + expensive operation. */ + if (map->length + XmdMAP_INITIAL_CAPACITY < map->capacity / 4) { + map->capacity /= XmdMAP_GROWTH_FACTOR; + } else if (map->length > (map->capacity * 2) / 3) { + map->capacity *= XmdMAP_GROWTH_FACTOR; + } else { + return; + } + + /* rehash */ + map->data = (XmdMapElement **) ( + XtCalloc(map->capacity, sizeof(XmdMapElement))); + for (Cardinal index = 0; index < oldCapacity; index ++) { + XmdMapElement *element = oldData[index]; + while (element != NULL) { + XmdMapSetInternal(map, element->key, element->value); + element = element->sister; + } + } + + XtFree((char *)(oldData)); +} + +static Cardinal XmdMapHash (XmdMap *map, XmdMapKey key) { + /* https://web.archive.org/web/20160329102146/http://elliottback.com/wp/ + hashmap-implementation-in-c/ */ + + /* Robert Jenkins' 32 bit Mix Function */ + key += (key << 12); + key ^= (key >> 22); + key += (key << 4); + key ^= (key >> 9); + key += (key << 10); + key ^= (key >> 2); + key += (key << 7); + key ^= (key >> 12); + + /* Knuth's Multiplicative Method */ + key = (key >> 3) * 2654435761; + + return key % map->capacity; +}