From fbe62ba5b23b5e287001fdff12ba2e40369329ec Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 30 Jun 2024 15:32:17 +0900 Subject: [PATCH] Impl. priority heap, minor tweaks/fixes Implemented a priority heap. Added tests for the priority heap. Add a "default" lcg generator function. Tweak hash_map to use the default lcg generator function. `clang-format`. --- CMakeLists.txt | 2 + cosmopolitan/Makefile | 4 +- src/algorithms/linear_congruential_gen.c | 5 + src/algorithms/linear_congruential_gen.h | 2 + src/data_structures/hash_map.c | 3 +- src/data_structures/priority_heap.c | 159 +++++++++++++++++++++++ src/data_structures/priority_heap.h | 55 ++++++++ src/data_structures/test.c | 110 ++++++++++++++++ src/platforms.h | 10 +- 9 files changed, 342 insertions(+), 8 deletions(-) create mode 100644 src/data_structures/priority_heap.c create mode 100644 src/data_structures/priority_heap.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d929f93..3542f02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ set(SimpleArchiver_SOURCES src/parser.c src/data_structures/linked_list.c src/data_structures/hash_map.c + src/data_structures/priority_heap.c src/algorithms/linear_congruential_gen.c ) @@ -29,5 +30,6 @@ add_executable(test_datastructures src/data_structures/test.c src/data_structures/linked_list.c src/data_structures/hash_map.c + src/data_structures/priority_heap.c src/algorithms/linear_congruential_gen.c ) diff --git a/cosmopolitan/Makefile b/cosmopolitan/Makefile index 9f70d86..098c0a3 100644 --- a/cosmopolitan/Makefile +++ b/cosmopolitan/Makefile @@ -8,13 +8,15 @@ SOURCES = \ ../src/parser.c \ ../src/algorithms/linear_congruential_gen.c \ ../src/data_structures/linked_list.c \ - ../src/data_structures/hash_map.c + ../src/data_structures/hash_map.c \ + ../src/data_structures/priority_heap.c HEADERS = \ ../src/parser.h \ ../src/algorithms/linear_congruential_gen.h \ ../src/data_structures/linked_list.h \ ../src/data_structures/hash_map.h \ + ../src/data_structures/priority_heap.h \ ../src/platforms.h OBJECTS = $(addprefix ${OBJDIR}/,$(subst ..,PREVDIR,$(patsubst %.c,%.c.o,${SOURCES}))) diff --git a/src/algorithms/linear_congruential_gen.c b/src/algorithms/linear_congruential_gen.c index da21c76..374dd5f 100644 --- a/src/algorithms/linear_congruential_gen.c +++ b/src/algorithms/linear_congruential_gen.c @@ -25,3 +25,8 @@ unsigned long long simple_archiver_algo_lcg(unsigned long long seed, // "m" is implicity 2^64. return seed * a + c; } + +unsigned long long simple_archiver_algo_lcg_defaults(unsigned long long seed) { + // "m" is implicity 2^64. + return seed * SC_ALGO_LCG_DEFAULT_A + SC_ALGO_LCG_DEFAULT_C; +} diff --git a/src/algorithms/linear_congruential_gen.h b/src/algorithms/linear_congruential_gen.h index a30fe2a..11a5940 100644 --- a/src/algorithms/linear_congruential_gen.h +++ b/src/algorithms/linear_congruential_gen.h @@ -27,4 +27,6 @@ unsigned long long simple_archiver_algo_lcg(unsigned long long seed, unsigned long long a, unsigned long long c); +unsigned long long simple_archiver_algo_lcg_defaults(unsigned long long seed); + #endif diff --git a/src/data_structures/hash_map.c b/src/data_structures/hash_map.c index f5653ad..d1f04d1 100644 --- a/src/data_structures/hash_map.c +++ b/src/data_structures/hash_map.c @@ -86,8 +86,7 @@ unsigned long long simple_archiver_hash_map_internal_key_to_hash( seed += temp; } - return simple_archiver_algo_lcg(seed, SC_ALGO_LCG_DEFAULT_A, - SC_ALGO_LCG_DEFAULT_C); + return simple_archiver_algo_lcg_defaults(seed); } /// Returns 0 on success. diff --git a/src/data_structures/priority_heap.c b/src/data_structures/priority_heap.c new file mode 100644 index 0000000..9e5a0b1 --- /dev/null +++ b/src/data_structures/priority_heap.c @@ -0,0 +1,159 @@ +/* + * Copyright 2024 Stephen Seo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * `priority_heap.c` is the source for a priority heap implementation. + */ + +#include "priority_heap.h" + +#include + +void simple_archiver_priority_heap_internal_realloc( + SDArchiverPHeap **priority_heap) { + SDArchiverPHeap *new_priority_heap = malloc(sizeof(SDArchiverPHeap)); + + new_priority_heap->capacity = (*priority_heap)->capacity * 2; + new_priority_heap->size = 0; + + new_priority_heap->nodes = + calloc(new_priority_heap->capacity, sizeof(SDArchiverPHNode)); + + for (unsigned int idx = 1; idx < (*priority_heap)->size + 1; ++idx) { + if ((*priority_heap)->nodes[idx].is_valid != 0) { + simple_archiver_priority_heap_insert( + &new_priority_heap, (*priority_heap)->nodes[idx].priority, + (*priority_heap)->nodes[idx].data, + (*priority_heap)->nodes[idx].data_cleanup_fn); + (*priority_heap)->nodes[idx].is_valid = 0; + } + } + + simple_archiver_priority_heap_free(priority_heap); + + *priority_heap = new_priority_heap; +} + +SDArchiverPHeap *simple_archiver_priority_heap_init(void) { + SDArchiverPHeap *priority_heap = malloc(sizeof(SDArchiverPHeap)); + + priority_heap->capacity = SC_SA_DS_PRIORITY_HEAP_START_SIZE; + priority_heap->size = 0; + + priority_heap->nodes = + calloc(priority_heap->capacity, sizeof(SDArchiverPHNode)); + + return priority_heap; +} + +void simple_archiver_priority_heap_free(SDArchiverPHeap **priority_heap) { + if (priority_heap && *priority_heap) { + for (unsigned int idx = 1; idx < (*priority_heap)->size + 1; ++idx) { + if ((*priority_heap)->nodes[idx].is_valid != 0) { + if ((*priority_heap)->nodes[idx].data_cleanup_fn) { + (*priority_heap) + ->nodes[idx] + .data_cleanup_fn((*priority_heap)->nodes[idx].data); + } else { + free((*priority_heap)->nodes[idx].data); + } + (*priority_heap)->nodes[idx].is_valid = 0; + } + } + + free((*priority_heap)->nodes); + free(*priority_heap); + *priority_heap = NULL; + } +} + +void simple_archiver_priority_heap_insert(SDArchiverPHeap **priority_heap, + long long priority, void *data, + void (*data_cleanup_fn)(void *)) { + if (!priority_heap || !*priority_heap) { + return; + } + + if ((*priority_heap)->size + 1 >= (*priority_heap)->capacity) { + simple_archiver_priority_heap_internal_realloc(priority_heap); + } + + unsigned int hole = (*priority_heap)->size + 1; + + while (hole > 1 && priority < (*priority_heap)->nodes[hole / 2].priority) { + (*priority_heap)->nodes[hole] = (*priority_heap)->nodes[hole / 2]; + hole /= 2; + } + + (*priority_heap)->nodes[hole].priority = priority; + (*priority_heap)->nodes[hole].data = data; + (*priority_heap)->nodes[hole].data_cleanup_fn = data_cleanup_fn; + (*priority_heap)->nodes[hole].is_valid = 1; + + ++(*priority_heap)->size; +} + +void *simple_archiver_priority_heap_top(SDArchiverPHeap *priority_heap) { + if (priority_heap && priority_heap->size != 0) { + return priority_heap->nodes[1].data; + } + + return NULL; +} + +void *simple_archiver_priority_heap_pop(SDArchiverPHeap *priority_heap) { + if (!priority_heap || priority_heap->size == 0) { + return NULL; + } + + void *data = priority_heap->nodes[1].data; + priority_heap->nodes[1].is_valid = 0; + + SDArchiverPHNode end = priority_heap->nodes[priority_heap->size]; + priority_heap->nodes[priority_heap->size].is_valid = 0; + + unsigned int hole = 1; + while (hole * 2 + 1 <= priority_heap->size) { + if (priority_heap->nodes[hole * 2].is_valid != 0 && + priority_heap->nodes[hole * 2 + 1].is_valid != 0) { + if (end.priority < priority_heap->nodes[hole * 2].priority && + end.priority < priority_heap->nodes[hole * 2 + 1].priority) { + break; + } + if (priority_heap->nodes[hole * 2].priority < + priority_heap->nodes[hole * 2 + 1].priority) { + priority_heap->nodes[hole] = priority_heap->nodes[hole * 2]; + hole = hole * 2; + } else { + priority_heap->nodes[hole] = priority_heap->nodes[hole * 2 + 1]; + hole = hole * 2 + 1; + } + } else if (priority_heap->nodes[hole * 2].is_valid != 0) { + if (end.priority < priority_heap->nodes[hole * 2].priority) { + break; + } + priority_heap->nodes[hole] = priority_heap->nodes[hole * 2]; + hole = hole * 2; + break; + } else { + break; + } + } + + priority_heap->nodes[hole] = end; + + --priority_heap->size; + + return data; +} diff --git a/src/data_structures/priority_heap.h b/src/data_structures/priority_heap.h new file mode 100644 index 0000000..840ed5f --- /dev/null +++ b/src/data_structures/priority_heap.h @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Stephen Seo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * `priority_heap.h` is the header for a priority heap implementation. + */ + +#ifndef SEODISPARATE_COM_SIMPLE_ARCHIVER_DATA_STRUCTURE_PRIORITY_HEAP_H_ +#define SEODISPARATE_COM_SIMPLE_ARCHIVER_DATA_STRUCTURE_PRIORITY_HEAP_H_ + +#define SC_SA_DS_PRIORITY_HEAP_START_SIZE 32 + +typedef struct SDArchiverPHNode { + long long priority; + void *data; + void (*data_cleanup_fn)(void *); + /// Is non-zero if valid. + int is_valid; +} SDArchiverPHNode; + +typedef struct SDArchiverPHeap { + SDArchiverPHNode *nodes; + unsigned long long capacity; + unsigned long long size; +} SDArchiverPHeap; + +SDArchiverPHeap *simple_archiver_priority_heap_init(void); +void simple_archiver_priority_heap_free(SDArchiverPHeap **priority_heap); + +/// If data_cleanup_fn is NULL, then "free()" is used on data when popped or +/// freed. +void simple_archiver_priority_heap_insert(SDArchiverPHeap **priority_heap, + long long priority, void *data, + void (*data_cleanup_fn)(void *)); + +/// Returns NULL if empty or if priority_heap is NULL. +void *simple_archiver_priority_heap_top(SDArchiverPHeap *priority_heap); + +/// Returns NULL if empty or if priority_heap is NULL. +/// When data is popped, the data_cleanup_fn is ignored and the user must take +/// ownership of the returned data pointer. +void *simple_archiver_priority_heap_pop(SDArchiverPHeap *priority_heap); + +#endif diff --git a/src/data_structures/test.c b/src/data_structures/test.c index 721232e..db90957 100644 --- a/src/data_structures/test.c +++ b/src/data_structures/test.c @@ -20,8 +20,10 @@ #include #include +#include "../algorithms/linear_congruential_gen.h" #include "hash_map.h" #include "linked_list.h" +#include "priority_heap.h" static int checks_checked = 0; static int checks_passed = 0; @@ -151,6 +153,114 @@ int main(void) { simple_archiver_hash_map_free(&hash_map); } + // Test PriorityHeap. + { + SDArchiverPHeap *priority_heap = simple_archiver_priority_heap_init(); + simple_archiver_priority_heap_free(&priority_heap); + + priority_heap = simple_archiver_priority_heap_init(); + + // Just 3 elements. + for (unsigned int idx = 0; idx < 3; ++idx) { + unsigned int *data = malloc(sizeof(unsigned int)); + *data = idx; + simple_archiver_priority_heap_insert(&priority_heap, idx, data, NULL); + } + for (unsigned int idx = 0; idx < 3; ++idx) { + unsigned int *data = simple_archiver_priority_heap_top(priority_heap); + CHECK_TRUE(*data == idx); + if (*data != idx) { + printf("idx is %u, data is %u\n", idx, *data); + } + data = simple_archiver_priority_heap_pop(priority_heap); + CHECK_TRUE(*data == idx); + if (*data != idx) { + printf("idx is %u, data is %u\n", idx, *data); + } + free(data); + } + + // 100 elements. + unsigned int max = 100; + + for (unsigned int idx = 0; idx < max; ++idx) { + unsigned int *data = malloc(sizeof(unsigned int)); + *data = idx; + simple_archiver_priority_heap_insert(&priority_heap, idx, data, NULL); + } + + for (unsigned int idx = 0; idx < max; ++idx) { + unsigned int *data = simple_archiver_priority_heap_top(priority_heap); + CHECK_TRUE(*data == idx); + data = simple_archiver_priority_heap_pop(priority_heap); + CHECK_TRUE(*data == idx); + free(data); + } + + // Insert in reverse order. + for (unsigned int idx = max; idx-- > 0;) { + unsigned int *data = malloc(sizeof(unsigned int)); + *data = idx; + simple_archiver_priority_heap_insert(&priority_heap, idx, data, NULL); + } + + for (unsigned int idx = 0; idx < max; ++idx) { + unsigned int *data = simple_archiver_priority_heap_top(priority_heap); + CHECK_TRUE(*data == idx); + data = simple_archiver_priority_heap_pop(priority_heap); + CHECK_TRUE(*data == idx); + free(data); + } + + // Insert in random order. + unsigned int *array = malloc(sizeof(unsigned int) * max); + for (unsigned int idx = 0; idx < max; ++idx) { + array[idx] = idx; + } + + // Deterministic randomization. + for (unsigned int idx = max - 1; idx-- > 0;) { + unsigned int other_idx = simple_archiver_algo_lcg_defaults(idx) % + (unsigned long long)(idx + 1); + if (idx != other_idx) { + unsigned int temp = array[max - 1]; + array[max - 1] = array[other_idx]; + array[other_idx] = temp; + } + } + + // Insert the deterministically randomized array. + for (unsigned int idx = 0; idx < max; ++idx) { + simple_archiver_priority_heap_insert(&priority_heap, array[idx], + array + idx, no_free_fn); + } + + for (unsigned int idx = 0; idx < max; ++idx) { + unsigned int *data = simple_archiver_priority_heap_top(priority_heap); + CHECK_TRUE(*data == idx); + if (*data != idx) { + printf("idx is %u, data is %u\n", idx, *data); + } + data = simple_archiver_priority_heap_pop(priority_heap); + CHECK_TRUE(*data == idx); + if (*data != idx) { + printf("idx is %u, data is %u\n", idx, *data); + } + } + free(array); + + simple_archiver_priority_heap_free(&priority_heap); + + // Insert, don't pop, do free, for memcheck. + priority_heap = simple_archiver_priority_heap_init(); + for (unsigned int idx = 0; idx < max; ++idx) { + unsigned int *data = malloc(sizeof(unsigned int)); + *data = idx; + simple_archiver_priority_heap_insert(&priority_heap, idx, data, NULL); + } + simple_archiver_priority_heap_free(&priority_heap); + } + printf("Checks checked: %u\n", checks_checked); printf("Checks passed: %u\n", checks_passed); return checks_passed == checks_checked ? 0 : 1; diff --git a/src/platforms.h b/src/platforms.h index 950f683..c7fd36a 100644 --- a/src/platforms.h +++ b/src/platforms.h @@ -25,13 +25,13 @@ #define SIMPLE_ARCHIVER_PLATFORM_UNKNOWN 0 #if defined __COSMOPOLITAN__ -# define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN +#define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN #elif defined _WIN32 -# define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_WINDOWS +#define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_WINDOWS #elif defined __APPLE__ -# define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_MAC +#define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_MAC #elif defined __linux__ -# define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_LINUX +#define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_LINUX #else -# define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_UNKNOWN +#define SIMPLE_ARCHIVER_PLATFORM SIMPLE_ARCHIVER_PLATFORM_UNKNOWN #endif