Search/Sort
This commit is contained in:
+320
@@ -0,0 +1,320 @@
|
||||
#include <c_Trie.h>
|
||||
#include <c_Memory.h>
|
||||
#include <c_ArrayStack.h>
|
||||
#include "c_StringBuffer.h"
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
/* Internal constructor helper to build an isolated trie node capsule */
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_TrieNode_t* c_TrieNode_Create(void) {
|
||||
c_TrieNode_t* node = (c_TrieNode_t*)C_CALLOC(1, sizeof(c_TrieNode_t));
|
||||
return node; // C_CALLOC initializes all children nodes inside next[] to NULL
|
||||
}
|
||||
|
||||
/* Internal destructor helper to clear trie nodes post-order non-recursively using an explicit stack */
|
||||
static void c_TrieNode_DestroyRecursive(c_TrieNode_t* root) {
|
||||
if (!root) return;
|
||||
|
||||
// Explicit tree-cleanup stack configuration limits space constraints safely
|
||||
c_ArrayStack_t node_stack;
|
||||
c_ArrayStack_Init(&node_stack, sizeof(c_TrieNode_t*), 4);
|
||||
c_ArrayStack_Push(&node_stack, &root);
|
||||
|
||||
while (!c_ArrayStack_IsEmpty(&node_stack)) {
|
||||
c_TrieNode_t* curr = 0;
|
||||
c_err_t err = c_ArrayStack_Pop(&node_stack, &curr);
|
||||
c_bool_t has_children = C_FALSE;
|
||||
|
||||
for (int i = 0; i < C_TRIE_R; i++) {
|
||||
if (curr->next[i]) {
|
||||
c_ArrayStack_Push(&node_stack, &curr->next[i]);
|
||||
curr->next[i] = NULL; // Break loop linkage to track post-order cleanup processing
|
||||
has_children = C_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_children == C_FALSE) {
|
||||
C_FREE(curr);
|
||||
}
|
||||
}
|
||||
c_ArrayStack_Destroy(&node_stack);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
c_err_t c_Trie_Init(c_Trie_t* self) {
|
||||
if (!self) return C_ERR_PARAM;
|
||||
self->root = c_TrieNode_Create();
|
||||
self->size = 0;
|
||||
return self->root ? C_ERR_OK : C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
void c_Trie_Destroy(c_Trie_t* self) {
|
||||
if (!self) return;
|
||||
c_TrieNode_DestroyRecursive(self->root);
|
||||
self->root = NULL;
|
||||
self->size = 0;
|
||||
}
|
||||
|
||||
c_err_t c_Trie_Put(c_Trie_t* self, const char* key, void* value) {
|
||||
if (!self || !key) return C_ERR_PARAM;
|
||||
if (!self->root) {
|
||||
self->root = c_TrieNode_Create();
|
||||
if (!self->root) return C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
c_TrieNode_t* curr = self->root;
|
||||
c_size_t len = strlen(key);
|
||||
|
||||
for (c_size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)key[i];
|
||||
if (!curr->next[c]) {
|
||||
curr->next[c] = c_TrieNode_Create();
|
||||
if (!curr->next[c]) return C_ERR_NOMEM;
|
||||
}
|
||||
curr = curr->next[c];
|
||||
}
|
||||
|
||||
if (curr->value == NULL && value != NULL) {
|
||||
self->size++;
|
||||
} else if (curr->value != NULL && value == NULL) {
|
||||
self->size--;
|
||||
}
|
||||
|
||||
curr->value = value;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
void* c_Trie_Get(c_Trie_t* self, const char* key) {
|
||||
if (!self || !key || !self->root) return NULL;
|
||||
|
||||
c_TrieNode_t* curr = self->root;
|
||||
c_size_t len = strlen(key);
|
||||
|
||||
for (c_size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)key[i];
|
||||
curr = curr->next[c];
|
||||
if (!curr) return NULL;
|
||||
}
|
||||
return curr->value;
|
||||
}
|
||||
|
||||
c_bool_t c_Trie_Contains(c_Trie_t* self, const char* key) {
|
||||
return (c_Trie_Get(self, key) != NULL) ? C_TRUE : C_FALSE;
|
||||
}
|
||||
|
||||
/* Internal recursive worker to support automated character branch purging on node deletions */
|
||||
static c_TrieNode_t* c_Trie_DeleteWorker(c_TrieNode_t* x, const char* key, c_size_t d, c_bool_t* out_deleted, c_size_t* size_ref) {
|
||||
if (!x) return NULL;
|
||||
|
||||
if (d == strlen(key)) {
|
||||
if (x->value != NULL) {
|
||||
x->value = NULL;
|
||||
(*size_ref)--;
|
||||
*out_deleted = C_TRUE;
|
||||
}
|
||||
} else {
|
||||
unsigned char c = (unsigned char)key[d];
|
||||
x->next[c] = c_Trie_DeleteWorker(x->next[c], key, d + 1, out_deleted, size_ref);
|
||||
}
|
||||
|
||||
// Clean up empty nodes dynamically: if this node holds a value or has other sub-branches, preserve it
|
||||
if (x->value != NULL) return x;
|
||||
for (int c = 0; c < C_TRIE_R; c++) {
|
||||
if (x->next[c] != NULL) return x;
|
||||
}
|
||||
|
||||
// Completely orphaned branch slot achieved; purge memory to prevent layout leaks
|
||||
C_FREE(x);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c_err_t c_Trie_Delete(c_Trie_t* self, const char* key) {
|
||||
if (!self || !key || !self->root) return C_ERR_PARAM;
|
||||
c_bool_t deleted = C_FALSE;
|
||||
self->root = c_Trie_DeleteWorker(self->root, key, 0, &deleted, &(self->size));
|
||||
return deleted ? C_ERR_OK : C_ERR_PARAM;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
static void c_Trie_CollectWorker(c_TrieNode_t* x, c_StringBuffer_t* sb, c_size_t depth, c_StringList* result) {
|
||||
if (!x) return;
|
||||
|
||||
// 1. If an active data payload value exists, export a snapshot copy directly to your StringList
|
||||
if (x->value != NULL) {
|
||||
// Retrieve the current continuous null-terminated string handle from the buffer wrapper
|
||||
// const char* completed_string = c_StringBuffer_CStr(sb);
|
||||
c_StringList_Append(result, sb->buffer);
|
||||
}
|
||||
|
||||
// 2. Iterate sequentially through all possible child alphabet pathways
|
||||
for (int c = 0; c < C_TRIE_R; c++) {
|
||||
if (x->next[c]) {
|
||||
// Append the edge character representation directly onto your builder stack frame
|
||||
char character_token = (char)c;
|
||||
c_StringBuffer_Append(sb, &character_token, 1);
|
||||
|
||||
// Descend recursively down to explore child nodes
|
||||
c_Trie_CollectWorker(x->next[c], sb, depth + 1, result);
|
||||
|
||||
/*
|
||||
* 🛡️ BACKTRACKING STRING INVARIANT:
|
||||
* When winding back up a call frame stack, we must pop/truncate the last character
|
||||
* from your string buffer to restore the parent prefix context state cleanly.
|
||||
*/
|
||||
c_StringBuffer_SetLength(sb, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c_err_t c_Trie_KeysWithPrefix(c_Trie_t* self, const char* prefix, c_StringList* result) {
|
||||
if (!self || !prefix || !result) return C_ERR_PARAM;
|
||||
|
||||
c_TrieNode_t* curr = self->root;
|
||||
c_size_t len = strlen(prefix);
|
||||
for (c_size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)prefix[i];
|
||||
curr = curr->next[c];
|
||||
if (!curr) return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_StringBuffer_t sb;
|
||||
if (c_StringBuffer_Init(&sb, len+256) != C_ERR_OK) {
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
c_StringBuffer_Append(&sb, (const char*)prefix, len);
|
||||
|
||||
c_Trie_CollectWorker(curr, &sb, len, result);
|
||||
|
||||
c_StringBuffer_Destroy(&sb);
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* Internal recursive matching collector worker */
|
||||
static void c_Trie_MatchWorker(c_TrieNode_t* x, c_StringBuffer_t* sb, const char* pattern, c_size_t depth, c_StringList* result) {
|
||||
if (!x) return;
|
||||
|
||||
c_size_t pattern_len = strlen(pattern);
|
||||
|
||||
// Invariant Guard: If we have reached the pattern length, check for an active terminal value payload
|
||||
if (depth == pattern_len) {
|
||||
if (x->value != NULL) {
|
||||
c_StringList_Append(result, sb->buffer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char c = (unsigned char)pattern[depth];
|
||||
|
||||
// Case A: The active cursor encounters the dot wildcard character ('.')
|
||||
if (c == '.') {
|
||||
for (int next_char = 0; next_char < C_TRIE_R; next_char++) {
|
||||
if (x->next[next_char]) {
|
||||
char token = (char)next_char;
|
||||
c_StringBuffer_Append(sb, &token, 1);
|
||||
|
||||
c_Trie_MatchWorker(x->next[next_char], sb, pattern, depth + 1, result);
|
||||
|
||||
// Backtracking unwinding step: reset logical buffer length context
|
||||
c_StringBuffer_SetLength(sb, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Case B: Explicit character absolute matching step
|
||||
else {
|
||||
if (x->next[c]) {
|
||||
char token = (char)c;
|
||||
c_StringBuffer_Append(sb, &token, 1);
|
||||
|
||||
c_Trie_MatchWorker(x->next[c], sb, pattern, depth + 1, result);
|
||||
|
||||
// Backtracking unwinding step: reset logical buffer length context
|
||||
c_StringBuffer_SetLength(sb, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all keys currently matching a specific wildcard pattern string
|
||||
*/
|
||||
c_err_t c_Trie_KeysThatMatch(c_Trie_t* self, const char* pattern, c_StringList* result) {
|
||||
if (!self || !pattern || !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 trie from root utilizing our string buffer builder
|
||||
c_Trie_MatchWorker(self->root, &sb, pattern, 0, result);
|
||||
|
||||
c_StringBuffer_Destroy(&sb);
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
/**
|
||||
* Find the longest key registered in the Trie that is a prefix of the query string.
|
||||
*/
|
||||
char* c_Trie_LongestPrefixOf(c_Trie_t* self, const char* query) {
|
||||
if (!self || !query || !self->root) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c_TrieNode_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;
|
||||
|
||||
// Iterate through characters of the query string sequentially
|
||||
for (c_size_t i = 0; i < query_len; i++) {
|
||||
unsigned char c = (unsigned char)query[i];
|
||||
curr = curr->next[c];
|
||||
|
||||
// If the path breaks, stop the search
|
||||
if (!curr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If this intermediate node marks a complete registered key, record its length
|
||||
if (curr->value != NULL) {
|
||||
longest_match_len = i + 1;
|
||||
match_found = C_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate an isolated heap buffer to hold the output copy string
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user