开始设计
This commit is contained in:
@@ -0,0 +1,515 @@
|
||||
#include <c_NlpTrie.h>
|
||||
|
||||
#include "c_Memory.h"
|
||||
#include <c_StringBuffer.h>
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
#define ALPHABET_SIZE 256
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
struct c_NlpTrieNode_t {
|
||||
struct c_NlpTrieNode_t* children[ALPHABET_SIZE];
|
||||
c_bool_t is_end_of_word;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_NlpTrieNode_t* c_NlpTrieNode_Create(void) {
|
||||
c_NlpTrieNode_t* node = (c_NlpTrieNode_t*)C_ALLOC(sizeof(*node));
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
node->is_end_of_word = C_FALSE;
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
node->children[i] = NULL;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
void c_NlpTrieNode_Destroy(c_NlpTrieNode_t* node) {
|
||||
C_FREE(node);
|
||||
}
|
||||
|
||||
static void c_NlpTrieNode_FreeSubtree(c_NlpTrieNode_t* node) {
|
||||
if (node == NULL) {
|
||||
return;
|
||||
}
|
||||
// 递归释放所有存活的子分支
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
if (node->children[i] != NULL) {
|
||||
c_NlpTrieNode_FreeSubtree(node->children[i]);
|
||||
}
|
||||
}
|
||||
// 使用用户指定的底层接口释放当前节点
|
||||
c_NlpTrieNode_Destroy(node);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpTrie_Init(c_NlpTrie_t* self) {
|
||||
if (!self) return C_ERR_PARAM;
|
||||
self->root = c_NlpTrieNode_Create();
|
||||
if (self->root == NULL) {
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
self->size = 0;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
void c_NlpTrie_Destroy(c_NlpTrie_t* self) {
|
||||
if (self == NULL || self->root == NULL) {
|
||||
return;
|
||||
}
|
||||
// 调用带深度扫描的子树释放函数
|
||||
c_NlpTrieNode_FreeSubtree(self->root);
|
||||
self->root = NULL;
|
||||
self->size = 0;
|
||||
}
|
||||
|
||||
c_err_t c_NlpTrie_Insert(c_NlpTrie_t* self, const char* word) {
|
||||
if (!self || !self->root || !word) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
c_NlpTrieNode_t* curr = self->root;
|
||||
for (c_size_t i = 0; word[i] != '\0'; i++) {
|
||||
// 转换为 uint8_t,确保中文字符等多字节编码(负值)在 0~255 范围内安全索引
|
||||
const uint8_t index = (uint8_t)word[i];
|
||||
|
||||
if (curr->children[index] == NULL) {
|
||||
curr->children[index] = c_NlpTrieNode_Create();
|
||||
if (curr->children[index] == NULL) {
|
||||
return C_ERR_NOMEM; // 内存池耗尽或分配失败
|
||||
}
|
||||
}
|
||||
curr = curr->children[index];
|
||||
}
|
||||
|
||||
curr->is_end_of_word = C_TRUE;
|
||||
self->size++;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_bool_t c_NlpTrie_Contains(c_NlpTrie_t* self, const char* word) {
|
||||
if (!self || !self->root || !word) {
|
||||
return C_FALSE;
|
||||
}
|
||||
|
||||
c_NlpTrieNode_t* curr = self->root;
|
||||
for (c_size_t i = 0; word[i] != '\0'; i++) {
|
||||
const uint8_t index = (uint8_t)word[i];
|
||||
|
||||
if (curr->children[index] == NULL) {
|
||||
return C_FALSE;
|
||||
}
|
||||
curr = curr->children[index];
|
||||
}
|
||||
|
||||
return curr->is_end_of_word;
|
||||
}
|
||||
|
||||
c_bool_t c_NlpTrie_HasStartWith(c_NlpTrie_t* self, const char* word) {
|
||||
if (!self || !self->root || !word) {
|
||||
return C_FALSE;
|
||||
}
|
||||
|
||||
c_NlpTrieNode_t* curr = self->root;
|
||||
for (c_size_t i = 0; word[i] != '\0'; i++) {
|
||||
const uint8_t index = (uint8_t)word[i];
|
||||
|
||||
if (curr->children[index] == NULL) {
|
||||
return C_FALSE;
|
||||
}
|
||||
curr = curr->children[index];
|
||||
}
|
||||
|
||||
return C_TRUE;
|
||||
}
|
||||
|
||||
c_err_t c_NlpTrie_LongestPrefixOf(c_NlpTrie_t* self, const char* text, char* result, c_size_t res_max_len) {
|
||||
if (!self || !self->root || !text || !result || res_max_len == 0) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 初始化返回值为空字符串
|
||||
result[0] = '\0';
|
||||
|
||||
c_NlpTrieNode_t* curr = self->root;
|
||||
c_size_t longest_len = 0; // 记录最长匹配的字符长度
|
||||
|
||||
// 遍历输入文本
|
||||
for (c_size_t i = 0; text[i] != '\0'; i++) {
|
||||
const uint8_t index = (uint8_t)text[i];
|
||||
|
||||
// 字符链断开,无法继续匹配更长的前缀,退出循环
|
||||
if (curr->children[index] == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
curr = curr->children[index];
|
||||
|
||||
// 如果当前节点是一个完整单词的结尾,更新最长匹配长度
|
||||
if (curr->is_end_of_word) {
|
||||
longest_len = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到了有效匹配,且长度在安全缓冲区范围内,进行拷贝
|
||||
if (longest_len > 0) {
|
||||
c_size_t copy_len = (longest_len < res_max_len) ? longest_len : (res_max_len - 1);
|
||||
strncpy(result, text, copy_len);
|
||||
result[copy_len] = '\0';
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_size_t c_NlpTrie_LongestPrefixOfLen(c_NlpTrie_t* self, const char* text) {
|
||||
if (!self || !self->root || !text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
c_NlpTrieNode_t* curr = self->root;
|
||||
c_size_t longest_len = 0;
|
||||
|
||||
for (c_size_t i = 0; text[i] != '\0'; i++) {
|
||||
const uint8_t index = (uint8_t)text[i];
|
||||
if (curr->children[index] == NULL) {
|
||||
break;
|
||||
}
|
||||
curr = curr->children[index];
|
||||
if (curr->is_end_of_word) {
|
||||
longest_len = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return longest_len;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* --- DFS Core Backtracking Engine with StringBuffer --- */
|
||||
|
||||
/**
|
||||
* @brief Internal recursive function to perform DFS using c_StringBuffer.
|
||||
* @param node Current Trie node being evaluated.
|
||||
* @param sb Pointer to the active string buffer tracking the current path.
|
||||
* @param output Pointer to the destination string list collector.
|
||||
* @return c_err_t C_ERR_OK on success, or C_ERR_NOMEM if string buffer allocation fails.
|
||||
*/
|
||||
static c_err_t c_NlpTrie_CollectDFS(c_NlpTrieNode_t* node, c_StringBuffer_t* sb, c_NlpStringList_t* output) {
|
||||
if (node == NULL) {
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
// 1. If this node marks an established word end, copy the raw payload directly into the list
|
||||
if (node->is_end_of_word) {
|
||||
c_err_t err = c_NlpStringList_Add(output, sb->buffer, sb->size);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
|
||||
// 2. Recurse down all 256 possible byte paths (handles raw ASCII and UTF-8 branches seamlessly)
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
if (node->children[i] != NULL) {
|
||||
char ch = (char)i;
|
||||
|
||||
// Append the single byte character onto the current tracking path path
|
||||
c_err_t err = c_StringBuffer_Append(sb, &ch, 1);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Deep traverse down the branch
|
||||
err = c_NlpTrie_CollectDFS(node->children[i], sb, output);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Backtrack: Remove the trailing byte character to restore original buffer size context
|
||||
err = c_StringBuffer_RemoveAt(sb, sb->size - 1, 1);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* --- Public Core API Implementation --- */
|
||||
|
||||
c_err_t c_NlpTrie_KeysWithPrefix(c_NlpTrie_t* self, const char* prefix, c_NlpStringList_t* output) {
|
||||
if (!self || !self->root || !prefix || !output) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 1. Initialize output list allocation safely
|
||||
c_err_t err = c_NlpStringList_Init(output, 4);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// 2. Locate the specific sub-root node where the given prefix stream terminates
|
||||
c_NlpTrieNode_t* curr = self->root;
|
||||
size_t prefix_len = 0;
|
||||
for (size_t i = 0; prefix[i] != '\0'; i++) {
|
||||
unsigned char index = (unsigned char)prefix[i];
|
||||
if (curr->children[index] == NULL) {
|
||||
// Prefix does not exist in the tree; return an empty list gracefully
|
||||
return C_ERR_OK;
|
||||
}
|
||||
curr = curr->children[index];
|
||||
prefix_len++;
|
||||
}
|
||||
|
||||
// 3. Initialize the temporary dynamic path buffer structure
|
||||
c_StringBuffer_t sb;
|
||||
err = c_StringBuffer_Init(&sb, prefix_len + 16);
|
||||
if (err != C_ERR_OK) {
|
||||
c_NlpStringList_Destroy(output);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 4. Pre-populate the tracking path layout with the found prefix string base
|
||||
err = c_StringBuffer_Append(&sb, prefix, prefix_len);
|
||||
if (err != C_ERR_OK) {
|
||||
c_StringBuffer_Destroy(&sb);
|
||||
c_NlpStringList_Destroy(output);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 5. Deploy DFS traversal starting from the located prefix sub-root
|
||||
err = c_NlpTrie_CollectDFS(curr, &sb, output);
|
||||
|
||||
// 6. Complete garbage collection on the temporary working buffer layout
|
||||
c_StringBuffer_Destroy(&sb);
|
||||
|
||||
// Rollback entirely if OOM conditions tripped during sub-branch traversals
|
||||
if (err != C_ERR_OK) {
|
||||
c_NlpStringList_Destroy(output);
|
||||
return err;
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Internal recursive engine to perform wildcard pattern matching traversal.
|
||||
* @param node Current Trie node being evaluated.
|
||||
* @param pattern The full pattern query string.
|
||||
* @param pattern_idx The current character position we are evaluating in the pattern.
|
||||
* @param sb Pointer to the dynamic string buffer managing the matching path.
|
||||
* @param output Pointer to the string list collector.
|
||||
* @return c_err_t C_ERR_OK on success, or execution allocation errors.
|
||||
*/
|
||||
static c_err_t c_NlpTrie_MatchDFS(c_NlpTrieNode_t* node, const char* pattern, size_t pattern_idx,
|
||||
c_StringBuffer_t* sb, c_NlpStringList_t* output) {
|
||||
if (node == NULL) {
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
char current_pattern_char = pattern[pattern_idx];
|
||||
|
||||
// Base Case: We reached the end of the pattern string
|
||||
if (current_pattern_char == '\0') {
|
||||
// If the current path structure forms a valid dictionary entry, collect it
|
||||
if (node->is_end_of_word) {
|
||||
c_err_t err = c_NlpStringList_Add(output, sb->buffer, sb->size);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
// Branch Case A: Wildcard character '.' matches any of the 256 branches
|
||||
if (current_pattern_char == '.') {
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
if (node->children[i] != NULL) {
|
||||
char ch = (char)i;
|
||||
|
||||
// Push branch byte onto string buffer path
|
||||
c_err_t err = c_StringBuffer_Append(sb, &ch, 1);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Move forward to the next node and advance pattern evaluation index
|
||||
err = c_NlpTrie_MatchDFS(node->children[i], pattern, pattern_idx + 1, sb, output);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Backtrack: Remove trailing byte character context
|
||||
err = c_StringBuffer_RemoveAt(sb, sb->size - 1, 1);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Branch Case B: Literal character match - process direct branch index
|
||||
else {
|
||||
unsigned char index = (unsigned char)current_pattern_char;
|
||||
if (node->children[index] != NULL) {
|
||||
// Push literal character byte onto string buffer path
|
||||
c_err_t err = c_StringBuffer_Append(sb, ¤t_pattern_char, 1);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Recurse to next node
|
||||
err = c_NlpTrie_MatchDFS(node->children[index], pattern, pattern_idx + 1, sb, output);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Backtrack
|
||||
err = c_StringBuffer_RemoveAt(sb, sb->size - 1, 1);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* --- Public Core API Implementation --- */
|
||||
|
||||
c_err_t c_NlpTrie_KeysThatMatch(c_NlpTrie_t* self, const char* pattern, c_NlpStringList_t* output) {
|
||||
if (!self || !self->root || !pattern || !output) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// Initialize list to collect matches (initial capacity of 4 items)
|
||||
c_err_t err = c_NlpStringList_Init(output, 4);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// Initialize tracking string buffer path to dynamically record characters
|
||||
c_StringBuffer_t sb;
|
||||
c_size_t estimate_len = strlen(pattern);
|
||||
err = c_StringBuffer_Init(&sb, estimate_len + 4);
|
||||
if (err != C_ERR_OK) {
|
||||
c_NlpStringList_Destroy(output);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Deploy the recursive match search starting at the root layout node
|
||||
err = c_NlpTrie_MatchDFS(self->root, pattern, 0, &sb, output);
|
||||
|
||||
// Clean up temporary path string buffer state
|
||||
c_StringBuffer_Destroy(&sb);
|
||||
|
||||
if (err != C_ERR_OK) {
|
||||
c_NlpStringList_Destroy(output); // Rollback dynamic arrays on inner traversal failures
|
||||
return err;
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/**
|
||||
* @brief Internal helper to check if a node contains any active child pointer.
|
||||
* @return c_bool_t C_TRUE if completely empty of branches, otherwise C_FALSE.
|
||||
*/
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_bool_t c_NlpTrieNode_IsEmpty(c_NlpTrieNode_t* node) {
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
if (node->children[i] != NULL) {
|
||||
return C_FALSE; // Found an active sub-branch path
|
||||
}
|
||||
}
|
||||
return C_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal recursive engine to trace characters and clean up dead nodes bottom-up.
|
||||
* @param node Current Trie node being evaluated.
|
||||
* @param word The target deletion string text stream.
|
||||
* @param depth The current character offset index.
|
||||
* @param deleted Output flag signaling the parent layout layer that the child was destroyed.
|
||||
* @return c_NlpTrieNode_t* Returns the adjusted pointer state of the evaluated node to update its parent.
|
||||
*/
|
||||
static c_NlpTrieNode_t* c_NlpTrie_DeleteDFS(c_NlpTrieNode_t* node, const char* word, size_t depth, c_bool_t* deleted) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Base Case: We have fully processed the last byte character of the word
|
||||
if (word[depth] == '\0') {
|
||||
if (node->is_end_of_word) {
|
||||
node->is_end_of_word = C_FALSE; // Deactivate word completion flag
|
||||
*deleted = C_TRUE;
|
||||
}
|
||||
|
||||
// If this node serves no purpose for another word suffix, mark it for pruning
|
||||
if (c_NlpTrieNode_IsEmpty(node)) {
|
||||
c_NlpTrieNode_Destroy(node);
|
||||
return NULL;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Recursive Step: Extract index byte and traverse deeper down the branch
|
||||
const uint8_t index = (uint8_t)word[depth];
|
||||
node->children[index] = c_NlpTrie_DeleteDFS(node->children[index], word, depth + 1, deleted);
|
||||
|
||||
// Post-Order Backtracking Pruning: Evaluate this node on the way up
|
||||
// We can only prune this node if:
|
||||
// 1. It is not marked as the end of a shorter word (is_end_of_word == C_FALSE)
|
||||
// 2. It has no other active branches left hanging under it
|
||||
if (node->is_end_of_word == C_FALSE && c_NlpTrieNode_IsEmpty(node)) {
|
||||
c_NlpTrieNode_Destroy(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* --- Public Core API Implementation --- */
|
||||
|
||||
c_err_t c_NlpTrie_Delete(c_NlpTrie_t* self, const char* word) {
|
||||
if (!self || !self->root || !word) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// Edge Case: Prevent actions if attempting to pass an empty string
|
||||
if (word[0] == '\0') {
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_bool_t deleted = C_FALSE;
|
||||
|
||||
// Execute the recursive deletion pattern starting right from the root node layer
|
||||
self->root = c_NlpTrie_DeleteDFS(self->root, word, 0, &deleted);
|
||||
|
||||
// Safety fallback: If the entire tree was pruned down, restore a valid base root node layout
|
||||
if (self->root == NULL) {
|
||||
self->root = c_NlpTrieNode_Create();
|
||||
if (self->root == NULL) {
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpTrie_Clear(c_NlpTrie_t* self) {
|
||||
// 1. Guard against invalid instances or missing base parameters
|
||||
if (!self || !self->root) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 2. Loop through all 256 structural starting slot positions of the root node
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
if (self->root->children[i] != NULL) {
|
||||
// Recursively destroy every deeper sub-branch path found
|
||||
c_NlpTrieNode_FreeSubtree(self->root->children[i]);
|
||||
self->root->children[i] = NULL; // Explicitly nullify pointer to prevent dangling handles
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Reset the root node's completion flag context
|
||||
self->root->is_end_of_word = C_FALSE;
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user