Binomial heap #4
			
				
			
		
		
		
	|  | @ -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] | ||||
|  | @ -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] | ||||
|  | @ -11,26 +11,12 @@ branches: | |||
| platform: ${PLATFORM} | ||||
| 
 | ||||
| pipeline: | ||||
|   lint: | ||||
|   build-and-test: | ||||
|     image: *image | ||||
|     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: | ||||
|       - make test | ||||
|       - make clean | ||||
|       - make test CFLAGS='-O3 -Werror -Wall' | ||||
|     when: | ||||
|       event: [push, pull_request] | ||||
							
								
								
									
										45
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										45
									
								
								Makefile
								
								
								
								
							|  | @ -8,6 +8,8 @@ SRC_DIR ?= src | |||
| TEST_DIR ?= test | ||||
| INC_DIRS ?= include | ||||
| 
 | ||||
| LIB := $(BUILD_DIR)/$(LIB_FILENAME) | ||||
| 
 | ||||
| SRCS != find '$(SRC_DIR)' -iname '*.c' | ||||
| SRCS_H != find $(INC_DIRS) -iname '*.h' | ||||
| 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) | ||||
| 
 | ||||
| BINS_TEST := $(OBJS_TEST:%.c.o=%) | ||||
| TARGETS_TEST := $(BINS_TEST:%=test-%) | ||||
| TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%) | ||||
| 
 | ||||
| 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.
 | ||||
| # -MP: generate a dummy target for every header file (according to the  docs it
 | ||||
| #  prevents some errors when removing header files)
 | ||||
| CFLAGS ?= -MMD -MP -Wall -Werror -Wextra | ||||
| CFLAGS += $(INC_FLAGS) | ||||
| CFLAGS ?= -MMD -MP -g | ||||
| VIETERCFLAGS := $(INC_FLAGS) $(CFLAGS) -Wall -Wextra | ||||
| 
 | ||||
| .PHONY: all | ||||
| all: vieter | ||||
| 
 | ||||
| 
 | ||||
| # =====COMPILATION=====
 | ||||
| # Utility used by the CI to lint
 | ||||
| .PHONY: objs | ||||
| objs: $(OBJS) | ||||
| 
 | ||||
| .PHONY: vieter | ||||
| vieter: $(BUILD_DIR)/$(LIB_FILENAME) | ||||
| vieter: $(LIB) | ||||
| $(BUILD_DIR)/$(LIB_FILENAME): $(OBJS) | ||||
| 	ar -rcs $@ $(OBJS) | ||||
| 
 | ||||
| $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c | ||||
| 	mkdir -p $(dir $@) | ||||
| 	$(CC) $(CFLAGS) -c $< -o $@ | ||||
| 	$(CC) $(VIETERCFLAGS) -c $< -o $@ | ||||
| 
 | ||||
| 
 | ||||
| # =====TESTING=====
 | ||||
| .PHONY: test | ||||
| test: build-test | ||||
| 	@ $(foreach bin,$(BINS_TEST),./$(bin);) | ||||
| test: $(TARGETS_TEST) | ||||
| 
 | ||||
| .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 | ||||
| build-test: $(BINS_TEST) | ||||
| 
 | ||||
| # 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.
 | ||||
| $(BINS_TEST): %: %.c.o $(OBJS) | ||||
| 	$(CC) $^ -o $@ | ||||
| $(BINS_TEST): %: %.c.o $(LIB) | ||||
| 	$(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 | ||||
| 	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=====
 | ||||
| .PHONY: lint | ||||
|  |  | |||
							
								
								
									
										41
									
								
								README.md
								
								
								
								
							
							
						
						
									
										41
									
								
								README.md
								
								
								
								
							|  | @ -1,15 +1,12 @@ | |||
| # 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). | ||||
| 
 | ||||
| The goal of this library is to be completely self-contained, meaning any | ||||
| required data structures have to be implemented as well. It can only depend on | ||||
| the C standard libraries. | ||||
| The goal of this library is to be as self-contained as possible; data | ||||
| structures should be implemented manually if possible. | ||||
| 
 | ||||
| Currently it contains the following: | ||||
| 
 | ||||
| * Cron expression parser & next time calculator | ||||
| See the [source code](src) for the list of modules. | ||||
| 
 | ||||
| ## Development | ||||
| 
 | ||||
|  | @ -18,13 +15,41 @@ Currently it contains the following: | |||
| Everything is handled by the provided Makefile. To compile the static library, | ||||
| 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 | ||||
| 
 | ||||
| This library uses [Acutest](https://github.com/mity/acutest) for its tests. | ||||
| 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. | ||||
| 
 | ||||
| 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 | ||||
| test binary, you can find them in `build/test`. | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| #ifndef VIETER_CRON | ||||
| #define VIETER_CRON | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
|  | @ -35,18 +34,38 @@ typedef struct vieter_cron_simple_time { | |||
|   int minute; | ||||
| } 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); | ||||
| 
 | ||||
| /*
 | ||||
|  * 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, | ||||
|                            vieter_cron_expression *ce, | ||||
|                            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, | ||||
|                                     vieter_cron_expression *ce); | ||||
| 
 | ||||
| enum vieter_cron_parse_error vieter_cron_expr_parse(vieter_cron_expression *out, | ||||
|                                                     const char *expression); | ||||
| /*
 | ||||
|  * 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); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -1,4 +1,8 @@ | |||
| #include "vieter_cron.h" | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| // This prefix is needed to properly compile
 | ||||
| const uint8_t parse_month_days[] = {31, 28, 31, 30, 31, 30, | ||||
|  | @ -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. | ||||
|  | @ -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); | ||||
| } | ||||
|  | @ -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; | ||||
| }; | ||||
|  | @ -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; | ||||
|   } | ||||
| } | ||||
|  | @ -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 | ||||
|  | @ -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} | ||||
| }; | ||||
|  | @ -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} | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue