From 00717dd038b2c024b529553b5c2a848fceccf2fb Mon Sep 17 00:00:00 2001 From: bronku Date: Thu, 23 Oct 2025 17:23:58 +0200 Subject: [PATCH] heap --- heap.c | 101 ++++++++++++------ heap.h | 19 ++-- heap_test.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++ makefile | 11 +- 4 files changed, 387 insertions(+), 46 deletions(-) create mode 100644 heap_test.c diff --git a/heap.c b/heap.c index 7ba34bb..2d9825f 100644 --- a/heap.c +++ b/heap.c @@ -1,45 +1,78 @@ #include "heap.h" #include "status_codes.h" +#include -// #unteasted -int heap_push(heap* h, heap_record* in) +heap* create_heap(int size, int (*comparison)(const void* a, const void* b)) { - if (h->length >= h->capacity) { - return NO_SPACE; - } + heap* out = malloc(sizeof(heap)); + out->data = malloc(sizeof(void*) * size); + out->length = 0; + out->capacity = size; + out->compare = comparison; + return out; +} - int current_index = h->length; +void destroy_heap(heap* h) +{ + free(h->data); + free(h); +} + +void swap(void** a, void** b) +{ + void* tmp = *a; + *a = *b; + *b = tmp; +} + +int heap_push(heap* h, void* in) +{ + int current = h->length; + void** d = h->data; h->length++; - heap_record* last = h->location; - last += current_index; - *last = *in; - - if (current_index == 0) { + d[current] = in; + if (current == 0) { return SUCCESS; } - - int parent_index = (current_index - 1) / 2; - // check if violated heap property - heap_record* parent = h->location; - parent += parent_index; - heap_record* current = h->location; - current += current_index; - // swim up - while (current_index != 0 && h->compare(parent, current) == 1) { - // current, parent = parent, current - heap_record tmp = *current; - *current = *parent; - *parent = tmp; - // current_index = parent_index - current_index = parent_index; - // parent_index = (current_index -1 )/2 - parent_index = (current_index - 1) / 2; - // current = loc[current_index] - current = h->location; - current += current_index; - // parent = loc[parent_index] - parent = h->location; - parent_index += parent_index; + int parent = (current - 1) / 2; + while (h->compare(d[current], d[parent]) == -1) { + swap(&d[current], &d[parent]); + current = parent; + if (current == 0) { + break; + } + parent = (current - 1) / 2; + } + return SUCCESS; +} + +int heap_pop(heap* h, void** out) +{ + void** d = h->data; + *out = d[0]; + h->length--; + if (h->length == 0) { + return SUCCESS; + } + swap(&d[0], &d[h->length]); + int current = 0; + int left = current * 2 + 1; + int right = current * 2 + 2; + while (true) { + int smallest = current; + if (left < h->length && h->compare(d[smallest], d[left]) == 1) { + smallest = left; + } + if (right < h->length && h->compare(d[smallest], d[right]) == 1) { + smallest = right; + } + if (smallest == current) { + break; + } + swap(&d[smallest], &d[current]); + current = smallest; + left = current * 2 + 1; + right = current * 2 + 2; } return SUCCESS; } diff --git a/heap.h b/heap.h index f59116d..601745b 100644 --- a/heap.h +++ b/heap.h @@ -1,22 +1,19 @@ +// doesn't ceck for user erros, so no veryfying that the void* supplied are valid +// unsafe for general use, but just fnuctional enough for the purposes of this project +// does not maintain ownership of its elements, they are managed by the caller #pragma once #include "stdbool.h" typedef struct { - int index; - int buffer_id; -} heap_record; - -// almost the same as typedef buffer, but the usage is different, so I think it can be repeated -typedef struct { - heap_record* location; + void** data; int length; int capacity; int (*compare)(const void* a, const void* b); } heap; -heap new_heap(int size); -// free_heap is just free(h->location), so it's kinda unnecessary +heap* create_heap(int size, int (*comparison)(const void* a, const void* b)); +void destroy_heap(heap* h); -int heap_pop(heap* h, heap_record* out); -int heap_push(heap* h, heap_record* in); +int heap_pop(heap* h, void** out); +int heap_push(heap* h, void* in); diff --git a/heap_test.c b/heap_test.c new file mode 100644 index 0000000..8462e59 --- /dev/null +++ b/heap_test.c @@ -0,0 +1,302 @@ +// the tests are ai generated +#include "heap.h" +#include +#include +#include + +// Comparator for integers +int compare_ints(const void* a, const void* b) +{ + int ia = *(const int*)a; + int ib = *(const int*)b; + return (ia > ib) - (ia < ib); +} + +// Comparator for doubles +int compare_doubles(const void* a, const void* b) +{ + double da = *(const double*)a; + double db = *(const double*)b; + if (da < db) + return -1; + if (da > db) + return 1; + return 0; +} + +// Test structure +typedef struct { + int priority; + char name[32]; +} Task; + +// Comparator for tasks (by priority) +int compare_tasks(const void* a, const void* b) +{ + const Task* ta = (const Task*)a; + const Task* tb = (const Task*)b; + return (ta->priority > tb->priority) - (ta->priority < tb->priority); +} + +void test_basic_push_pop() +{ + printf("Test: Basic push and pop... "); + + heap* h = create_heap(5, compare_ints); + int values[] = { 5, 3, 7, 1, 9 }; + + // Push all values + for (int i = 0; i < 5; i++) { + heap_push(h, &values[i]); + } + + // Pop and verify they come out in sorted order + void* out; + int expected[] = { 1, 3, 5, 7, 9 }; + for (int i = 0; i < 5; i++) { + heap_pop(h, &out); + assert(*(int*)out == expected[i]); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_single_element() +{ + printf("Test: Single element... "); + + heap* h = create_heap(1, compare_ints); + int val = 42; + + heap_push(h, &val); + + void* out; + heap_pop(h, &out); + assert(*(int*)out == 42); + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_already_sorted() +{ + printf("Test: Already sorted input... "); + + heap* h = create_heap(5, compare_ints); + int values[] = { 1, 2, 3, 4, 5 }; + + for (int i = 0; i < 5; i++) { + heap_push(h, &values[i]); + } + + void* out; + for (int i = 0; i < 5; i++) { + heap_pop(h, &out); + assert(*(int*)out == i + 1); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_reverse_sorted() +{ + printf("Test: Reverse sorted input... "); + + heap* h = create_heap(5, compare_ints); + int values[] = { 5, 4, 3, 2, 1 }; + + for (int i = 0; i < 5; i++) { + heap_push(h, &values[i]); + } + + void* out; + for (int i = 0; i < 5; i++) { + heap_pop(h, &out); + assert(*(int*)out == i + 1); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_duplicates() +{ + printf("Test: Duplicate values... "); + + heap* h = create_heap(7, compare_ints); + int values[] = { 3, 1, 4, 1, 5, 9, 2 }; + + for (int i = 0; i < 7; i++) { + heap_push(h, &values[i]); + } + + void* out; + int expected[] = { 1, 1, 2, 3, 4, 5, 9 }; + for (int i = 0; i < 7; i++) { + heap_pop(h, &out); + assert(*(int*)out == expected[i]); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_with_doubles() +{ + printf("Test: Double precision values... "); + + heap* h = create_heap(5, compare_doubles); + double values[] = { 3.14, 1.41, 2.71, 0.57, 1.61 }; + + for (int i = 0; i < 5; i++) { + heap_push(h, &values[i]); + } + + void* out; + double expected[] = { 0.57, 1.41, 1.61, 2.71, 3.14 }; + for (int i = 0; i < 5; i++) { + heap_pop(h, &out); + assert(*(double*)out == expected[i]); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_with_structs() +{ + printf("Test: Custom struct (tasks by priority)... "); + + heap* h = create_heap(4, compare_tasks); + Task tasks[] = { + { 3, "Low priority" }, + { 1, "High priority" }, + { 2, "Medium priority" }, + { 1, "Also high priority" } + }; + + for (int i = 0; i < 4; i++) { + heap_push(h, &tasks[i]); + } + + void* out; + int expected_priorities[] = { 1, 1, 2, 3 }; + for (int i = 0; i < 4; i++) { + heap_pop(h, &out); + Task* t = (Task*)out; + assert(t->priority == expected_priorities[i]); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_interleaved_operations() +{ + printf("Test: Interleaved push/pop... "); + + heap* h = create_heap(10, compare_ints); + int values[] = { 5, 3, 7, 1, 9, 2, 8, 4, 6, 0 }; + + // Push 5, pop 1, push 3 more, pop 2, etc. + heap_push(h, &values[0]); // 5 + heap_push(h, &values[1]); // 3 + heap_push(h, &values[2]); // 7 + + void* out; + heap_pop(h, &out); + assert(*(int*)out == 3); + + heap_push(h, &values[3]); // 1 + heap_push(h, &values[4]); // 9 + heap_push(h, &values[5]); // 2 + + heap_pop(h, &out); + assert(*(int*)out == 1); + heap_pop(h, &out); + assert(*(int*)out == 2); + + heap_push(h, &values[6]); // 8 + heap_push(h, &values[7]); // 4 + + heap_pop(h, &out); + assert(*(int*)out == 4); + + destroy_heap(h); + printf("PASSED\n"); +} + +void test_large_heap() +{ + printf("Test: Large heap (1000 elements)... "); + + heap* h = create_heap(1000, compare_ints); + int* values = malloc(1000 * sizeof(int)); + + // Insert values in random-ish order + for (int i = 0; i < 1000; i++) { + values[i] = (i * 7 + 13) % 1000; + heap_push(h, &values[i]); + } + + // Pop all and verify sorted + void* out; + int prev = -1; + for (int i = 0; i < 1000; i++) { + heap_pop(h, &out); + int curr = *(int*)out; + assert(curr >= prev); + prev = curr; + } + + free(values); + destroy_heap(h); + printf("PASSED\n"); +} + +void test_all_same_values() +{ + printf("Test: All same values... "); + + heap* h = create_heap(5, compare_ints); + int val = 42; + int values[5]; + + for (int i = 0; i < 5; i++) { + values[i] = val; + heap_push(h, &values[i]); + } + + void* out; + for (int i = 0; i < 5; i++) { + heap_pop(h, &out); + assert(*(int*)out == 42); + } + + destroy_heap(h); + printf("PASSED\n"); +} + +int main() +{ + printf("Running Min Heap Tests\n"); + printf("======================\n\n"); + + test_basic_push_pop(); + test_single_element(); + test_already_sorted(); + test_reverse_sorted(); + test_duplicates(); + test_with_doubles(); + test_with_structs(); + test_interleaved_operations(); + test_large_heap(); + test_all_same_values(); + + printf("\n======================\n"); + printf("All tests PASSED! ✓\n"); + + return 0; +} diff --git a/makefile b/makefile index f72b723..6dd82d5 100644 --- a/makefile +++ b/makefile @@ -33,7 +33,16 @@ build/run/1.in: build/run build/main run: build/main build/run/1.in build/run/tmp build/main -i build/run/1.in -d build/run/tmp | less +build/heap_test.o: heap_test.c build + clang $(CFLAGS) -c heap_test.c -o build/heap_test.o + +build/test: build/heap_test.o build/heap.o + clang $(CFLAGS) build/heap_test.o build/heap.o -o build/test + +test: build/test + build/test + debug: build/main tests/1.in lldb -- build/main -.PHONY: clean run debug +.PHONY: clean run debug test