312 lines
9.8 KiB
C
312 lines
9.8 KiB
C
#include <c_TST.h>
|
|
#include <c_Memory.h>
|
|
#include <c_StringBuffer.h>
|
|
#include <c_ArrayStack.h>
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
|
|
/* Internal constructor helper to build an isolated TST node capsule */
|
|
static c_TSTNode_t* c_TSTNode_Create(char c) {
|
|
c_TSTNode_t* node = (c_TSTNode_t*)C_CALLOC(1, sizeof(c_TSTNode_t));
|
|
if (node) {
|
|
node->c = c;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/* Internal destructor helper to clear TST nodes non-recursively using an explicit heap stack */
|
|
static void c_TSTNode_DestroyRecursive(c_TSTNode_t* root) {
|
|
if (!root) return;
|
|
|
|
c_ArrayStack_t node_stack;
|
|
c_ArrayStack_Init(&node_stack, sizeof(c_TSTNode_t*), 256);
|
|
c_ArrayStack_Push(&node_stack, &root);
|
|
|
|
while (!c_ArrayStack_IsEmpty(&node_stack)) {
|
|
c_TSTNode_t* curr = 0;
|
|
c_ArrayStack_Pop(&node_stack, &curr);
|
|
c_bool_t advanced = C_FALSE;
|
|
|
|
// Push children onto the cleanup stack frame and clear links to avoid cycles
|
|
if (curr->left) {
|
|
c_ArrayStack_Push(&node_stack, &curr->left);
|
|
curr->left = NULL;
|
|
advanced = C_TRUE;
|
|
} else if (curr->mid) {
|
|
c_ArrayStack_Push(&node_stack, &curr->mid);
|
|
curr->mid = NULL;
|
|
advanced = C_TRUE;
|
|
} else if (curr->right) {
|
|
c_ArrayStack_Push(&node_stack, &curr->right);
|
|
curr->right = NULL;
|
|
advanced = C_TRUE;
|
|
}
|
|
|
|
if (advanced == C_FALSE) {
|
|
C_FREE(curr);
|
|
}
|
|
}
|
|
|
|
c_ArrayStack_Destroy(&node_stack);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
|
|
c_err_t c_TST_Init(c_TST_t* self) {
|
|
if (!self) return C_ERR_PARAM;
|
|
self->root = NULL;
|
|
self->size = 0;
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
void c_TST_Destroy(c_TST_t* self) {
|
|
if (!self) return;
|
|
c_TSTNode_DestroyRecursive(self->root);
|
|
self->root = NULL;
|
|
self->size = 0;
|
|
}
|
|
|
|
/* Internal recursive worker to support clean TST node creation and value insertions */
|
|
static c_TSTNode_t* c_TST_PutWorker(c_TSTNode_t* x, const char* key, c_size_t d, void* value, c_bool_t* is_new, c_err_t* err) {
|
|
char c = key[d];
|
|
if (!x) {
|
|
x = c_TSTNode_Create(c);
|
|
if (!x) {
|
|
*err = C_ERR_NOMEM;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (c < x->c) {
|
|
x->left = c_TST_PutWorker(x->left, key, d, value, is_new, err);
|
|
} else if (c > x->c) {
|
|
x->right = c_TST_PutWorker(x->right, key, d, value, is_new, err);
|
|
} else if (d < strlen(key) - 1) {
|
|
x->mid = c_TST_PutWorker(x->mid, key, d + 1, value, is_new, err);
|
|
} else {
|
|
if (x->value == NULL) {
|
|
*is_new = C_TRUE;
|
|
}
|
|
x->value = value;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
c_err_t c_TST_Put(c_TST_t* self, const char* key, void* value) {
|
|
if (!self || !key || strlen(key) == 0 || !value) return C_ERR_PARAM;
|
|
|
|
c_bool_t is_new = C_FALSE;
|
|
c_err_t err = C_ERR_OK;
|
|
self->root = c_TST_PutWorker(self->root, key, 0, value, &is_new, &err);
|
|
|
|
if (err == C_ERR_OK && is_new == C_TRUE) {
|
|
self->size++;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
void* c_TST_Get(c_TST_t* self, const char* key) {
|
|
if (!self || !key || strlen(key) == 0 || !self->root) return NULL;
|
|
|
|
c_TSTNode_t* curr = self->root;
|
|
c_size_t d = 0;
|
|
c_size_t len = strlen(key);
|
|
|
|
while (curr) {
|
|
char c = key[d];
|
|
if (c < curr->c) {
|
|
curr = curr->left;
|
|
} else if (c > curr->c) {
|
|
curr = curr->right;
|
|
} else if (d < len - 1) {
|
|
curr = curr->mid;
|
|
d++;
|
|
} else {
|
|
return curr->value;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
c_bool_t c_TST_Contains(c_TST_t* self, const char* key) {
|
|
return (c_TST_Get(self, key) != NULL) ? C_TRUE : C_FALSE;
|
|
}
|
|
|
|
/* Internal prefix traversal worker */
|
|
static void c_TST_CollectWorker(c_TSTNode_t* x, c_StringBuffer_t* sb, c_size_t depth, c_StringList* result) {
|
|
if (!x) return;
|
|
|
|
// Explore smaller alphabetical character trees leftward (keeps current string prefix length unchanged)
|
|
c_TST_CollectWorker(x->left, sb, depth, result);
|
|
|
|
// Append the matching node character token directly to the string builder
|
|
c_StringBuffer_Append(sb, &(x->c), 1);
|
|
if (x->value != NULL) {
|
|
c_StringList_Append(result, sb->buffer);
|
|
}
|
|
|
|
// Continue crawling down matching children on the middle branch
|
|
c_TST_CollectWorker(x->mid, sb, depth + 1, result);
|
|
|
|
// Backtrack step: restore parent character layout configuration length boundaries
|
|
c_StringBuffer_SetLength(sb, depth);
|
|
|
|
// Explore larger alphabetical character trees rightward
|
|
c_TST_CollectWorker(x->right, sb, depth, result);
|
|
}
|
|
|
|
c_err_t c_TST_KeysWithPrefix(c_TST_t* self, const char* prefix, c_StringList* result) {
|
|
if (!self || !prefix || !result || !self->root) return C_ERR_PARAM;
|
|
|
|
c_TSTNode_t* curr = self->root;
|
|
c_size_t d = 0;
|
|
c_size_t len = strlen(prefix);
|
|
|
|
// Navigate to the end node matching the prefix string character rules
|
|
while (curr) {
|
|
char c = prefix[d];
|
|
if (c < curr->c) {
|
|
curr = curr->left;
|
|
} else if (c > curr->c) {
|
|
curr = curr->right;
|
|
} else if (d < len - 1) {
|
|
curr = curr->mid;
|
|
d++;
|
|
} else {
|
|
break; // Prefix matched up to curr node boundaries
|
|
}
|
|
}
|
|
|
|
if (!curr) return C_ERR_OK; // Prefix not found safely yields 0 matches
|
|
|
|
c_StringBuffer_t sb;
|
|
if (c_StringBuffer_Init(&sb, 256) != C_ERR_OK) return C_ERR_NOMEM;
|
|
|
|
// Seed our builder with the matching prefix handle string tokens
|
|
c_StringBuffer_Append(&sb, prefix, len);
|
|
|
|
// If the prefix node boundary itself holds an active value, record it first
|
|
if (curr->value != NULL) {
|
|
c_StringList_Append(result, prefix);
|
|
}
|
|
|
|
// Crawl down the middle branch to extract all matching multi-character children variations
|
|
c_TST_CollectWorker(curr->mid, &sb, len, result);
|
|
|
|
c_StringBuffer_Destroy(&sb);
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
/* Internal recursive wildcard collector worker */
|
|
static void c_TST_MatchWorker(c_TSTNode_t* x, c_StringBuffer_t* sb, const char* pattern, c_size_t d, c_StringList* result) {
|
|
if (!x) return;
|
|
|
|
char c = pattern[d];
|
|
c_size_t len = strlen(pattern);
|
|
|
|
// Explore smaller characters leftward if the pattern permits or if it's a wildcard
|
|
if (c == '.' || c < x->c) {
|
|
c_TST_MatchWorker(x->left, sb, pattern, d, result);
|
|
}
|
|
|
|
// Process current node character matching boundaries
|
|
if (c == '.' || c == x->c) {
|
|
// Append the current split character token onto your string buffer builder stack
|
|
c_StringBuffer_Append(sb, &(x->c), 1);
|
|
|
|
// Terminal case: If we have reached the final character index of the pattern layout string
|
|
if (d == len - 1) {
|
|
if (x->value != NULL) {
|
|
c_StringList_Append(result, sb->buffer);
|
|
}
|
|
} else {
|
|
// Advance deeper down the middle branch to explore matching multi-character continuations
|
|
c_TST_MatchWorker(x->mid, sb, pattern, d + 1, result);
|
|
}
|
|
|
|
// Backtracking unwinding step: reset logical buffer length configuration framework
|
|
c_StringBuffer_SetLength(sb, d);
|
|
}
|
|
|
|
// Explore larger characters rightward if the pattern permits or if it's a wildcard
|
|
if (c == '.' || c > x->c) {
|
|
c_TST_MatchWorker(x->right, sb, pattern, d, result);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gather all keys currently matching a specific wildcard pattern string inside the TST
|
|
*/
|
|
c_err_t c_TST_KeysThatMatch(c_TST_t* self, const char* pattern, c_StringList* result) {
|
|
if (!self || !pattern || strlen(pattern) == 0 || !result || !self->root) {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
c_StringBuffer_t sb;
|
|
if (c_StringBuffer_Init(&sb, 256) != C_ERR_OK) {
|
|
return C_ERR_NOMEM;
|
|
}
|
|
|
|
// Start crawling the ternary search tree using our shared string buffer accumulator
|
|
c_TST_MatchWorker(self->root, &sb, pattern, 0, result);
|
|
|
|
c_StringBuffer_Destroy(&sb);
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
char* c_TST_LongestPrefixOf(c_TST_t* self, const char* query) {
|
|
if (!self || !query || !self->root) {
|
|
return NULL;
|
|
}
|
|
|
|
c_TSTNode_t* curr = self->root;
|
|
c_size_t query_len = strlen(query);
|
|
c_size_t longest_match_len = 0;
|
|
c_bool_t match_found = C_FALSE;
|
|
c_size_t d = 0;
|
|
|
|
// Run an iterative O(L) scan across TST split character tokens
|
|
while (curr && d < query_len) {
|
|
char c = query[d];
|
|
|
|
if (c < curr->c) {
|
|
curr = curr->left; // Step left: current character is smaller
|
|
} else if (c > curr->c) {
|
|
curr = curr->right; // Step right: current character is larger
|
|
} else {
|
|
// Character matches curr->c exactly! Check if this marks a complete key
|
|
if (curr->value != NULL) {
|
|
longest_match_len = d + 1;
|
|
match_found = C_TRUE;
|
|
}
|
|
curr = curr->mid; // Advance down the middle branch
|
|
d++; // Advance to next character in query string
|
|
}
|
|
}
|
|
|
|
// Allocate an isolated heap buffer to hold the output string copy
|
|
c_size_t output_bytes = match_found ? (longest_match_len + 1) : 1;
|
|
char* result_str = (char*)C_ALLOC(output_bytes);
|
|
if (!result_str) {
|
|
return NULL;
|
|
}
|
|
|
|
if (match_found == C_TRUE) {
|
|
memcpy(result_str, query, longest_match_len);
|
|
result_str[longest_match_len] = '\0';
|
|
} else {
|
|
result_str[0] = '\0'; // Return a clean empty string if no prefix matches
|
|
}
|
|
|
|
return result_str;
|
|
} |