diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e1f979..b319534 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,9 @@ target_link_libraries(coroutine LINK_PUBLIC allocation) add_library(default_allocator src/default_allocator.c) set(ALLOCATORS default_allocator) +add_library(heap src/heap.c) +target_link_libraries(heap LINK_PUBLIC allocation) + if(HAVE_MMAP) add_library(mmap_allocator src/mmap_allocator.c) set(ALLOCATORS ${ALLOCATORS} mmap_allocator) @@ -33,3 +36,5 @@ add_executable(test-allocation test/allocation.c) target_link_libraries(test-allocation LINK_PUBLIC allocation ${ALLOCATORS}) add_executable(test-coroutine test/coroutine.c) target_link_libraries(test-coroutine LINK_PUBLIC coroutine ${ALLOCATORS}) +add_executable(test-heap test/heap.c) +target_link_libraries(test-heap LINK_PUBLIC heap ${ALLOCATORS}) diff --git a/src/heap.c b/src/heap.c new file mode 100644 index 0000000..9413bd6 --- /dev/null +++ b/src/heap.c @@ -0,0 +1,134 @@ +/* threadless.io + * Copyright (c) 2016 Justin R. Cutler + * Licensed under the MIT License. See LICENSE file in the project root for + * full license information. + */ +/** @file + * heap interface implementation + * @author Justin R. Cutler + */ + +/* size_t, NULL */ +#include + +/* allocation_realloc_array */ +#include +/* ... */ +#include + + +static inline void swap(heap_node_t **storage, size_t a, size_t b) +{ + /* swap elements */ + heap_node_t *tmp = storage[a]; + storage[a] = storage[b]; + storage[b] = tmp; + /* update back references */ + storage[a]->index = a; + storage[b]->index = b; +} + + +static void sift_down(const heap_t *heap, size_t start, size_t pos) +{ + heap_node_t **storage = heap->allocation.memory; + while (pos > start) { + /* if node pos is "better" than parent, swap them */ + size_t parent = (pos - 1) >> 1; + if (heap->compare(storage[pos], storage[parent]) < 0) { + swap(storage, pos, parent); + pos = parent; + } else { + /* node pos is in the correct location */ + break; + } + } +} + + +static void sift_up(const heap_t *heap, size_t pos, size_t end) +{ + heap_node_t **storage = heap->allocation.memory; + size_t start = pos; + size_t child = (pos << 1) + 1; + /* bubble smaller child up until hitting a leaf */ + while (child < end) { + size_t right = child + 1; + if (right < end && heap->compare(storage[right], storage[child]) < 0) { + child = right; + } + /* move smaller child up */ + swap(storage, pos, child); + pos = child; + child = (pos << 1) + 1; + } + /* bubble node originally at pos into place */ + sift_down(heap, start, pos); +} + + +int heap_push(heap_t *heap, heap_node_t *node) +{ + int error = allocation_realloc_array(&(heap->allocation), heap->count + 1, + sizeof(heap_node_t *)); + if (!error) { + heap_node_t **storage = heap->allocation.memory; + /* place item at end of heap */ + node->heap = heap; + node->index = heap->count++; + storage[node->index] = node; + /* bubble item into place */ + sift_down(heap, 0, node->index); + } + return error; +} + + +void heap_remove(heap_node_t *node) +{ + heap_t *heap = node->heap; + size_t pos = node->index; + + if (heap->count) { + /* shrink heap */ + heap->count--; + if (pos != heap->count) { + /* exchange previous last element for removed element */ + swap(heap->allocation.memory, pos, heap->count); + /* shrink storage */ + (void) allocation_realloc_array(&(heap->allocation), heap->count, + sizeof(heap_node_t *)); + /* restore heap invariant */ + sift_up(heap, pos, heap->count); + } + } + + /* disassociate node from heap */ + node->heap = NULL; + node->index = 0; +} + + +heap_node_t *heap_pop(heap_t *heap) +{ + heap_node_t *node = heap_peek(heap); + + if (NULL != node) { + heap_remove(node); + } + + return node; +} + + +void heap_fini(heap_t *heap) +{ + /* diassociate all remaining nodes from heap */ + heap_node_t **storage = heap->allocation.memory; + size_t i; + for (i = 0; i < heap->count; ++i) { + storage[i]->heap = NULL; + storage[i]->index = 0; + } + allocation_free(&(heap->allocation)); +} diff --git a/test/heap.c b/test/heap.c new file mode 100644 index 0000000..991c783 --- /dev/null +++ b/test/heap.c @@ -0,0 +1,117 @@ +/* threadless.io + * Copyright (c) 2016 Justin R. Cutler + * Licensed under the MIT License. See LICENSE file in the project root for + * full license information. + */ +/** @file + * heap interface test + * @author Justin R. Cutler + */ + +/* HAVE_* */ +#include "config.h" + +/* errno, EINVAL */ +#include +/* printf, perror */ +#include +/* EXIT_SUCCESS, EXIT_FAILURE */ +#include + +/* container_of */ +#include +/* default_allocator_get */ +#include +#ifdef HAVE_MMAP +/* mmap_allocator_get */ +# include +#endif +/* ... */ +#include + + +const int data[] = { 4 /* duplicate */, 1, 9, 2, 8, 4, 0, 5, 3, 6, 7, 99 }; +const size_t data_count = sizeof(data) / sizeof(data[0]); + + +typedef struct { + heap_node_t node; + int value; +} value_t; + + +static int min_compare(const heap_node_t *a, const heap_node_t *b) +{ + int va = container_of(a, const value_t, node)->value; + int vb = container_of(b, const value_t, node)->value; + return va - vb; +} + + +static int run(allocator_t *allocator) +{ + int error = 0; + allocation_t alloc; + value_t *values = NULL; + heap_t heap; + size_t i; + heap_node_t *node; + + allocation_init(&alloc, allocator); + error = allocation_realloc_array(&alloc, data_count, sizeof(*values)); + if (error) { + return error; + } + values = alloc.memory; + + heap_init(&heap, allocator, min_compare); + + /* push values into heap */ + for (i = 0; !error && i < data_count; ++i) { + values[i].value = data[i]; + error = heap_push(&heap, &(values[i].node)); + } + + /* remove from middle */ + heap_remove(&(values[0].node)); + /* remove from end */ + heap_remove(&(values[data_count - 1].node)); + + /* pull values out of heap (minimum first) */ + while (NULL != (node = heap_pop(&heap))) { + value_t *value = container_of(node, value_t, node); + printf("%i\n", value->value); + } + + heap_fini(&heap); + + allocation_free(&alloc); + + return error; +} + + +int main(int argc, char *argv[]) +{ + int error; + allocator_t *allocator; + + (void) argc; + (void) argv; + + printf("default allocator:\n"); + allocator = default_allocator_get(); + error = run(allocator); + allocator_destroy(allocator); + +#ifdef HAVE_MMAP + if (!error) { + printf("mmap allocator:\n"); + allocator = mmap_allocator_get(); + error = run(allocator); + allocator_destroy(allocator); + } +#endif + + return !error ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/threadless/heap.h b/threadless/heap.h new file mode 100644 index 0000000..ed0369a --- /dev/null +++ b/threadless/heap.h @@ -0,0 +1,112 @@ +/* threadless.io + * Copyright (c) 2016 Justin R. Cutler + * Licensed under the MIT License. See LICENSE file in the project root for + * full license information. + */ +/** @file + * heap interface definition + * @author Justin R. Cutler + */ +#ifndef THREADLESS_HEAP_H +#define THREADLESS_HEAP_H + +/* size_t, NULL */ +#include + +/* allocation_t, allocator_t, allocation_init */ +#include + +/** Heap descriptor type */ +typedef struct heap heap_t; + +/** Heap node type */ +typedef struct { + /** Back-pointer to containing heap */ + heap_t *heap; + /** Current index within @p heap */ + size_t index; +} heap_node_t; + +/** Heap node comparison function type + * @param[in] a first node + * @param[in] b second node + * @retval >0 @p a is "better" than @p b + * @retval 0 @p a is equivalent to @p b + * @retval <0 @p a is "worse" than @p b + */ +typedef int (heap_compare_function_t)(const heap_node_t *a, + const heap_node_t *b); + +/** Heap descriptor structure */ +struct heap { + /** Heap node pointer storage */ + allocation_t allocation; + /** Number of nodes in heap */ + size_t count; + /** Heap node comparison function */ + heap_compare_function_t *compare; +}; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Initialize a heap descriptor + * @param[out] heap heap to initialize + * @param allocator allocator instance + * @param compare comparison function + * @post @p heap represents an empty heap with given @p compare function + */ +static inline void heap_init(heap_t *heap, allocator_t *allocator, + heap_compare_function_t *compare) +{ + allocation_init(&heap->allocation, allocator); + heap->count = 0; + heap->compare = compare; +} + +/** Peek at the "best" node in @p heap + * @param[in] heap heap + * @retval NULL @p heap is empty + * @retval non-NULL pointer to "best" node in @p heap + */ +static inline heap_node_t *heap_peek(const heap_t *heap) +{ + heap_node_t **storage = heap->count ? heap->allocation.memory : NULL; + return storage ? *storage : NULL; +} + +/** Push a @p node into @p heap + * @param[in,out] heap heap + * @param[in,out] node node + * @retval 0 success + * @retval -1 error + */ +int heap_push(heap_t *heap, heap_node_t *node); + +/** Remove an arbitrary @p node from its heap + * @param[in,out] node node + * @pre @p node must be in a heap + * @post @p node is no longer in a heap + */ +void heap_remove(heap_node_t *node); + +/** Remove and return the "best" node in @p heap + * @param[in,out] heap heap + * @retval NULL @p heap was empty + * @retval non-NULL pointer to removed "best" node from @p heap + */ +heap_node_t *heap_pop(heap_t *heap); + +/** Finalize a heap + * @param[in,out] heap heap + * @post All nodes have been removed from @p heap and all backing memory has + * been released + */ +void heap_fini(heap_t *heap); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* THREADLESS_HEAP_H */