forked from vieter-v/libvieter
Compare commits
8 Commits
9452fea2ab
...
efbd9eaef9
Author | SHA1 | Date |
---|---|---|
GreekStapler | efbd9eaef9 | |
GreekStapler | c94ab920a4 | |
GreekStapler | 1da345f8b3 | |
GreekStapler | 3d69c8edeb | |
GreekStapler | 31fc4e2989 | |
GreekStapler | 62d1081d38 | |
GreekStapler | d252c55fb0 | |
GreekStapler | cc3cacc1f7 |
|
@ -11,12 +11,26 @@ branches:
|
||||||
platform: ${PLATFORM}
|
platform: ${PLATFORM}
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
build-and-test:
|
lint:
|
||||||
image: *image
|
image: *image
|
||||||
pull: true
|
pull: true
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make lint
|
||||||
- make clean
|
when:
|
||||||
- make test CFLAGS='-O3 -Werror -Wall'
|
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
|
||||||
when:
|
when:
|
||||||
event: [push, pull_request]
|
event: [push, pull_request]
|
|
@ -1,16 +0,0 @@
|
||||||
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]
|
|
|
@ -1,20 +0,0 @@
|
||||||
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]
|
|
46
Makefile
46
Makefile
|
@ -8,8 +8,6 @@ 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'
|
||||||
|
@ -19,8 +17,6 @@ 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))
|
||||||
|
|
||||||
|
@ -29,66 +25,42 @@ 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 -g
|
CFLAGS ?= -MMD -MP -Wall -Werror -Wextra
|
||||||
VIETERCFLAGS := $(INC_FLAGS) $(CFLAGS) -Wall -Wextra
|
CFLAGS += $(INC_FLAGS)
|
||||||
|
|
||||||
.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: $(LIB)
|
vieter: $(BUILD_DIR)/$(LIB_FILENAME)
|
||||||
$(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) $(VIETERCFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
|
||||||
# =====TESTING=====
|
# =====TESTING=====
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: $(TARGETS_TEST)
|
test: build-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.
|
||||||
<<<<<<< HEAD
|
|
||||||
$(BINS_TEST): %: %.c.o $(LIB)
|
|
||||||
$(CC) \
|
|
||||||
$^ -o $@
|
|
||||||
=======
|
|
||||||
$(BINS_TEST): %: %.c.o $(OBJS)
|
$(BINS_TEST): %: %.c.o $(OBJS)
|
||||||
$(CC) $^ -larchive -o $@
|
$(CC) $^ -larchive -o $@
|
||||||
>>>>>>> c94ab92 (refactor: Add libarchive link to test compilation area of the Makefile. Created test units with xcursor-dmz as test package.)
|
|
||||||
|
|
||||||
# Along with the include directory, each test includes $(TEST_DIR) (which
|
# Each test includes the test directory, which contains the acutest header file
|
||||||
# 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) $(VIETERCFLAGS) -I$(TEST_DIR) \
|
$(CC) $(CFLAGS) -I$(TEST_DIR) -c $< -o $@
|
||||||
-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
|
|
||||||
-c $< -o $@
|
|
||||||
|
|
||||||
# =====MAINTENANCE=====
|
# =====MAINTENANCE=====
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
|
|
41
README.md
41
README.md
|
@ -1,12 +1,15 @@
|
||||||
# libvieter
|
# libvieter
|
||||||
|
|
||||||
This library powers part of Vieter, most notably the sections that can easily
|
This library powers part of Vieter, most noteably 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 as self-contained as possible; data
|
The goal of this library is to be completely self-contained, meaning any
|
||||||
structures should be implemented manually if possible.
|
required data structures have to be implemented as well. It can only depend on
|
||||||
|
the C standard libraries.
|
||||||
|
|
||||||
See the [source code](src) for the list of modules.
|
Currently it contains the following:
|
||||||
|
|
||||||
|
* Cron expression parser & next time calculator
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
@ -15,41 +18,13 @@ See the [source code](src) for the list of modules.
|
||||||
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 to those in `src`. Test files should begin with
|
directories that correspond 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`.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#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>
|
||||||
|
@ -34,38 +35,18 @@ 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
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
#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,8 +1,4 @@
|
||||||
#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,
|
|
@ -1,33 +0,0 @@
|
||||||
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.
|
|
|
@ -1,96 +0,0 @@
|
||||||
#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);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
#include "vieter_heap.h"
|
|
||||||
#include "vieter_heap_tree.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
struct vieter_heap {
|
|
||||||
vieter_heap_node *tree;
|
|
||||||
pthread_rwlock_t lock;
|
|
||||||
};
|
|
|
@ -1,192 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
#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
|
|
|
@ -1,187 +0,0 @@
|
||||||
#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}
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
#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