diff --git a/tests/main/main.c b/tests/main/main.c index 1ddcfd3..8d3685d 100644 --- a/tests/main/main.c +++ b/tests/main/main.c @@ -9,6 +9,8 @@ #include "unt/treemap.c" #include "unt/treeset.c" +#include "unt/multimap.c" + int main(void) { deque_test(); @@ -22,5 +24,8 @@ int main(void) treemap_test(); treeset_test(); + /* ext */ + multimap_test(); + return 0; } diff --git a/tests/main/src/multimap.c b/tests/main/src/multimap.c new file mode 100644 index 0000000..c1ec3ab --- /dev/null +++ b/tests/main/src/multimap.c @@ -0,0 +1,535 @@ +#include "ext/multimap.h" + +typedef struct multimap_s +{ + struct multimap_entry_s *(*buffer)[2]; + size_t capacity; + size_t count; + double load; + int (*cmp)(size_t, size_t); + size_t (*hash)(size_t); + struct multimap_iter_s (*it_start)(struct multimap_s *); + struct multimap_iter_s (*it_end)(struct multimap_s *); +} multimap, *multimap_ptr; +typedef struct multimap_entry_s +{ + size_t key; + size_t value; + struct multimap_entry_s *next; + struct multimap_entry_s *prev; +} multimap_entry, *multimap_entry_ptr; +typedef struct multimap_iter_s +{ + struct multimap_s *target; + struct multimap_entry_s *curr_entry; + size_t cursor; + size_t index; + size_t first; + size_t last; + bool start; + bool end; +} multimap_iter, *multimap_iter_ptr; +multimap *mm_new(size_t size, double load, int (*compare)(size_t, size_t), size_t (*hash)(size_t)); +void mm_clear(multimap *_map_); +void mm_free(multimap *_map_); +bool mm_insert(multimap *_map_, size_t key, size_t value); +bool mm_remove(multimap *_map_, size_t key, size_t *value); +size_t mm_remove_all(multimap *_map_, size_t key); +bool mm_insert_if(multimap *_map_, size_t key, size_t value, bool condition); +bool mm_remove_if(multimap *_map_, size_t key, size_t *value, bool condition); +bool mm_max(multimap *_map_, size_t *key, size_t *value); +bool mm_min(multimap *_map_, size_t *key, size_t *value); +size_t mm_get(multimap *_map_, size_t key); +size_t *mm_get_ref(multimap *_map_, size_t key); +bool mm_contains(multimap *_map_, size_t key); +bool mm_empty(multimap *_map_); +size_t mm_count(multimap *_map_); +size_t mm_capacity(multimap *_map_); +multimap_iter *mm_iter_new(multimap *target); +void mm_iter_free(multimap_iter *iter); +void mm_iter_init(multimap_iter *iter, multimap *target); +bool mm_iter_start(multimap_iter *iter); +bool mm_iter_end(multimap_iter *iter); +void mm_iter_to_start(multimap_iter *iter); +void mm_iter_to_end(multimap_iter *iter); +bool mm_iter_next(multimap_iter *iter); +bool mm_iter_prev(multimap_iter *iter); +size_t mm_iter_key(multimap_iter *iter); +size_t mm_iter_value(multimap_iter *iter); +size_t *mm_iter_rvalue(multimap_iter *iter); +size_t mm_iter_index(multimap_iter *iter); +static inline size_t mm_impl_default_key(void) +{ + size_t _empty_key_; + memset(&_empty_key_, 0, sizeof(size_t)); + return _empty_key_; +} +static inline size_t mm_impl_default_value(void) +{ + size_t _empty_value_; + memset(&_empty_value_, 0, sizeof(size_t)); + return _empty_value_; +} +bool mm_impl_grow(multimap *_map_); +multimap_entry *mm_impl_new_entry(size_t key, size_t value); +multimap_entry *mm_impl_get_entry(multimap *_map_, size_t key); +size_t mm_impl_calculate_size(size_t required); +static multimap_iter mm_impl_it_start(multimap *_map_); +static multimap_iter mm_impl_it_end(multimap *_map_); +multimap *mm_new(size_t capacity, double load, int (*compare)(size_t, size_t), size_t (*hash)(size_t)) +{ + if (capacity == 0 || load <= 0) + return NULL; + size_t real_capacity = mm_impl_calculate_size(capacity / load); + multimap *_map_ = malloc(sizeof(multimap)); + if (!_map_) + return NULL; + _map_->buffer = malloc(sizeof(multimap_entry_ptr[2]) * real_capacity); + if (!_map_->buffer) + { + free(_map_); + return NULL; + } + memset(_map_->buffer, 0, sizeof(multimap_entry_ptr[2]) * real_capacity); + _map_->count = 0; + _map_->capacity = real_capacity; + _map_->load = load; + _map_->cmp = compare; + _map_->hash = hash; + return _map_; +} +void mm_clear(multimap *_map_) +{ + size_t index = 0; + multimap_entry *scan; + while (index < _map_->capacity) + { + scan = _map_->buffer[index][0]; + if (scan != NULL) + { + if (scan->next == NULL && scan->prev == NULL) + free(scan); + else + { + while (scan != NULL) + { + multimap_entry *tmp = scan; + scan = scan->next; + free(tmp); + } + } + } + index++; + } + memset(_map_->buffer, 0, sizeof(multimap_entry_ptr[2]) * _map_->capacity); + _map_->count = 0; +} +void mm_free(multimap *_map_) +{ + mm_clear(_map_); + free(_map_->buffer); + free(_map_); +} +bool mm_insert(multimap *_map_, size_t key, size_t value) +{ + if ((double)_map_->capacity * _map_->load <= (double)_map_->count) + { + if (!mm_impl_grow(_map_)) + return false; + } + size_t hash = _map_->hash(key); + size_t pos = hash % _map_->capacity; + multimap_entry *entry = mm_impl_new_entry(key, value); + if (_map_->buffer[pos][0] == NULL) + { + _map_->buffer[pos][0] = entry; + _map_->buffer[pos][1] = entry; + } + else + { + entry->prev = _map_->buffer[pos][1]; + _map_->buffer[pos][1]->next = entry; + _map_->buffer[pos][1] = entry; + } + _map_->count++; + return true; +} +bool mm_remove(multimap *_map_, size_t key, size_t *value) +{ + size_t hash = _map_->hash(key); + multimap_entry **head = &(_map_->buffer[hash % _map_->capacity][0]); + multimap_entry **tail = &(_map_->buffer[hash % _map_->capacity][1]); + if (*head == NULL) + return false; + multimap_entry *entry = *head; + if (entry->next == NULL && entry->prev == NULL) + { + if (_map_->cmp(entry->key, key) == 0) + { + *head = NULL; + *tail = NULL; + *value = entry->value; + } + else + return false; + } + else + { + bool found = false; + while (entry != NULL) + { + if (_map_->cmp(entry->key, key) == 0) + { + if (*head == entry) + *head = entry->next; + if (*tail == entry) + *tail = entry->prev; + if (entry->prev != NULL) + entry->prev->next = entry->next; + if (entry->next != NULL) + entry->next->prev = entry->prev; + *value = entry->value; + found = true; + break; + } + else + entry = entry->next; + } + if (!found) + return false; + } + free(entry); + _map_->count--; + return true; +} +size_t mm_remove_all(multimap *_map_, size_t key) +{ + size_t total = 0; + size_t hash = _map_->hash(key); + multimap_entry **head = &(_map_->buffer[hash % _map_->capacity][0]); + multimap_entry **tail = &(_map_->buffer[hash % _map_->capacity][1]); + if (*head == NULL) + return total; + multimap_entry *entry = *head; + if (entry->next == NULL) + { + *head = NULL; + *tail = NULL; + free(entry); + total++; + } + else + { + while (entry != NULL) + { + if (_map_->cmp(entry->key, key) == 0) + { + if (*head == entry) + *head = entry->next; + if (*tail == entry) + *tail = entry->prev; + multimap_entry *temp = entry->next; + if (entry->prev != NULL) + entry->prev->next = entry->next; + if (entry->next != NULL) + entry->next->prev = entry->prev; + free(entry); + entry = temp; + total++; + } + else + entry = entry->next; + } + } + _map_->count -= total; + return total; +} +bool mm_insert_if(multimap *_map_, size_t key, size_t value, bool condition) +{ + if (condition) + return mm_insert(_map_, key, value); + return false; +} +bool mm_remove_if(multimap *_map_, size_t key, size_t *value, bool condition) +{ + if (condition) + return mm_remove(_map_, key, value); + return false; +} +bool mm_max(multimap *_map_, size_t *key, size_t *value) +{ + if (mm_empty(_map_)) + return false; + multimap_iter iter; + for (mm_iter_init(&iter, _map_); !mm_iter_end(&iter); mm_iter_next(&iter)) + { + size_t result_key = mm_iter_key(&iter); + size_t result_value = mm_iter_value(&iter); + size_t index = mm_iter_index(&iter); + if (index == 0) + { + *key = result_key; + *value = result_value; + } + else if (_map_->cmp(result_key, *key) > 0) + { + *key = result_key; + *value = result_value; + } + } + return true; +} +bool mm_min(multimap *_map_, size_t *key, size_t *value) +{ + if (mm_empty(_map_)) + return false; + multimap_iter iter; + for (mm_iter_init(&iter, _map_); !mm_iter_end(&iter); mm_iter_next(&iter)) + { + size_t result_key = mm_iter_key(&iter); + size_t result_value = mm_iter_value(&iter); + size_t index = mm_iter_index(&iter); + if (index == 0) + { + *key = result_key; + *value = result_value; + } + else if (_map_->cmp(result_key, *key) < 0) + { + *key = result_key; + *value = result_value; + } + } + return true; +} +size_t mm_get(multimap *_map_, size_t key) +{ + multimap_entry *entry = mm_impl_get_entry(_map_, key); + if (!entry) + return mm_impl_default_value(); + return entry->value; +} +size_t *mm_get_ref(multimap *_map_, size_t key) +{ + multimap_entry *entry = mm_impl_get_entry(_map_, key); + if (!entry) + return NULL; + return &(entry->value); +} +bool mm_contains(multimap *_map_, size_t key) { return mm_impl_get_entry(_map_, key) != NULL; } +bool mm_empty(multimap *_map_) { return _map_->count == 0; } +size_t mm_count(multimap *_map_) { return _map_->count; } +size_t mm_capacity(multimap *_map_) { return _map_->capacity; } +multimap_iter *mm_iter_new(multimap *target) +{ + multimap_iter *iter = malloc(sizeof(multimap_iter)); + if (!iter) + return NULL; + mm_iter_init(iter, target); + return iter; +} +void mm_iter_free(multimap_iter *iter) { free(iter); } +void mm_iter_init(multimap_iter *iter, multimap *target) +{ + iter->target = target; + iter->cursor = 0; + iter->index = 0; + iter->start = true; + iter->end = mm_empty(target); + if (!mm_empty(target)) + { + for (size_t i = 0; i < target->capacity; i++) + { + if (target->buffer[i][0] != NULL) + { + iter->curr_entry = target->buffer[i][0]; + iter->first = i; + break; + } + } + iter->cursor = iter->first; + for (size_t i = target->capacity; i > 0; i--) + { + if (target->buffer[i - 1][0] != NULL) + { + iter->last = i - 1; + break; + } + } + } +} +bool mm_iter_start(multimap_iter *iter) { return mm_empty(iter->target) || iter->start; } +bool mm_iter_end(multimap_iter *iter) { return mm_empty(iter->target) || iter->end; } +void mm_iter_to_start(multimap_iter *iter) +{ + iter->cursor = iter->first; + iter->index = 0; + iter->start = true; + iter->end = mm_empty(iter->target); + iter->curr_entry = iter->target->buffer[iter->first][0]; +} +void mm_iter_to_end(multimap_iter *iter) +{ + iter->cursor = iter->last; + iter->index = iter->target->count - 1; + iter->start = mm_empty(iter->target); + iter->end = true; + iter->curr_entry = iter->target->buffer[iter->last][1]; +} +bool mm_iter_next(multimap_iter *iter) +{ + if (iter->end) + return false; + if (iter->curr_entry->next != NULL) + { + iter->curr_entry = iter->curr_entry->next; + iter->index++; + } + else + { + if (iter->cursor == iter->last) + { + iter->end = true; + return false; + } + else + { + iter->cursor++; + while (iter->target->buffer[iter->cursor][0] == NULL) + iter->cursor++; + iter->curr_entry = iter->target->buffer[iter->cursor][0]; + iter->index++; + } + } + iter->start = mm_empty(iter->target); + return true; +} +bool mm_iter_prev(multimap_iter *iter) +{ + if (iter->start) + return false; + if (iter->curr_entry->prev != NULL) + { + iter->curr_entry = iter->curr_entry->prev; + iter->index--; + } + else + { + if (iter->cursor == iter->first) + { + iter->start = true; + return false; + } + else + { + iter->cursor--; + while (iter->target->buffer[iter->cursor][1] == NULL) + iter->cursor--; + iter->curr_entry = iter->target->buffer[iter->cursor][1]; + iter->index--; + } + } + iter->end = mm_empty(iter->target); + return true; +} +size_t mm_iter_key(multimap_iter *iter) +{ + if (mm_empty(iter->target)) + return mm_impl_default_key(); + return iter->curr_entry->key; +} +size_t mm_iter_value(multimap_iter *iter) +{ + if (mm_empty(iter->target)) + return mm_impl_default_value(); + return iter->curr_entry->value; +} +size_t *mm_iter_rvalue(multimap_iter *iter) +{ + if (mm_empty(iter->target)) + return NULL; + return &(iter->curr_entry->value); +} +size_t mm_iter_index(multimap_iter *iter) { return iter->index; } +bool mm_impl_grow(multimap *_map_) +{ + size_t new_capacity = mm_impl_calculate_size(_map_->capacity * 1.5); + multimap *_new_map_ = mm_new(new_capacity, _map_->load, _map_->cmp, _map_->hash); + if (!_new_map_) + return false; + multimap_iter iter; + for (mm_iter_init(&iter, _map_); !mm_iter_end(&iter); mm_iter_next(&iter)) + { + size_t key = mm_iter_key(&iter); + size_t value = mm_iter_value(&iter); + mm_insert(_new_map_, key, value); + } + if (_map_->count != _new_map_->count) + { + mm_free(_new_map_); + return false; + } + multimap_entry *(*tmp_b)[2] = _map_->buffer; + _map_->buffer = _new_map_->buffer; + _new_map_->buffer = tmp_b; + size_t tmp_c = _map_->capacity; + _map_->capacity = _new_map_->capacity; + _new_map_->capacity = tmp_c; + mm_free(_new_map_); + return true; +} +multimap_entry *mm_impl_new_entry(size_t key, size_t value) +{ + multimap_entry *entry = malloc(sizeof(multimap_entry)); + if (!entry) + return NULL; + entry->key = key; + entry->value = value; + entry->next = NULL; + entry->prev = NULL; + return entry; +} +multimap_entry *mm_impl_get_entry(multimap *_map_, size_t key) +{ + size_t hash = _map_->hash(key); + multimap_entry *entry = _map_->buffer[hash % _map_->capacity][0]; + if (entry == NULL) + return NULL; + if (entry->next == NULL && entry->prev == NULL) + { + if (_map_->cmp(entry->key, key) == 0) + return entry; + else + return NULL; + } + while (entry != NULL) + { + if (_map_->cmp(entry->key, key) == 0) + return entry; + entry = entry->next; + } + return NULL; +} +size_t mm_impl_calculate_size(size_t required) +{ + const size_t count = sizeof(cmc_hashtable_primes) / sizeof(cmc_hashtable_primes[0]); + if (cmc_hashtable_primes[count - 1] < required) + return required; + size_t i = 0; + while (cmc_hashtable_primes[i] < required) + i++; + return cmc_hashtable_primes[i]; +} +static multimap_iter mm_impl_it_start(multimap *_map_) +{ + multimap_iter iter; + mm_iter_init(&iter, _map_); + return iter; +} +static multimap_iter mm_impl_it_end(multimap *_map_) +{ + multimap_iter iter; + mm_iter_init(&iter, _map_); + mm_iter_to_end(&iter); + return iter; +} diff --git a/tests/main/unt/multimap.c b/tests/main/unt/multimap.c new file mode 100644 index 0000000..dd292b8 --- /dev/null +++ b/tests/main/unt/multimap.c @@ -0,0 +1,173 @@ +#include "utl/assert.h" +#include "utl/test.h" +#include "../src/multimap.c" + +#include "utl.c" + +CMC_CREATE_UNIT(multimap_test, true, { + CMC_CREATE_TEST(new, { + multimap *map = mm_new(943722, 0.8, cmp, hash); + + bool passed = (map->capacity >= (943722 / 0.8)) && map->count == 0 && map->buffer; + + cmc_assert_not_equals(ptr, NULL, map); + cmc_assert_greater_equals(size_t, (943722 / 0.8), mm_capacity(map)); + + CMC_TEST_PASS_ELSE_FAIL(passed); + + mm_free(map); + }); + + CMC_CREATE_TEST(new[edge_case:capacity = 0], { + multimap *map = mm_new(0, 0.8, cmp, hash); + + cmc_assert_equals(ptr, NULL, map); + + CMC_TEST_PASS_ELSE_FAIL(map == NULL); + }); + + CMC_CREATE_TEST(new[edge_case:capacity = UINT64_MAX], { + multimap *map = mm_new(UINT64_MAX, 0.8, cmp, hash); + + cmc_assert_equals(ptr, NULL, map); + + CMC_TEST_PASS_ELSE_FAIL(map == NULL); + }); + + CMC_CREATE_TEST(clear[count capacity], { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + for (size_t i = 0; i < 50; i++) + cmc_assert(mm_insert(map, i, i)); + + cmc_assert_equals(size_t, 50, mm_count(map)); + + mm_clear(map); + + cmc_assert_equals(size_t, 0, mm_count(map)); + + CMC_TEST_PASS_ELSE_FAIL(mm_count(map) == 0); + + mm_free(map); + }); + + CMC_CREATE_TEST(insert[count], { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + for (size_t i = 0; i < 200; i++) + cmc_assert(mm_insert(map, i % 20, i % 20)); + + cmc_assert_equals(size_t, 200, mm_count(map)); + + CMC_TEST_PASS_ELSE_FAIL(mm_count(map) == 200); + + mm_free(map); + }); + + CMC_CREATE_TEST(remove[count], { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + for (size_t i = 0; i < 200; i++) + cmc_assert(mm_insert(map, i % 20, i % 20)); + + size_t r; + + for (size_t i = 0; i < 60; i++) + cmc_assert(mm_remove(map, i % 20, &r)); + + cmc_assert_equals(size_t, 140, mm_count(map)); + + CMC_TEST_PASS_ELSE_FAIL(mm_count(map) == 140); + + mm_free(map); + }); + + CMC_CREATE_TEST(remove[edge_case:count = 0], { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + size_t r; + + cmc_assert(!mm_remove(map, 10, &r)); + + CMC_TEST_PASS_ELSE_FAIL(!mm_remove(map, 10, &r)); + + mm_free(map); + }); + + CMC_CREATE_TEST(remove[edge_case:count = 1], { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + size_t r; + + cmc_assert(mm_insert(map, 10, 11)); + cmc_assert(mm_remove(map, 10, &r)); + + CMC_TEST_PASS_ELSE_FAIL(mm_count(map) == 0); + + mm_free(map); + }); + + CMC_CREATE_TEST(insert_if, { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + for (size_t i = 1; i <= 100; i++) + cmc_assert(mm_insert_if(map, i % 20, i, i % 2 == 0) || i % 2 != 0); + + cmc_assert_equals(size_t, 50, mm_count(map)); + + CMC_TEST_PASS_ELSE_FAIL(mm_count(map) == 50); + + mm_free(map); + }); + + CMC_CREATE_TEST(remove_if, { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + for (size_t i = 1; i <= 100; i++) + cmc_assert(mm_insert(map, i % 20, i)); + + size_t r; + + for (size_t i = 1; i <= 100; i++) + cmc_assert(mm_remove_if(map, i % 20, &r, i % 2 == 0) || i % 2 != 0); + + cmc_assert_equals(size_t, 50, mm_count(map)); + + CMC_TEST_PASS_ELSE_FAIL(mm_count(map) == 50); + + mm_free(map); + }); + + CMC_CREATE_TEST(get[key_ordering], { + multimap *map = mm_new(100, 0.8, cmp, hash); + + cmc_assert_not_equals(ptr, NULL, map); + + /* Assert that the value 100 is accessed before the value 101 for the + * same key */ + cmc_assert(mm_insert(map, 10, 100)); + cmc_assert(mm_insert(map, 20, 102)); + cmc_assert(mm_insert(map, 10, 101)); + + cmc_assert_equals(size_t, 100, mm_get(map, 10)); + + CMC_TEST_PASS_ELSE_FAIL(mm_get(map, 10) == 100); + + mm_free(map); + }); + +});