From 5564e23ceb4e34055cb1150dd24c0053d7eb02a6 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 22 Dec 2023 22:07:09 +0100 Subject: [PATCH] feat(lsm): binary tree iterators --- CHANGELOG.md | 2 + lsm/include/lsm/bt.h | 36 +++++++++++++++++ lsm/src/_include/lsm/bt_internal.h | 5 ++- lsm/src/bt/lsm_bt.c | 64 ++++++++++++++++++++++++++++-- lsm/test/bt/bt.c | 28 +++++++++++++ 5 files changed, 130 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aca5284..3528883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Trie-based router (no more RegEx) * Landerctl * `-c` flag to use custom config file (useful for testing) +* LSM + * Binary tree iterators ## Removed diff --git a/lsm/include/lsm/bt.h b/lsm/include/lsm/bt.h index 581288f..67655d4 100644 --- a/lsm/include/lsm/bt.h +++ b/lsm/include/lsm/bt.h @@ -1,6 +1,8 @@ #ifndef LSM_BT #define LSM_BT +#include + #include "lsm.h" /** @@ -8,6 +10,11 @@ */ typedef struct lsm_bt lsm_bt; +/** + * A node inside an `lsm_bt` binary tree. + */ +typedef struct lsm_bt_node lsm_bt_node; + /** * Initialize a new binary tree * @@ -68,4 +75,33 @@ lsm_error lsm_bt_remove(void **out, lsm_bt *bt, char key); */ lsm_error lsm_bt_replace(void **out, lsm_bt *bt, char key, void *data); +/** + * Struct representing an in-flight iterator over the binary tree + */ +typedef struct lsm_bt_iterator { + lsm_bt_node *next; +} lsm_bt_iterator; + +/** + * Initialize the given iterator for the binary tree. + * + * The iterator is explicitely allowed to be allocated by the user, as these are + * commonly used inside functions where they can simply be stored on the stack. + * + * @param out iterator to initialize + * @param bt binary tree to iterate + */ +void lsm_bt_iter(lsm_bt_iterator *out, const lsm_bt *bt); + +/** + * Advance the iterator to the next element. + * + * @param out where to store pointer to data; ignored if NULL + * @param key_out where to store key; ignored if NULL + * @param iter iterator to advance + * @return true if a new entry was returned, false if the iterator has no more + * entries to return + */ +bool lsm_bt_iter_next(const void **out, char *key_out, lsm_bt_iterator *iter); + #endif diff --git a/lsm/src/_include/lsm/bt_internal.h b/lsm/src/_include/lsm/bt_internal.h index 4b55771..ed353a6 100644 --- a/lsm/src/_include/lsm/bt_internal.h +++ b/lsm/src/_include/lsm/bt_internal.h @@ -9,12 +9,13 @@ /** * Node inside a binary tree */ -typedef struct lsm_bt_node { +struct lsm_bt_node { struct lsm_bt_node *left; struct lsm_bt_node *right; + struct lsm_bt_node *parent; void *data; char key; -} lsm_bt_node; +}; /** * Initialize a new binary tree node diff --git a/lsm/src/bt/lsm_bt.c b/lsm/src/bt/lsm_bt.c index c09fa01..501dbf8 100644 --- a/lsm/src/bt/lsm_bt.c +++ b/lsm/src/bt/lsm_bt.c @@ -1,5 +1,6 @@ #include +#include "lsm/bt.h" #include "lsm/bt_internal.h" lsm_error lsm_bt_node_init(lsm_bt_node **ptr, const char key, void *data) { @@ -60,10 +61,12 @@ void lsm_bt_free(lsm_bt *bt) { uint64_t lsm_bt_size(const lsm_bt *bt) { return bt->size; } lsm_error lsm_bt_insert(lsm_bt *bt, char key, void *data) { + lsm_bt_node *parent = NULL; lsm_bt_node **dest = &bt->root; // Traverse down the tree until we reach the new point to insert our node while ((*dest != NULL) && ((*dest)->key != key)) { + parent = *dest; dest = key < (*dest)->key ? &(*dest)->left : &(*dest)->right; } @@ -71,9 +74,8 @@ lsm_error lsm_bt_insert(lsm_bt *bt, char key, void *data) { return lsm_error_already_present; } - if (lsm_bt_node_init(dest, key, data) != lsm_error_ok) { - return lsm_error_failed_alloc; - } + LSM_RES(lsm_bt_node_init(dest, key, data)); + (*dest)->parent = parent; bt->size++; @@ -125,13 +127,27 @@ lsm_error lsm_bt_remove(void **out, lsm_bt *bt, char key) { (*dest)->data = (*succ)->data; lsm_bt_node *succ_replacement = (*succ)->right; + lsm_bt_node *parent = (*succ)->parent; + lsm_bt_node_free(*succ); + *succ = succ_replacement; + + if (*succ != NULL) { + (*succ)->parent = parent; + } } else { lsm_bt_node *replacement = (*dest)->left != NULL ? (*dest)->left : (*dest)->right; + lsm_bt_node *parent = (*dest)->parent; + lsm_bt_node_free(*dest); + *dest = replacement; + + if (*dest != NULL) { + (*dest)->parent = parent; + } } return lsm_error_ok; @@ -156,3 +172,45 @@ lsm_error lsm_bt_replace(void **out, lsm_bt *bt, char key, void *data) { return lsm_error_ok; } + +void lsm_bt_iter(lsm_bt_iterator *out, const lsm_bt *bt) { + out->next = bt->root; + + if (bt->root != NULL) { + // Initialize the iterator to the smallest element in the tree + while (out->next->left != NULL) { + out->next = out->next->left; + } + } +} + +bool lsm_bt_iter_next(const void **out, char *key_out, lsm_bt_iterator *iter) { + if (iter->next == NULL) { + return false; + } + + if (out != NULL) { + *out = iter->next->data; + } + + if (key_out != NULL) { + *key_out = iter->next->key; + } + + if (iter->next->right != NULL) { + iter->next = iter->next->right; + + while (iter->next->left != NULL) { + iter->next = iter->next->left; + } + } else { + while ((iter->next->parent != NULL) && + (iter->next->parent->right == iter->next)) { + iter->next = iter->next->parent; + } + + iter->next = iter->next->parent; + } + + return true; +} diff --git a/lsm/test/bt/bt.c b/lsm/test/bt/bt.c index fdff839..908f35a 100644 --- a/lsm/test/bt/bt.c +++ b/lsm/test/bt/bt.c @@ -104,6 +104,33 @@ void test_remove_multiple() { lsm_bt_free(bt); } +void test_iter() { + char chars[] = "falcoep"; + size_t char_count = sizeof(chars) / sizeof(char) - 1; + + char sorted_chars[] = "aceflop"; + + BT_INIT(); + + for (size_t i = 0; i < char_count; i++) { + TEST_CHECK(lsm_bt_insert(bt, chars[i], (void *)(i + 1)) == lsm_error_ok); + } + + lsm_bt_iterator iter; + lsm_bt_iter(&iter, bt); + + char key; + const void *data; + size_t i = 0; + + while (lsm_bt_iter_next(&data, &key, &iter)) { + TEST_CHECK_(key == sorted_chars[i], "%c == %c", key, sorted_chars[i]); + i++; + } + + TEST_CHECK(i == char_count); +} + TEST_LIST = { { "bt init", test_init }, { "bt insert first", test_insert_first }, @@ -111,5 +138,6 @@ TEST_LIST = { { "bt insert multiple", test_insert_multiple }, { "bt remove root", test_remove_root }, { "bt remove multiple", test_remove_multiple }, + { "bt iter", test_iter }, { NULL, NULL } };