Compare commits

..

25 Commits

Author SHA1 Message Date
Jef Roosens 379a05a7b6 Merge pull request 'Binomial heap' (#4) from Chewing_Bever/libvieter:min-heap into dev
Reviewed-on: vieter-v/libvieter#4
2023-01-27 22:32:02 +01:00
Jef Roosens 167611e6fa
test(heap): test insert after every pop just in case 2023-01-27 21:59:09 +01:00
Jef Roosens 5b2ce6acaa
feat(heap): thread-safety features 2023-01-27 21:23:32 +01:00
Jef Roosens d77b3e4fee
chore: some nitpicking 2023-01-27 20:50:23 +01:00
Jef Roosens ab418e57b3
test: also test with -O3 which can produce extra errors 2023-01-26 12:24:34 +01:00
Jef Roosens a6bdd39776
docs(heap): add readme 2023-01-26 11:03:41 +01:00
Jef Roosens 05b96d1fd6
fix(heap): finally fixed that pop bug 2023-01-26 10:21:30 +01:00
Jef Roosens 3ec2e76af9
refactor(heap): some better variable names; some more tests 2023-01-25 22:12:22 +01:00
Jef Roosens dc557f57ab
test(heap): some more tests to expose flaws 2023-01-25 20:49:18 +01:00
Jef Roosens 63100c5b99
test(heap): add random test that exposes some faults 2023-01-24 21:19:08 +01:00
Jef Roosens 6845e67cb6
feat(heap): possibly working pop 2023-01-24 21:02:29 +01:00
Jef Roosens 95d8c9972b
refactor(heap): combine tree and node into single struct 2023-01-24 17:22:11 +01:00
Jef Roosens 09c488aa0f
feat(heap): not quite working pop 2023-01-24 17:01:37 +01:00
Jef Roosens 6cf4eaaf0b
chore: separate target for each test binary 2023-01-24 14:06:46 +01:00
Jef Roosens 3c8c33b47a
fix(heap): some insert fixes 2023-01-24 12:07:30 +01:00
Jef Roosens c1ad26cf0c
feat(heap): initially working insert 2023-01-24 07:53:51 +01:00
Jef Roosens 16b78b8431 chore: improve ci lint 2023-01-22 13:02:11 +01:00
Jef Roosens 0c673a2751 chore: make lint job check for warnings 2023-01-22 12:46:09 +01:00
Jef Roosens d11d074960 chore: some reorganising 2023-01-22 12:35:55 +01:00
Jef Roosens 13a63d548c docs: added docstrings to public headers 2023-01-22 12:32:47 +01:00
Jef Roosens 8609769389 chore: better separate ci jobs 2023-01-22 12:17:56 +01:00
Jef Roosens 2cc974accc chore: updated readme 2023-01-22 11:56:11 +01:00
Jef Roosens ef625ed14e chore: allow tests to access internal methods 2023-01-22 10:10:17 +01:00
Jef Roosens 6823050c2f refactor(heap): properly organised code 2023-01-22 09:40:35 +01:00
Jef Roosens 050e99b413 feat(heap): code skeleton 2023-01-21 16:31:22 +01:00
16 changed files with 792 additions and 40 deletions

View File

@ -0,0 +1,16 @@
variables:
&image 'git.rustybever.be/chewing_bever/c-devop:alpine3.17'
branches:
exclude: [ main ]
platform: linux/amd64
pipeline:
lint:
image: *image
pull: true
commands:
- make lint
- make objs CFLAGS='-Werror -fsyntax-only'
when:
event: [push, pull_request]

View File

@ -0,0 +1,20 @@
variables:
&image 'git.rustybever.be/chewing_bever/c-devop:alpine3.17'
branches:
exclude: [ main ]
platform: linux/amd64
depends_on:
- test
pipeline:
test:
image: *image
pull: true
commands:
- make test-mem
- make clean
- make test-mem CFLAGS='-O3 -Werror -Wall'
when:
event: [push, pull_request]

View File

@ -11,26 +11,12 @@ branches:
platform: ${PLATFORM} platform: ${PLATFORM}
pipeline: pipeline:
lint: build-and-test:
image: *image image: *image
pull: true pull: true
commands:
- make lint
when:
event: [push, pull_request]
build:
image: *image
commands:
- make
- make clean
- CFLAGS='-O3' make
when:
event: [push, pull_request]
test:
image: *image
commands: commands:
- make test - make test
- make clean
- make test CFLAGS='-O3 -Werror -Wall'
when: when:
event: [push, pull_request] event: [push, pull_request]

View File

@ -8,6 +8,8 @@ SRC_DIR ?= src
TEST_DIR ?= test TEST_DIR ?= test
INC_DIRS ?= include INC_DIRS ?= include
LIB := $(BUILD_DIR)/$(LIB_FILENAME)
SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS != find '$(SRC_DIR)' -iname '*.c'
SRCS_H != find $(INC_DIRS) -iname '*.h' SRCS_H != find $(INC_DIRS) -iname '*.h'
SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c'
@ -17,6 +19,8 @@ OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o)
DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d)
BINS_TEST := $(OBJS_TEST:%.c.o=%) BINS_TEST := $(OBJS_TEST:%.c.o=%)
TARGETS_TEST := $(BINS_TEST:%=test-%)
TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%)
INC_FLAGS := $(addprefix -I,$(INC_DIRS)) INC_FLAGS := $(addprefix -I,$(INC_DIRS))
@ -25,42 +29,61 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS))
# object file is also recompiled if only a header is changed. # object file is also recompiled if only a header is changed.
# -MP: generate a dummy target for every header file (according to the docs it # -MP: generate a dummy target for every header file (according to the docs it
# prevents some errors when removing header files) # prevents some errors when removing header files)
CFLAGS ?= -MMD -MP -Wall -Werror -Wextra CFLAGS ?= -MMD -MP -g
CFLAGS += $(INC_FLAGS) VIETERCFLAGS := $(INC_FLAGS) $(CFLAGS) -Wall -Wextra
.PHONY: all .PHONY: all
all: vieter all: vieter
# =====COMPILATION===== # =====COMPILATION=====
# Utility used by the CI to lint
.PHONY: objs
objs: $(OBJS)
.PHONY: vieter .PHONY: vieter
vieter: $(BUILD_DIR)/$(LIB_FILENAME) vieter: $(LIB)
$(BUILD_DIR)/$(LIB_FILENAME): $(OBJS) $(BUILD_DIR)/$(LIB_FILENAME): $(OBJS)
ar -rcs $@ $(OBJS) ar -rcs $@ $(OBJS)
$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c
mkdir -p $(dir $@) mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(VIETERCFLAGS) -c $< -o $@
# =====TESTING===== # =====TESTING=====
.PHONY: test .PHONY: test
test: build-test test: $(TARGETS_TEST)
@ $(foreach bin,$(BINS_TEST),./$(bin);)
.PHONY: test-mem
test-mem: $(TARGETS_MEM_TEST)
.PHONY: $(TARGETS_TEST)
$(TARGETS_TEST): test-%: %
./$^
.PHONY: $(TARGETS_MEM_TEST)
$(TARGETS_MEM_TEST): test-mem-%: %
valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^
.PHONY: build-test .PHONY: build-test
build-test: $(BINS_TEST) build-test: $(BINS_TEST)
# For simplicity, we link every object file to each of the test files. This # For simplicity, we link every object file to each of the test files. This
# might be changed later if this starts to become too slow. # might be changed later if this starts to become too slow.
$(BINS_TEST): %: %.c.o $(OBJS) $(BINS_TEST): %: %.c.o $(LIB)
$(CC) $^ -o $@ $(CC) \
$^ -o $@
# Each test includes the test directory, which contains the acutest header file # Along with the include directory, each test includes $(TEST_DIR) (which
# contains the acutest.h header file), and the src directory of the module it's
# testing. This allows tests to access internal methods, which aren't publicly
# exposed.
$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
mkdir -p $(dir $@) mkdir -p $(dir $@)
$(CC) $(CFLAGS) -I$(TEST_DIR) -c $< -o $@ $(CC) $(VIETERCFLAGS) -I$(TEST_DIR) \
-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
-c $< -o $@
# =====MAINTENANCE===== # =====MAINTENANCE=====
.PHONY: lint .PHONY: lint

View File

@ -1,15 +1,12 @@
# libvieter # libvieter
This library powers part of Vieter, most noteably the sections that can easily This library powers part of Vieter, most notably the sections that can easily
be implemented in C (or just parts I want to implement in C because it's fun). be implemented in C (or just parts I want to implement in C because it's fun).
The goal of this library is to be completely self-contained, meaning any The goal of this library is to be as self-contained as possible; data
required data structures have to be implemented as well. It can only depend on structures should be implemented manually if possible.
the C standard libraries.
Currently it contains the following: See the [source code](src) for the list of modules.
* Cron expression parser & next time calculator
## Development ## Development
@ -18,13 +15,41 @@ Currently it contains the following:
Everything is handled by the provided Makefile. To compile the static library, Everything is handled by the provided Makefile. To compile the static library,
simply run `make`. simply run `make`.
### Project structure
Each module has its own subdirectory inside `src`, e.g. `src/cron`. This
directory contains the actual implementation of a module, along with any
internally used header files. Each internal function should be defined in a
header file, as to make testing these possible.
Each module should also have its own header file inside the `include`
directory. This header file defines the public API that the library exposes for
this specific module.
Any code in a module may only import internal headers from that module, along
with any of the public API header files. Modules should not depend on each
other's internal implementationns.
Each module should contain a README describing its contents.
All file names, function names... (even internals) should follow snake case
convention and have a prefix unique to that module, starting with `vieter_`.
For example, the `cron` modules uses the `vieter_cron_` prefix for everything.
Header files should only import what they explicitely need. If some function is
only used in a .c file, the import should be placed in the .c file instead.
### Testing ### Testing
This library uses [Acutest](https://github.com/mity/acutest) for its tests. This library uses [Acutest](https://github.com/mity/acutest) for its tests.
Tests should be placed in the `test` subdirectory, further divided into Tests should be placed in the `test` subdirectory, further divided into
directories that correspond those in `src`. Test files should begin with directories that correspond to those in `src`. Test files should begin with
`test_`, and their format should follow the expected format for Acutest. `test_`, and their format should follow the expected format for Acutest.
Each `test_` is compiled separately into a binary, linked with libvieter. A
test file can import any of the public API header files, along with any header
files defined in its respective module. This allows testing internal functions.
To run the tests, simply run `make test`. If you wish to only run a specific To run the tests, simply run `make test`. If you wish to only run a specific
test binary, you can find them in `build/test`. test binary, you can find them in `build/test`.

View File

@ -1,7 +1,6 @@
#ifndef VIETER_CRON #ifndef VIETER_CRON
#define VIETER_CRON #define VIETER_CRON
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -35,18 +34,38 @@ typedef struct vieter_cron_simple_time {
int minute; int minute;
} vieter_cron_simple_time; } vieter_cron_simple_time;
vieter_cron_expression *ce_init(); /*
* Allocate and initialize a new empty cron expression.
*/
vieter_cron_expression *vieter_cron_expr_init();
/*
* Deallocate a cron expression.
*/
void vieter_cron_expr_free(vieter_cron_expression *ce); void vieter_cron_expr_free(vieter_cron_expression *ce);
/*
* Given a cron expression and a reference time, calculate the next time after
* the reference time that this expression matches.
*/
void vieter_cron_expr_next(vieter_cron_simple_time *out, void vieter_cron_expr_next(vieter_cron_simple_time *out,
vieter_cron_expression *ce, vieter_cron_expression *ce,
vieter_cron_simple_time *ref); vieter_cron_simple_time *ref);
/*
* Convencience wrapper around vieter_cron_expr_next that uses the current time
* as the reference time.
*/
void vieter_cron_expr_next_from_now(vieter_cron_simple_time *out, void vieter_cron_expr_next_from_now(vieter_cron_simple_time *out,
vieter_cron_expression *ce); vieter_cron_expression *ce);
enum vieter_cron_parse_error vieter_cron_expr_parse(vieter_cron_expression *out, /*
* Try to parse a string into a cron expression. Note that the cron expression
* is updated in-place, meaning it can contain invalid information if the
* function returns an error. The cron expression should only be used if the
* function succeeded.
*/
vieter_cron_parse_error vieter_cron_expr_parse(vieter_cron_expression *out,
const char *expression); const char *expression);
#endif #endif

View File

@ -0,0 +1,62 @@
#ifndef VIETER_HEAP
#define VIETER_HEAP
#include <stdint.h>
typedef struct vieter_heap vieter_heap;
typedef enum vieter_heap_error {
vieter_heap_ok = 0,
vieter_heap_empty = 1
} vieter_heap_error;
/*
* Allocate and initialize an empty heap.
*/
vieter_heap *vieter_heap_init();
/*
* Deallocate a heap.
*/
void vieter_heap_free(vieter_heap *heap);
/*
* Return how many elements are currently in the heap.
*/
uint64_t vieter_heap_size(vieter_heap *heap);
/*
* Insert a new value into the heap.
*/
vieter_heap_error vieter_heap_insert(vieter_heap *heap, uint64_t key,
void *data);
/*
* Remove the smallest element from the heap.
*/
vieter_heap_error vieter_heap_pop(void **out, vieter_heap *heap);
/*
* Get the smallest element in the heap without removing it.
*/
vieter_heap_error vieter_heap_peek(void **out, vieter_heap *heap);
/*
* Acquire a read lock on the heap. Return value is the result of
* pthread_rwlock_rdlock.
*/
int vieter_heap_rlock(vieter_heap *heap);
/*
* Acquire a write lock on the heap. Return value is the result of
* pthread_rwlock_wrlock.
*/
int vieter_heap_wlock(vieter_heap *heap);
/*
* Unlock the lock after having acquired it. Return value is the result of
* pthread_rwlock_unlock.
*/
int vieter_heap_unlock(vieter_heap *heap);
#endif

View File

@ -1,4 +1,8 @@
#include "vieter_cron.h" #include "vieter_cron.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// This prefix is needed to properly compile // This prefix is needed to properly compile
const uint8_t parse_month_days[] = {31, 28, 31, 30, 31, 30, const uint8_t parse_month_days[] = {31, 28, 31, 30, 31, 30,

33
src/heap/README.md 100644
View File

@ -0,0 +1,33 @@
This min-heap implementation is a pretty standard binomial heap.
## Representation in memory
A heap consists of one or more binomial trees, each with a different order `k`
and `2^k` total nodes. This heap can contain `2^64 - 1` elements at most, which
is far more than your memory can contain, but it's still fun to mention.
A tree does not have its own memory structure; a node that's the root of a
binomial tree is simply called the tree.
Each node has the following layout:
```c
typedef struct vieter_heap_node {
uint64_t key;
void *data;
struct vieter_heap_node *largest_order;
union {
struct vieter_heap_node *next_tree;
struct vieter_heap_node *next_largest_order;
} ptr;
uint8_t order;
} vieter_heap_node;
```
Each node has a pointer to its child with the largest order (if the node's
order is `0`, this pointer will be NULL). Each non-root node has a pointer to
its sibling with the next-highest order. These pointers allow the children of a
binomial tree to be recombined into a new tree, once their root has been
pop'ed.
Roots point to the binomial tree in the heap with the next largest order.

View File

@ -0,0 +1,96 @@
#include "vieter_heap_internal.h"
#include <stdlib.h>
vieter_heap *vieter_heap_init() {
vieter_heap *heap = calloc(1, sizeof(vieter_heap));
pthread_rwlock_init(&heap->lock, NULL);
return heap;
}
uint64_t vieter_heap_size(vieter_heap *heap) {
uint64_t size = 0;
vieter_heap_node *tree = heap->tree;
while (tree != NULL) {
size |= (uint64_t)1 << tree->order;
tree = tree->ptr.next_tree;
}
return size;
}
void vieter_heap_free(vieter_heap *heap) {
vieter_heap_node *tree = heap->tree;
vieter_heap_node *next;
while (tree != NULL) {
next = tree->ptr.next_tree;
vieter_heap_tree_free(tree);
tree = next;
}
free(heap);
}
vieter_heap_error vieter_heap_insert(vieter_heap *heap, uint64_t key,
void *data) {
vieter_heap_node *new_tree = vieter_heap_node_init();
new_tree->key = key;
new_tree->data = data;
new_tree->order = 0;
if (heap->tree == NULL) {
heap->tree = new_tree;
} else {
heap->tree = vieter_heap_tree_merge(heap->tree, new_tree);
}
return vieter_heap_ok;
}
vieter_heap_error vieter_heap_pop(void **out, vieter_heap *heap) {
if (heap->tree == NULL) {
return vieter_heap_empty;
}
heap->tree = vieter_heap_tree_pop(out, heap->tree);
return vieter_heap_ok;
}
vieter_heap_error vieter_heap_peek(void **out, vieter_heap *heap) {
if (heap->tree == NULL) {
return vieter_heap_empty;
}
vieter_heap_node *tree = heap->tree;
uint64_t smallest_key = tree->key;
*out = tree->data;
while (tree->ptr.next_tree != NULL) {
tree = tree->ptr.next_tree;
if (tree->key < smallest_key) {
smallest_key = tree->key;
*out = tree->data;
}
}
return vieter_heap_ok;
}
int vieter_heap_rlock(vieter_heap *heap) {
return pthread_rwlock_rdlock(&heap->lock);
}
int vieter_heap_wlock(vieter_heap *heap) {
return pthread_rwlock_wrlock(&heap->lock);
}
int vieter_heap_unlock(vieter_heap *heap) {
return pthread_rwlock_unlock(&heap->lock);
}

View File

@ -0,0 +1,8 @@
#include "vieter_heap.h"
#include "vieter_heap_tree.h"
#include <pthread.h>
struct vieter_heap {
vieter_heap_node *tree;
pthread_rwlock_t lock;
};

View File

@ -0,0 +1,192 @@
#include "vieter_heap_tree.h"
vieter_heap_node *vieter_heap_node_init() {
return calloc(1, sizeof(vieter_heap_node));
}
void vieter_heap_node_free(vieter_heap_node *node) { free(node); }
void vieter_heap_tree_free(vieter_heap_node *root) {
if (root->order == 0) {
goto end;
}
uint64_t size = 1;
vieter_heap_node **stack =
malloc(((uint64_t)1 << root->order) * sizeof(vieter_heap_node *));
stack[0] = root->largest_order;
vieter_heap_node *node;
while (size > 0) {
node = stack[size - 1];
size--;
if (node->largest_order != NULL) {
stack[size] = node->largest_order;
size++;
}
if (node->ptr.next_largest_order != NULL) {
stack[size] = node->ptr.next_largest_order;
size++;
}
vieter_heap_node_free(node);
}
free(stack);
end:
vieter_heap_node_free(root);
}
vieter_heap_node *vieter_heap_tree_merge_same_order(vieter_heap_node *root_a,
vieter_heap_node *root_b) {
vieter_heap_node *root, *child;
if (root_a->key <= root_b->key) {
root = root_a;
child = root_b;
} else {
root = root_b;
child = root_a;
}
child->ptr.next_largest_order = root->largest_order;
root->largest_order = child;
root->order++;
return root;
}
vieter_heap_node *vieter_heap_tree_merge(vieter_heap_node *target_tree,
vieter_heap_node *other_tree) {
vieter_heap_node *out = target_tree;
vieter_heap_node *next_other_tree, *next_target_tree;
vieter_heap_node *previous_target_tree = NULL;
while (target_tree != NULL && other_tree != NULL) {
if (target_tree->order == other_tree->order) {
next_other_tree = other_tree->ptr.next_tree;
next_target_tree = target_tree->ptr.next_tree;
target_tree = vieter_heap_tree_merge_same_order(target_tree, other_tree);
target_tree->ptr.next_tree = next_target_tree;
// If this merge produces a binomial tree whose size is already in
// target, it will be the next target. Therefore, we can merge target's
// trees until we no longer have a duplicate depth.
while (next_target_tree != NULL &&
next_target_tree->order == target_tree->order) {
next_target_tree = next_target_tree->ptr.next_tree;
target_tree = vieter_heap_tree_merge_same_order(
target_tree, target_tree->ptr.next_tree);
target_tree->ptr.next_tree = next_target_tree;
}
if (previous_target_tree != NULL) {
previous_target_tree->ptr.next_tree = target_tree;
} else {
out = target_tree;
}
other_tree = next_other_tree;
} else if (target_tree->order > other_tree->order) {
next_other_tree = other_tree->ptr.next_tree;
if (previous_target_tree == NULL) {
previous_target_tree = other_tree;
out = other_tree;
} else {
previous_target_tree->ptr.next_tree = other_tree;
// This single missing line right here broke this entire function for
// nearly a week.
previous_target_tree = other_tree;
}
other_tree->ptr.next_tree = target_tree;
other_tree = next_other_tree;
} else {
if (previous_target_tree == NULL) {
out = target_tree;
}
previous_target_tree = target_tree;
target_tree = target_tree->ptr.next_tree;
}
}
// Append final part of tree to target
if (target_tree == NULL) {
previous_target_tree->ptr.next_tree = other_tree;
}
return out;
}
vieter_heap_node *vieter_heap_tree_pop(void **out, vieter_heap_node *tree) {
vieter_heap_node *tree_before_smallest = NULL;
vieter_heap_node *previous_tree = NULL;
vieter_heap_node *original_root = tree;
uint64_t smallest_key = tree->key;
while (tree->ptr.next_tree != NULL) {
previous_tree = tree;
tree = tree->ptr.next_tree;
if (tree->key < smallest_key) {
smallest_key = tree->key;
tree_before_smallest = previous_tree;
}
}
vieter_heap_node *tree_to_pop;
if (tree_before_smallest != NULL) {
tree_to_pop = tree_before_smallest->ptr.next_tree;
tree_before_smallest->ptr.next_tree = tree_to_pop->ptr.next_tree;
} else {
tree_to_pop = original_root;
original_root = original_root->ptr.next_tree;
}
*out = tree_to_pop->data;
if (tree_to_pop->order == 0) {
vieter_heap_tree_free(tree_to_pop);
return original_root;
}
// Each child has a pointer to its sibling with the next largest order. If we
// want to convert this list of children into their own tree, these pointers
// have to be reversed.
previous_tree = tree_to_pop->largest_order;
vieter_heap_node_free(tree_to_pop);
tree = previous_tree->ptr.next_largest_order;
previous_tree->ptr.next_tree = NULL;
vieter_heap_node *next_tree;
while (tree != NULL) {
next_tree = tree->ptr.next_largest_order;
tree->ptr.next_tree = previous_tree;
previous_tree = tree;
tree = next_tree;
}
// original_root is zero if the heap only contained a single tree.
if (original_root != NULL) {
return vieter_heap_tree_merge(original_root, previous_tree);
} else {
return previous_tree;
}
}

View File

@ -0,0 +1,53 @@
#ifndef VIETER_HEAP_TREE
#define VIETER_HEAP_TREE
#include <stdint.h>
#include <stdlib.h>
typedef struct vieter_heap_node {
uint64_t key;
void *data;
struct vieter_heap_node *largest_order;
union {
// Roots point to next tree in the heap, other nodes point to their first
// neighbour.
struct vieter_heap_node *next_tree;
struct vieter_heap_node *next_largest_order;
} ptr;
uint8_t order;
} vieter_heap_node;
/*
* Allocate and initialize a heap node object.
*/
vieter_heap_node *vieter_heap_node_init();
/*
* Deallocate a node object.
*/
void vieter_heap_node_free(vieter_heap_node *node);
/*
* Deallocate a node's entire structure.
*/
void vieter_heap_tree_free(vieter_heap_node *root);
/*
* Given the roots of the smallest trees in two heaps, merge them into a single
* large heap.
*/
vieter_heap_node *vieter_heap_tree_merge(vieter_heap_node *root_a, vieter_heap_node *root_b);
/*
* Given the roots of two trees of the same order, merge them into a heap of one
* order larger.
*/
vieter_heap_node *vieter_heap_tree_merge_same_order(vieter_heap_node *root_a,
vieter_heap_node *root_b);
/*
* Remove the smallest element from the given heap.
*/
vieter_heap_node *vieter_heap_tree_pop(void **out, vieter_heap_node *root);
#endif

View File

@ -0,0 +1,187 @@
#include "acutest.h"
#include "vieter_heap_internal.h"
#include <stdlib.h>
#define TEST_SIZE(heap, size) \
TEST_CHECK(vieter_heap_size(heap) == size); \
TEST_MSG("Size: %zu, expected: %lu", vieter_heap_size(heap), (uint64_t)size)
void test_init() {
vieter_heap *heap = vieter_heap_init();
TEST_CHECK(heap != NULL);
TEST_SIZE(heap, 0);
vieter_heap_free(heap);
}
void count_nodes(uint64_t *counter, vieter_heap_node *root) {
(*counter)++;
if (root->largest_order != NULL) {
count_nodes(counter, root->largest_order);
}
// This will also traverse the various trees
if (root->ptr.next_largest_order != NULL) {
count_nodes(counter, root->ptr.next_largest_order);
}
}
uint64_t count_nodes_heap(vieter_heap *heap) {
uint64_t counter = 0;
if (heap->tree != NULL) {
count_nodes(&counter, heap->tree);
}
return counter;
}
void test_insert() {
vieter_heap *heap = vieter_heap_init();
TEST_SIZE(heap, 0);
void *data;
for (uint64_t i = 50; i > 0; i--) {
vieter_heap_insert(heap, i, (void *)i);
TEST_SIZE(heap, (uint64_t)51 - i);
TEST_CHECK(count_nodes_heap(heap) == (uint64_t)51 - i);
data = 0;
TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok);
TEST_CHECK_(data == (void *)i, "%lX == %lX", (uint64_t)data, i);
}
vieter_heap_free(heap);
}
void test_insert_random() {
srand(1);
vieter_heap *heap = vieter_heap_init();
TEST_SIZE(heap, 0);
uint64_t num = rand();
uint64_t smallest = num;
void *data = NULL;
for (uint64_t i = 0; i < 5000; i++) {
vieter_heap_insert(heap, num, (void *)num);
TEST_SIZE(heap, i + 1);
TEST_CHECK(count_nodes_heap(heap) == (uint64_t)i + 1);
if (num < smallest) {
smallest = num;
}
TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok);
TEST_CHECK(data == (void *)smallest);
data = NULL;
num = rand();
}
vieter_heap_free(heap);
}
void test_pop() {
const uint64_t n = 500;
vieter_heap *heap = vieter_heap_init();
TEST_SIZE(heap, 0);
void *data;
for (uint64_t i = n; i > 0; i--) {
vieter_heap_insert(heap, i, (void *)i);
TEST_SIZE(heap, (uint64_t)n + 1 - i);
TEST_CHECK(count_nodes_heap(heap) == (uint64_t)n + 1 - i);
TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok);
TEST_CHECK(data == (void*)i);
}
data = NULL;
for (uint64_t i = 1; i <= n; i++) {
TEST_CHECK(vieter_heap_pop(&data, heap) == vieter_heap_ok);
TEST_CHECK(data == (void*)i);
TEST_SIZE(heap, (uint64_t)n - i);
}
vieter_heap_free(heap);
}
int uint64_t_compare(const void *a, const void *b) {
if ((*(uint64_t *)a) < (*(uint64_t *)b)) {
return -1;
} else if ((*(uint64_t *)a) > (*(uint64_t *)b)) {
return 1;
} else {
return 0;
}
}
void test_pop_random() {
const uint64_t n = 500;
srand(0);
vieter_heap *heap = vieter_heap_init();
uint64_t *numbers = malloc(n * sizeof(uint64_t));
uint64_t num;
for (uint64_t i = 0; i < n; i++) {
num = rand();
vieter_heap_insert(heap, num, (void *)num);
TEST_SIZE(heap, i + 1);
TEST_CHECK(count_nodes_heap(heap) == i + 1);
numbers[i] = num;
}
qsort(numbers, n, sizeof(uint64_t), uint64_t_compare);
void *data = NULL;
for (uint64_t i = 0; i < n; i++) {
TEST_CHECK(vieter_heap_peek(&data, heap) == vieter_heap_ok);
TEST_CHECK_(data == (void *)numbers[i], "peek %lx == %lx", (uint64_t)data, numbers[i]);
data = NULL;
TEST_CHECK(vieter_heap_pop(&data, heap) == vieter_heap_ok);
TEST_CHECK_(data == (void *)numbers[i], "pop %lx == %lx", (uint64_t)data, numbers[i]);
TEST_SIZE(heap, n - i - 1);
TEST_CHECK(count_nodes_heap(heap) == n - i - 1);
// Assure each size is also a valid heap after inserting
vieter_heap_insert(heap, numbers[i], (void *)numbers[i]);
TEST_SIZE(heap, n - i);
TEST_CHECK(count_nodes_heap(heap) == n - i);
data = NULL;
TEST_CHECK(vieter_heap_pop(&data, heap) == vieter_heap_ok);
TEST_CHECK_(data == (void *)numbers[i], "pop %lx == %lx", (uint64_t)data, numbers[i]);
TEST_SIZE(heap, n - i - 1);
TEST_CHECK(count_nodes_heap(heap) == n - i - 1);
}
vieter_heap_free(heap);
free(numbers);
}
TEST_LIST = {
{"init", test_init},
{"insert", test_insert},
{"insert random", test_insert_random},
{"pop", test_pop},
{"pop random", test_pop_random},
{NULL, NULL}
};

View File

@ -0,0 +1,28 @@
#include "acutest.h"
#include "vieter_heap.h"
#include "vieter_heap_tree.h"
#include <stdlib.h>
void test_merge_same_order() {
vieter_heap_node *root_a = vieter_heap_node_init();
root_a->key = 1;
root_a->order = 0;
vieter_heap_node *root_b = vieter_heap_node_init();
root_b->key = 2;
root_b->order = 0;
vieter_heap_node *merged = vieter_heap_tree_merge_same_order(root_a, root_b);
TEST_CHECK(merged == root_a);
TEST_CHECK(merged->key == 1);
TEST_CHECK(merged->largest_order == root_b);
TEST_CHECK(merged->ptr.next_largest_order == NULL);
vieter_heap_tree_free(merged);
}
TEST_LIST = {
{"merge same order", test_merge_same_order},
{NULL, NULL}
};