diff --git a/src/deque.c b/src/deque.c index 913a9e4..4c1f0d5 100644 --- a/src/deque.c +++ b/src/deque.c @@ -26,7 +26,8 @@ #include #include "deque.h" -const int BLOCK_SIZE = 8; +static const int BLOCK_SIZE = 8; +static const double RESIZE_RATIO = 1.5; struct _deque { size_t data_size; @@ -167,7 +168,7 @@ int deque_push_front(deque me, void *const data) if (inner_index == BLOCK_SIZE - 1) { if (block_index == -1) { const int old_block_count = me->block_count; - me->block_count = (int) ceil(1.5 * me->block_count); + me->block_count = (int) ceil(RESIZE_RATIO * me->block_count); const int added_blocks = me->block_count - old_block_count; void *temp = realloc(me->block, me->block_count * sizeof(struct node)); @@ -214,7 +215,7 @@ int deque_push_back(deque me, void *const data) const int inner_index = me->end_index % BLOCK_SIZE; if (inner_index == 0) { if (block_index == me->block_count) { - me->block_count = (int) ceil(1.5 * me->block_count); + me->block_count = (int) ceil(RESIZE_RATIO * me->block_count); void *temp = realloc(me->block, me->block_count * sizeof(struct node)); if (temp == NULL) { diff --git a/src/queue.c b/src/queue.c index af770fa..14410ae 100644 --- a/src/queue.c +++ b/src/queue.c @@ -24,7 +24,7 @@ #include "deque.h" #include "queue.h" -const int TRIM_SIZE = 64; +static const int TRIM_SIZE = 64; struct _queue { int trim_count; diff --git a/src/unordered_map.c b/src/unordered_map.c new file mode 100644 index 0000000..724d402 --- /dev/null +++ b/src/unordered_map.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2017 Bailey Thompson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "unordered_map.h" + +static const int STARTING_BUCKETS = 8; +static const double RESIZE_AT = 0.75; +static const double RESIZE_RATIO = 1.5; + +struct _unordered_map { + size_t key_size; + size_t value_size; + unsigned long (*hash)(const void *const key); + int (*comparator)(const void *const one, const void *const two); + int size; + int capacity; + struct node **buckets; +}; + +struct node { + void *key; + void *value; + unsigned long hash; + struct node *next; +}; + +/** + * Initializes an unordered map, which is a collection of key-value pairs, + * hashed by keys, keys are unique + * + * @param data_size The size of each element in the unordered map. + * @param hash The hash function which computes the hash from the key. + * @param comparator The comparator function which compares two keys. + * + * @return The newly-initialized unordered map, or NULL if memory allocation + * error. + */ +unordered_map unordered_map_init(const size_t key_size, + const size_t value_size, + unsigned long (*hash)(const void *const), + int (*comparator)(const void *const, + const void *const)) +{ + struct _unordered_map *const init = malloc(sizeof(struct _unordered_map)); + if (init == NULL) { + return NULL; + } + init->key_size = key_size; + init->value_size = value_size; + init->hash = hash; + init->comparator = comparator; + init->size = 0; + init->capacity = STARTING_BUCKETS; + init->buckets = calloc(STARTING_BUCKETS, sizeof(struct node *)); + if (init->buckets == NULL) { + free(init); + return NULL; + } + return init; +} + +/* + * Adds the specified node to the map. + */ +static void unordered_map_add_item(unordered_map me, struct node *const add) +{ + const int index = (int) (add->hash % me->capacity); + add->next = NULL; + if (me->buckets[index] == NULL) { + me->buckets[index] = add; + return; + } + struct node *traverse = me->buckets[index]; + while (traverse->next != NULL) { + traverse = traverse->next; + } + traverse->next = add; +} + +/** + * Rehashes all the keys in the unordered map. Used when storing references and + * changing the keys. This should rarely be used. + * + * @param me The unordered map to rehash. + * + * @return 0 No error. + * -ENOMEM Out of memory. + */ +int unordered_map_rehash(unordered_map me) +{ + struct node **old_buckets = me->buckets; + me->buckets = calloc((size_t) me->capacity, sizeof(struct node *)); + if (me->buckets == NULL) { + me->buckets = old_buckets; + return -ENOMEM; + } + for (int i = 0; i < me->capacity; i++) { + struct node *traverse = old_buckets[i]; + while (traverse != NULL) { + struct node *const backup = traverse->next; + traverse->hash = me->hash(traverse->key); + unordered_map_add_item(me, traverse); + traverse = backup; + } + } + free(old_buckets); + return 0; +} + +/** + * Gets the size of the unordered map. + * + * @param me The unordered map to check. + * + * @return The size of the unordered map. + */ +int unordered_map_size(unordered_map me) +{ + return me->size; +} + +/** + * Determines whether or not the unordered map is empty. + * + * @param me The unordered map to check. + * + * @return If the unordered map is empty. + */ +bool unordered_map_is_empty(unordered_map me) +{ + return unordered_map_size(me) == 0; +} + +/* + * Increases the size of the map and redistributes the nodes. + */ +static int unordered_map_resize(unordered_map me) +{ + const int old_capacity = me->capacity; + me->capacity *= RESIZE_RATIO; + struct node **old_buckets = me->buckets; + me->buckets = calloc((size_t) me->capacity, sizeof(struct node *)); + if (me->buckets == NULL) { + me->buckets = old_buckets; + return -ENOMEM; + } + for (int i = 0; i < old_capacity; i++) { + struct node *traverse = old_buckets[i]; + while (traverse != NULL) { + struct node *const backup = traverse->next; + unordered_map_add_item(me, traverse); + traverse = backup; + } + } + free(old_buckets); + return 0; +} + +/* + * Determines if an element is equal to the key. + */ +inline static bool unordered_map_is_equal(unordered_map me, + const struct node *const item, + const unsigned long hash, + const void *const key) +{ + return item->hash == hash && me->comparator(item->key, key) == 0; +} + +/* + * Creates an element to add. + */ +static struct node *const unordered_map_create_element(unordered_map me, + const unsigned long hash, + const void *const key, + const void *const value) +{ + struct node *const init = malloc(sizeof(struct node)); + if (init == NULL) { + return NULL; + } + init->key = malloc(me->key_size); + if (init->key == NULL) { + free(init); + return NULL; + } + memcpy(init->key, key, me->key_size); + init->value = malloc(me->value_size); + if (init->value == NULL) { + free(init->key); + free(init); + return NULL; + } + memcpy(init->value, value, me->value_size); + init->hash = hash; + init->next = NULL; + return init; +} + +/** + * Adds a key-value pair to the unordered map if the unordered map does not + * already contain it. + * + * @param me The unordered map to add to. + * @param data The element to add. + * + * @return 0 No error. + * -ENOMEM Out of memory. + */ +int unordered_map_put(unordered_map me, void *const key, void *const value) +{ + + const unsigned long hash = me->hash(key); + const int index = (int) (hash % me->capacity); + if (me->buckets[index] == NULL) { + me->buckets[index] = unordered_map_create_element(me, hash, key, value); + if (me->buckets[index] == NULL) { + return -ENOMEM; + } + } else { + struct node *traverse = me->buckets[index]; + if (unordered_map_is_equal(me, traverse, hash, key)) { + memcpy(traverse->value, value, me->value_size); + return 0; + } + while (traverse->next != NULL) { + traverse = traverse->next; + if (unordered_map_is_equal(me, traverse, hash, key)) { + memcpy(traverse->value, value, me->value_size); + return 0; + } + } + traverse->next = unordered_map_create_element(me, hash, key, value); + if (traverse->next == NULL) { + return -ENOMEM; + } + } + me->size++; + if (me->size >= RESIZE_AT * me->capacity) { + return unordered_map_resize(me); + } + return 0; +} + +/** + * Gets the value associated with a key in the unordered map. + * + * @param value The value to copy to. + * @param me The unordered map to get from. + * @param key The key to search for. + */ +void unordered_map_get(void *const value, unordered_map me, void *const key) +{ + const unsigned long hash = me->hash(key); + const int index = (int) (hash % me->capacity); + struct node *traverse = me->buckets[index]; + while (traverse != NULL) { + if (unordered_map_is_equal(me, traverse, hash, key)) { + memcpy(value, traverse->value, me->value_size); + return; + } + traverse = traverse->next; + } +} + +/** + * Determines if the unordered map contains the specified element. + * + * @param me The unordered map to check for the element. + * @param data The element to check. + * + * @return If the unordered map contained the element. + */ +bool unordered_map_contains(unordered_map me, void *const key) +{ + const unsigned long hash = me->hash(key); + const int index = (int) (hash % me->capacity); + const struct node *traverse = me->buckets[index]; + while (traverse != NULL) { + if (unordered_map_is_equal(me, traverse, hash, key)) { + return true; + } + traverse = traverse->next; + } + return false; +} + +/** + * Removes the element from the unordered map if it contains it. + * + * @param me The unordered map to remove an element from. + * @param data The element to remove. + * + * @return If the unordered map contained the element. + */ +bool unordered_map_remove(unordered_map me, void *const key) +{ + const unsigned long hash = me->hash(key); + const int index = (int) (hash % me->capacity); + if (me->buckets[index] == NULL) { + return false; + } + struct node *traverse = me->buckets[index]; + if (unordered_map_is_equal(me, traverse, hash, key)) { + me->buckets[index] = traverse->next; + free(traverse->key); + free(traverse->value); + free(traverse); + me->size--; + return true; + } + while (traverse->next != NULL) { + if (unordered_map_is_equal(me, traverse->next, hash, key)) { + struct node *const backup = traverse->next; + traverse->next = traverse->next->next; + free(backup->key); + free(backup->value); + free(backup); + me->size--; + return true; + } + traverse = traverse->next; + } + return false; +} + +/** + * Clears the elements from the unordered map. + * + * @param me The unordered map to clear. + */ +void unordered_map_clear(unordered_map me) +{ + for (int i = 0; i < me->capacity; i++) { + struct node *traverse = me->buckets[i]; + while (traverse != NULL) { + struct node *const backup = traverse; + traverse = traverse->next; + free(backup->key); + free(backup->value); + free(backup); + } + me->buckets[i] = NULL; + } + me->size = 0; +} + +/** + * Frees the unordered map memory. + * + * @param me The unordered map to free from memory. + * + * @return NULL + */ +unordered_map unordered_map_destroy(unordered_map me) +{ + unordered_map_clear(me); + free(me->buckets); + free(me); + return NULL; +} diff --git a/src/unordered_map.h b/src/unordered_map.h new file mode 100644 index 0000000..857b250 --- /dev/null +++ b/src/unordered_map.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 Bailey Thompson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CONTAINERS_UNORDERED_MAP_H +#define CONTAINERS_UNORDERED_MAP_H + +#include + +typedef struct _unordered_map *unordered_map; + +// Starting +unordered_map unordered_map_init(size_t key_size, + size_t value_size, + unsigned long (*hash)(const void *const key), + int (*comparator)(const void *const one, + const void *const two)); + +// Utility +int unordered_map_rehash(unordered_map me); +int unordered_map_size(unordered_map me); +bool unordered_map_is_empty(unordered_map me); + +// Accessing +int unordered_map_put(unordered_map me, void *key, void *value); +void unordered_map_get(void *value, unordered_map me, void *key); +bool unordered_map_contains(unordered_map me, void *key); +bool unordered_map_remove(unordered_map me, void *key); + +// Ending +void unordered_map_clear(unordered_map me); +unordered_map unordered_map_destroy(unordered_map me); + +#endif /* CONTAINERS_UNORDERED_MAP_H */ diff --git a/src/unordered_set.c b/src/unordered_set.c index 176f539..3172d1c 100644 --- a/src/unordered_set.c +++ b/src/unordered_set.c @@ -25,9 +25,9 @@ #include #include "unordered_set.h" -const int STARTING_BUCKETS = 8; -const double RESIZE_AT = 0.75; -const double RESIZE_RATIO = 1.5; +static const int STARTING_BUCKETS = 8; +static const double RESIZE_AT = 0.75; +static const double RESIZE_RATIO = 1.5; struct _unordered_set { size_t key_size; @@ -217,7 +217,7 @@ static struct node *const unordered_set_create_element(unordered_set me, * @return 0 No error. * -ENOMEM Out of memory. */ -int unordered_set_add(unordered_set me, void *const key) +int unordered_set_put(unordered_set me, void *const key) { const unsigned long hash = me->hash(key); @@ -262,9 +262,6 @@ bool unordered_set_contains(unordered_set me, void *const key) { const unsigned long hash = me->hash(key); const int index = (int) (hash % me->capacity); - if (me->buckets[index] == NULL) { - return false; - } const struct node *traverse = me->buckets[index]; while (traverse != NULL) { if (unordered_set_is_equal(me, traverse, hash, key)) { diff --git a/src/unordered_set.h b/src/unordered_set.h index 35defe8..330ba20 100644 --- a/src/unordered_set.h +++ b/src/unordered_set.h @@ -39,7 +39,7 @@ int unordered_set_size(unordered_set me); bool unordered_set_is_empty(unordered_set me); // Accessing -int unordered_set_add(unordered_set me, void *key); +int unordered_set_put(unordered_set me, void *key); bool unordered_set_contains(unordered_set me, void *key); bool unordered_set_remove(unordered_set me, void *key); diff --git a/src/vector.c b/src/vector.c index dbdfc0c..f3e8f5b 100644 --- a/src/vector.c +++ b/src/vector.c @@ -25,7 +25,8 @@ #include #include "vector.h" -const int START_SPACE = 8; +static const int START_SPACE = 8; +static const double RESIZE_RATIO = 1.5; struct _vector { size_t data_size; @@ -172,7 +173,7 @@ int vector_add_at(vector me, const int index, void *const data) return -EINVAL; } if (me->offset + 1 >= me->space) { - me->space *= 1.5; + me->space *= RESIZE_RATIO; void *const temp = realloc(me->storage, me->space * me->data_size); if (temp == NULL) { return -ENOMEM; diff --git a/tst/test.c b/tst/test.c index 7105105..7294421 100644 --- a/tst/test.c +++ b/tst/test.c @@ -11,5 +11,6 @@ int main() test_set(); test_array(); test_unordered_set(); + test_unordered_map(); return 0; } diff --git a/tst/test.h b/tst/test.h index a8ceaee..195a580 100644 --- a/tst/test.h +++ b/tst/test.h @@ -14,5 +14,6 @@ void test_queue(void); void test_set(void); void test_array(void); void test_unordered_set(void); +void test_unordered_map(void); #endif /* CONTAINERS_TEST_H */ diff --git a/tst/unordered_map.c b/tst/unordered_map.c new file mode 100644 index 0000000..69898b4 --- /dev/null +++ b/tst/unordered_map.c @@ -0,0 +1,135 @@ +#include "test.h" +#include "../src/unordered_map.h" + +static int compare_int(const void *const one, const void *const two) +{ + const int a = *(int *) one; + const int b = *(int *) two; + return a - b; +} + +static int hash_count; + +static unsigned long hash_int(const void *const key) +{ + hash_count++; + unsigned long hash = 17; + hash = 31 * hash + *(int *) key; + return hash; +} + +void test_unordered_map(void) +{ + unordered_map a = unordered_map_init(sizeof(int), + sizeof(int), + hash_int, + compare_int); + assert(unordered_map_size(a) == 0); + assert(unordered_map_is_empty(a)); + int b = 4; + int c = 9; + unordered_map_put(a, &b, &c); + assert(unordered_map_size(a) == 1); + c = 5; + unordered_map_put(a, &b, &c); + assert(unordered_map_size(a) == 1); + assert(!unordered_map_is_empty(a)); + assert(unordered_map_contains(a, &b)); + c = 0xdeadbeef; + unordered_map_get(&c, a, &b); + assert(c == 5); + b = 7; + assert(!unordered_map_contains(a, &b)); + unordered_map_put(a, &b, &c); + assert(unordered_map_size(a) == 2); + assert(unordered_map_contains(a, &b)); + int d[10] = {5, 9, 4, -5, 0, 6, 1, 5, 7, 2}; + for (int i = 0; i < 10; i++) { + unordered_map_put(a, &d[i], &c); + assert(unordered_map_contains(a, &d[i])); + } + assert(unordered_map_size(a) == 9); + for (int i = 0; i < 10; i++) { + assert(unordered_map_contains(a, &d[i])); + } + for (int i = -100; i < 100; i++) { + bool contains = false; + for (int j = 0; j < 10; j++) { + if (d[j] == i) { + contains = true; + } + } + assert(unordered_map_contains(a, &i) == contains); + } + int num = -3; + assert(!unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 9); + assert(!unordered_map_contains(a, &num)); + num = 6; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 8); + assert(!unordered_map_contains(a, &num)); + num = 4; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 7); + assert(!unordered_map_contains(a, &num)); + num = 7; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 6); + assert(!unordered_map_contains(a, &num)); + num = 9; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 5); + assert(!unordered_map_contains(a, &num)); + num = -5; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 4); + assert(!unordered_map_contains(a, &num)); + num = 0; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 3); + assert(!unordered_map_contains(a, &num)); + num = 1; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 2); + assert(!unordered_map_contains(a, &num)); + num = 5; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 1); + assert(!unordered_map_contains(a, &num)); + num = 2; + assert(unordered_map_remove(a, &num)); + assert(unordered_map_size(a) == 0); + assert(!unordered_map_contains(a, &num)); + // Add a lot of items and remove individually. + for (int i = 5000; i < 6000; i++) { + unordered_map_put(a, &i, &c); + assert(unordered_map_contains(a, &i)); + } + assert(unordered_map_size(a) == 1000); + for (int i = 5000; i < 6000; i++) { + unordered_map_remove(a, &i); + assert(!unordered_map_contains(a, &i)); + } + assert(unordered_map_size(a) == 0); + assert(unordered_map_is_empty(a)); + unordered_map_clear(a); + assert(unordered_map_size(a) == 0); + assert(unordered_map_is_empty(a)); + // Add a lot of items and clear. + for (int i = 5000; i < 6000; i++) { + unordered_map_put(a, &i, &c); + assert(unordered_map_contains(a, &i)); + } + assert(unordered_map_size(a) == 1000); + hash_count = 0; + unordered_map_rehash(a); + assert(hash_count == 1000); + unordered_map_clear(a); + int p = 0xdeadbeef; + assert(!unordered_map_remove(a, &p)); + assert(unordered_map_size(a) == 0); + assert(unordered_map_is_empty(a)); + a = unordered_map_destroy(a); + assert(a == NULL); +} diff --git a/tst/unordered_set.c b/tst/unordered_set.c index c595902..35ffd9c 100644 --- a/tst/unordered_set.c +++ b/tst/unordered_set.c @@ -24,20 +24,20 @@ void test_unordered_set(void) assert(unordered_set_size(a) == 0); assert(unordered_set_is_empty(a)); int b = 4; - unordered_set_add(a, &b); + unordered_set_put(a, &b); assert(unordered_set_size(a) == 1); - unordered_set_add(a, &b); + unordered_set_put(a, &b); assert(unordered_set_size(a) == 1); assert(!unordered_set_is_empty(a)); assert(unordered_set_contains(a, &b)); b = 7; assert(!unordered_set_contains(a, &b)); - unordered_set_add(a, &b); + unordered_set_put(a, &b); assert(unordered_set_size(a) == 2); assert(unordered_set_contains(a, &b)); int c[10] = {5, 9, 4, -5, 0, 6, 1, 5, 7, 2}; for (int i = 0; i < 10; i++) { - unordered_set_add(a, &c[i]); + unordered_set_put(a, &c[i]); assert(unordered_set_contains(a, &c[i])); } assert(unordered_set_size(a) == 9); @@ -95,7 +95,7 @@ void test_unordered_set(void) assert(!unordered_set_contains(a, &num)); // Add a lot of items and remove individually. for (int i = 5000; i < 6000; i++) { - unordered_set_add(a, &i); + unordered_set_put(a, &i); assert(unordered_set_contains(a, &i)); } assert(unordered_set_size(a) == 1000); @@ -110,7 +110,7 @@ void test_unordered_set(void) assert(unordered_set_is_empty(a)); // Add a lot of items and clear. for (int i = 5000; i < 6000; i++) { - unordered_set_add(a, &i); + unordered_set_put(a, &i); assert(unordered_set_contains(a, &i)); } assert(unordered_set_size(a) == 1000);