#include #include #include #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; }