开始设计

This commit is contained in:
2026-08-10 01:21:15 +08:00
commit e45398991f
228 changed files with 20827 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
#include <c_NlpStringList.h>
#include <c_Memory.h>
#define DEFAULT_INIT_CAPACITY 4
c_err_t c_NlpStringList_Init(c_NlpStringList_t* list, c_size_t capacity) {
if (!list ) return C_ERR_PARAM;
list->count = 0;
list->capacity = (capacity==0)?DEFAULT_INIT_CAPACITY:capacity; // Start small, grow exponentially
list->items = (char**)C_ALLOC(sizeof(char*) * list->capacity);
if (!list->items) return C_ERR_NOMEM;
return C_ERR_OK;
}
void c_NlpStringList_Destroy(c_NlpStringList_t* list) {
if (!list) return;
if (list->items) {
for (size_t i = 0; i < list->count; i++) {
C_FREE(list->items[i]);
}
C_FREE(list->items);
}
list->count = 0;
list->capacity = 0;
}
#include <string.h>
/**
* @brief Appends a length-bounded string segment to the string list.
* @param list Pointer to the active string list instance.
* @param str Pointer to the source string segment.
* @param str_length The length of characters to copy.
* @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid inputs, or C_ERR_NOMEM on allocation failure.
*/
c_err_t c_NlpStringList_Add(c_NlpStringList_t* list, const char* str, c_size_t str_length) {
if (!list || !list->items || !str || str_length == 0) {
return C_ERR_PARAM;
}
// 1. Double the internal storage capacity exponentially if the array bounds hit limits
if (list->count >= list->capacity) {
c_size_t new_capacity = list->capacity == 0 ? 4 : list->capacity * 2;
char** new_items = (char**)C_ALLOC(sizeof(char*) * new_capacity);
if (!new_items) return C_ERR_NOMEM;
if (list->items && list->count > 0) {
memcpy(new_items, list->items, sizeof(char*) * list->count);
}
C_FREE(list->items);
list->items = new_items;
list->capacity = new_capacity;
}
// 2. Allocate an isolated target heap layout tracking buffer segment (+1 for '\0')
char* copy = (char*)C_ALLOC(str_length + 1);
if (!copy) return C_ERR_NOMEM;
// 3. Duplicate raw segment content and secure the terminal null byte
memcpy(copy, str, str_length);
copy[str_length] = '\0';
list->items[list->count++] = copy;
return C_ERR_OK;
}
/**
* @brief Appends a null-terminated string to the string list.
* @param list Pointer to the active string list instance.
* @param str Pointer to the null-terminated source string.
* @return c_err_t C_ERR_OK on success, or parameter/ OOM error codes.
*/
c_err_t c_NlpStringList_AddStr(c_NlpStringList_t* list, const char* str) {
if (!str) return C_ERR_PARAM;
return c_NlpStringList_Add(list, str, strlen(str));
}
c_err_t c_NlpStringList_Remove(c_NlpStringList_t* list, c_size_t index) {
// 1. Guard against invalid instances and out-of-bound indices
if (!list || index >= list->count) {
return C_ERR_PARAM;
}
// 2. Safely free the dynamic string allocation to prevent memory leaks
if (list->items[index] != NULL) {
C_FREE(list->items[index]);
}
// 3. Shift subsequent string pointers left to fill the gap
const c_size_t num_elements_to_shift = list->count - index - 1;
if (num_elements_to_shift > 0) {
memmove(&list->items[index],
&list->items[index + 1],
sizeof(char*) * num_elements_to_shift);
}
// 4. Decrement the structural item count
list->count--;
return C_ERR_OK;
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef INCLUDED_C_NLPSTRINGLIST_H
#define INCLUDED_C_NLPSTRINGLIST_H
#ifndef INCLUDED_C_BASE_H
#include <c_Base.h>
#endif /*INCLUDED_C_BASE_H*/
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
typedef struct {
char** items; // Array of string pointers
c_size_t count; // Current number of items in the list
c_size_t capacity; // Total allocated capacity of the items array
} c_NlpStringList_t;
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_NlpStringList_Init(c_NlpStringList_t* self, c_size_t capacity);
void c_NlpStringList_Destroy(c_NlpStringList_t* self);
c_err_t c_NlpStringList_Add(c_NlpStringList_t* list, const char* str, c_size_t str_length);
c_err_t c_NlpStringList_AddStr(c_NlpStringList_t* list, const char* str);
c_err_t c_NlpStringList_Remove(c_NlpStringList_t* list, c_size_t index);
#endif /*INCLUDED_C_NLPSTRINGLIST_H*/
+110
View File
@@ -0,0 +1,110 @@
#include "c_NlpStringList.h"
#include <stdlib.h>
#include <stdio.h>
#define RUN_LIST_TEST(condition, test_name) \
do { \
printf("[TEST] %s... ", test_name); \
if (condition) { \
printf("\033[32mPASSED\033[0m\n"); \
} else { \
printf("\033[31mFAILED\033[0m (at Line %d)\n", __LINE__); \
return C_ERR_FAIL; \
} \
} while(0)
/**
* @brief Complete unit testing suite for the updated c_NlpStringList component.
* @return c_err_t Returns C_ERR_OK if all testing blocks pass.
*/
c_err_t c_NlpStringList_UnitTest(void) {
c_NlpStringList_t list;
c_err_t err;
printf("==================================================\n");
printf(" STARTING C_NLPSTRINGLIST UNIT TESTING \n");
printf("==================================================\n");
/* 1. Defensive Boundary Parameter Safety Tests */
RUN_LIST_TEST(c_NlpStringList_Init(NULL, 10) == C_ERR_PARAM, "Init handles NULL structure pointer");
RUN_LIST_TEST(c_NlpStringList_AddStr(NULL, "data") == C_ERR_PARAM, "Add safely rejects NULL list context");
RUN_LIST_TEST(c_NlpStringList_AddStr(&list, NULL) == C_ERR_PARAM, "Add safely rejects NULL input strings (before init)");
RUN_LIST_TEST(c_NlpStringList_Remove(NULL, 0) == C_ERR_PARAM, "Remove safely rejects NULL list reference");
/* 2. Custom Configured Capacity Initialization Test */
err = c_NlpStringList_Init(&list, 2); // Initialized with small capacity to ease expansion verification
RUN_LIST_TEST(err == C_ERR_OK, "Initialization with explicit capacity returns C_ERR_OK");
RUN_LIST_TEST(list.count == 0, "Structural items counter starts at 0");
RUN_LIST_TEST(list.capacity == 2, "Allocated capacity exactly mirrors input parameter");
RUN_LIST_TEST(list.items != NULL, "Internal cluster storage pointer successfully bound");
/* 3. Sequential Elements Add & Read Validation */
err = c_NlpStringList_AddStr(&list, "Apple");
err |= c_NlpStringList_AddStr(&list, "Banana");
RUN_LIST_TEST(err == C_ERR_OK, "Successfully pushed elements up to capacity limit");
RUN_LIST_TEST(list.count == 2, "Count correctly calculated at 2");
RUN_LIST_TEST(list.capacity == 2, "Capacity remains unchanged before hitting boundary");
RUN_LIST_TEST(strcmp(list.items[0], "Apple") == 0, "Slot 0 retains string 'Apple'");
RUN_LIST_TEST(strcmp(list.items[1], "Banana") == 0, "Slot 1 retains string 'Banana'");
/* 4. Automated Array Expansion and Reallocation Safeguard */
err = c_NlpStringList_AddStr(&list, "Cherry"); // Hits boundary, forcing C_ALLOC internal upscale expansion
RUN_LIST_TEST(err == C_ERR_OK, "Pushed 3rd string successfully past boundary limits");
RUN_LIST_TEST(list.count == 3, "Count advanced to 3");
RUN_LIST_TEST(list.capacity == 4, "Capacity doubled exponentially from 2 to 4");
RUN_LIST_TEST(strcmp(list.items[2], "Cherry") == 0, "Post-reallocation element validation holds 'Cherry'");
/* 5. Memory Shift Extraction Performance (Remove Verification) */
// Current setup state: ["Apple", "Banana", "Cherry"]
// Test wild indices out of bounds protection
RUN_LIST_TEST(c_NlpStringList_Remove(&list, 999) == C_ERR_PARAM, "Rejects wild indices beyond context array boundary");
RUN_LIST_TEST(c_NlpStringList_Remove(&list, list.count) == C_ERR_PARAM, "Rejects index exactly hitting edge limit");
// Remove middle slot element ("Banana")
err = c_NlpStringList_Remove(&list, 1);
RUN_LIST_TEST(err == C_ERR_OK, "Successfully extracted middle entry at Index 1");
RUN_LIST_TEST(list.count == 2, "Total item count updated smoothly down to 2");
// Verify remaining contents moved left continuously without forming holes
RUN_LIST_TEST(strcmp(list.items[0], "Apple") == 0, "Slot 0 continues holding 'Apple'");
RUN_LIST_TEST(strcmp(list.items[1], "Cherry") == 0, "Slot 1 successfully consolidated to 'Cherry'");
// Remove remaining front element ("Apple")
err = c_NlpStringList_Remove(&list, 0);
RUN_LIST_TEST(err == C_ERR_OK, "Extracted front leading element at Index 0");
RUN_LIST_TEST(list.count == 1, "Count downshifted to 1");
RUN_LIST_TEST(strcmp(list.items[0], "Cherry") == 0, "Slot 0 now rolled over to 'Cherry'");
/* 6. Multi-Byte UTF-8 String Asset Preservation */
err = c_NlpStringList_AddStr(&list, "深度学习与大模型");
RUN_LIST_TEST(err == C_ERR_OK, "Added complex multi-byte Chinese token string");
RUN_LIST_TEST(strcmp(list.items[1], "深度学习与大模型") == 0, "Multi-byte raw tracking array verification holds");
/* 7. Garbage Collection & Prevent Secondary Dangling Freeing */
c_NlpStringList_Destroy(&list);
RUN_LIST_TEST(list.items == NULL, "Array storage pointer nullified upon destruction sequence");
RUN_LIST_TEST(list.count == 0, "Counters reset cleanly to 0");
RUN_LIST_TEST(list.capacity == 0, "Capacity indicators initialized to 0");
// Idempotent test validation
c_NlpStringList_Destroy(&list);
c_NlpStringList_Destroy(NULL);
printf("[TEST] Double structural destruction safety... \033[32mPASSED\033[0m\n");
printf("==================================================\n");
printf("\033[32mSUCCESS: ALL REVISED LIST SPECIFICATION TESTS PASSED!\033[0m\n");
printf("==================================================\n");
return C_ERR_OK;
}
int main(void) {
if (c_NlpStringList_UnitTest() != C_ERR_OK) {
return -1;
}
return 0;
}
+515
View File
@@ -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, &current_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;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef INCLUDED_C_NLPTRIE_H
#define INCLUDED_C_NLPTRIE_H
#ifndef INCLUDED_C_BASE_H
#include <c_Base.h>
#endif /*INCLUDED_C_BASE_H*/
#ifndef INCLUDED_C_NLPSTRINGLIST_H
#include <c_NlpStringList.h>
#endif /*INCLUDED_C_NLPSTRINGLIST_H*/
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
typedef struct c_NlpTrieNode_t c_NlpTrieNode_t;
typedef struct {
c_NlpTrieNode_t* root;
c_size_t size;
}c_NlpTrie_t;
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_err_t c_NlpTrie_Init(c_NlpTrie_t* self);
void c_NlpTrie_Destroy(c_NlpTrie_t* self);
c_err_t c_NlpTrie_Insert(c_NlpTrie_t* self, const char* word);
c_bool_t c_NlpTrie_Contains(c_NlpTrie_t* self, const char* word);
c_bool_t c_NlpTrie_HasStartWith(c_NlpTrie_t* self, const char* word);
c_err_t c_NlpTrie_LongestPrefixOf(c_NlpTrie_t* self, const char* text, char* result, size_t res_max_len);
c_size_t c_NlpTrie_LongestPrefixOfLen(c_NlpTrie_t* self, const char* text);
c_err_t c_NlpTrie_KeysWithPrefix(c_NlpTrie_t* self, const char* prefix, c_NlpStringList_t* output);
c_err_t c_NlpTrie_KeysThatMatch(c_NlpTrie_t* self, const char* pattern, c_NlpStringList_t* output);
c_err_t c_NlpTrie_Delete(c_NlpTrie_t* self, const char* word);
/**
* @brief Resets and purges all words and branches inside the Trie while preserving the root instance.
* @param self Pointer to the active Trie instance.
* @return c_err_t C_ERR_OK on successful clearing, or C_ERR_PARAM if the pointer instance is invalid.
*/
c_err_t c_NlpTrie_Clear(c_NlpTrie_t* self);
#endif /*INCLUDED_C_NLPTRIE_H*/
+276
View File
@@ -0,0 +1,276 @@
#include "c_NlpTrie.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <assert.h>
/* --- 自定义测试断言宏 --- */
#define RUN_TEST(test_case, name) \
do { \
printf("[RUN] %s... ", name); \
if (test_case) { \
printf("\033[32mPASSED\033[0m\n"); \
} else { \
printf("\033[31mFAILED\033[0m (%s:%d)\n", __FILE__, __LINE__); \
return C_ERR_FAIL; \
} \
} while(0)
/**
* @brief c_NlpTrie 模块标准单元测试函数
* @return c_err_t 返回 C_ERR_OK 表示全部通过,返回 C_ERROR 表示有测试项失败
*/
c_err_t c_NlpTrie_UnitTest(void) {
c_NlpTrie_t trie;
c_err_t err;
printf("========================================\n");
printf(" STARTING c_NlpTrie UNIT TESTS \n");
printf("========================================\n");
/* 1. 防御性边界测试 (Null Pointer Protection) */
RUN_TEST(c_NlpTrie_Init(NULL) == C_ERR_PARAM, "Init with NULL pointer");
RUN_TEST(c_NlpTrie_Insert(NULL, "test") == C_ERR_PARAM, "Insert with NULL self");
RUN_TEST(c_NlpTrie_Insert(&trie, NULL) == C_ERR_PARAM, "Insert with NULL word (before init)");
RUN_TEST(c_NlpTrie_Contains(NULL, "test") == C_FALSE, "Search with NULL self");
RUN_TEST(c_NlpTrie_HasStartWith(NULL, "test") == C_FALSE, "HasStartWith with NULL self");
/* 2. 初始化测试 (Initialization) */
err = c_NlpTrie_Init(&trie);
RUN_TEST(err == C_ERR_OK && trie.root != NULL, "Normal initialization");
/* 3. 基础插入与精确查找测试 (Basic Insert & Search) */
err = c_NlpTrie_Insert(&trie, "nlp");
RUN_TEST(err == C_ERR_OK, "Insert normal word 'nlp'");
RUN_TEST(c_NlpTrie_Contains(&trie, "nlp") == C_TRUE, "Search existing word 'nlp'");
RUN_TEST(c_NlpTrie_Contains(&trie, "nl") == C_FALSE, "Search non-existing shorter word 'nl'");
RUN_TEST(c_NlpTrie_Contains(&trie, "nlps") == C_FALSE, "Search non-existing longer word 'nlps'");
/* 4. 前缀包含关系与空字符串测试 (Prefix & Edge cases) */
err = c_NlpTrie_Insert(&trie, "apple");
err |= c_NlpTrie_Insert(&trie, "app");
RUN_TEST(err == C_ERR_OK, "Insert words with shared prefix ('apple', 'app')");
RUN_TEST(c_NlpTrie_Contains(&trie, "app") == C_TRUE, "Search shared prefix word 'app'");
RUN_TEST(c_NlpTrie_Contains(&trie, "apple") == C_TRUE, "Search full word 'apple'");
// 空字符串通常作为根节点本身的结尾标记(如果允许插入)
err = c_NlpTrie_Insert(&trie, "");
RUN_TEST(err == C_ERR_OK, "Insert empty string ''");
RUN_TEST(c_NlpTrie_Contains(&trie, "") == C_TRUE, "Search empty string ''");
/* 5. 256 全字符集测试 (UTF-8 Chinese & ASCII Symbols) */
// 包含:全角符号、大写英文、空格、数字、扩展 ASCII
err = c_NlpTrie_Insert(&trie, "自然语言处理_v2.0");
RUN_TEST(err == C_ERR_OK, "Insert complex UTF-8 word with symbols and numbers");
RUN_TEST(c_NlpTrie_Contains(&trie, "自然语言处理_v2.0") == C_TRUE, "Search complex UTF-8 word");
RUN_TEST(c_NlpTrie_Contains(&trie, "自然语言") == C_FALSE, "Search non-existing sub-word '自然语言'");
/* 6. 前缀查找功能测试 (HasStartWith) */
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自然") == C_TRUE, "HasStartWith existing Chinese prefix");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自然语言处理_v2.0") == C_TRUE, "HasStartWith full match as prefix");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自燃") == C_FALSE, "HasStartWith non-existing Chinese prefix");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "nl") == C_TRUE, "HasStartWith existing English prefix");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "xyz") == C_FALSE, "HasStartWith non-existing English prefix");
/* 7. 销毁与悬空安全测试 (Destroy & Safety) */
c_NlpTrie_Destroy(&trie);
RUN_TEST(trie.root == NULL, "Trie root set to NULL after destroy");
// 销毁后的二次防御调用不应引发崩溃
RUN_TEST(c_NlpTrie_Contains(&trie, "nlp") == C_FALSE, "Search on destroyed trie");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "nlp") == C_FALSE, "HasStartWith on destroyed trie");
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_NlpTrie_Init(&trie);
/* 8. 最长前缀匹配测试 (LongestPrefixOf) */
char res_buf[128];
// 准备数据
c_NlpTrie_Insert(&trie, "自然");
c_NlpTrie_Insert(&trie, "自然语言");
c_NlpTrie_Insert(&trie, "自然语言处理");
c_NlpTrie_Insert(&trie, "nlp");
// 测试点 1:有多重匹配时,应当贪婪匹配最长的一个
c_NlpTrie_LongestPrefixOf(&trie, "自然语言处理核心技术", res_buf, sizeof(res_buf));
RUN_TEST(strcmp(res_buf, "自然语言处理") == 0, "LongestPrefixOf greedy match '自然语言处理'");
// 测试点 2:部分输入匹配,落到中途的有效单词上
c_NlpTrie_LongestPrefixOf(&trie, "自然语言学习", res_buf, sizeof(res_buf));
RUN_TEST(strcmp(res_buf, "自然语言") == 0, "LongestPrefixOf sub-match '自然语言'");
// 测试点 3:完全无法匹配的情况
c_NlpTrie_LongestPrefixOf(&trie, "人工智能", res_buf, sizeof(res_buf));
RUN_TEST(strcmp(res_buf, "") == 0, "LongestPrefixOf no match returns empty string");
// 测试点 4:英文前缀匹配
c_NlpTrie_LongestPrefixOf(&trie, "nlpsolver", res_buf, sizeof(res_buf));
RUN_TEST(strcmp(res_buf, "nlp") == 0, "LongestPrefixOf English word 'nlp'");
c_NlpTrie_Destroy(&trie);
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
/* 9. Autocomplete Prefix Matching Tests (KeysWithPrefix) */
c_NlpTrie_Init(&trie);
c_NlpStringList_t matches;
c_NlpTrie_Insert(&trie, "app");
c_NlpTrie_Insert(&trie, "apple");
c_NlpTrie_Insert(&trie, "apricot");
c_NlpTrie_Insert(&trie, "banana");
c_NlpTrie_Insert(&trie, "自然语言");
c_NlpTrie_Insert(&trie, "自然语言处理");
// Test Point 1: Match English Prefix 'ap' (Expect: app, apple, apricot)
err = c_NlpTrie_KeysWithPrefix(&trie, "ap", &matches);
RUN_TEST(err == C_ERR_OK && matches.count == 3, "KeysWithPrefix found 3 matches for 'ap'");
RUN_TEST(strcmp(matches.items[0], "app") == 0, "Matches item 0 is 'app'");
RUN_TEST(strcmp(matches.items[1], "apple") == 0, "Matches item 1 is 'apple'");
RUN_TEST(strcmp(matches.items[2], "apricot") == 0, "Matches item 2 is 'apricot'");
c_NlpStringList_Destroy(&matches);
// Test Point 2: Match UTF-8 Chinese Prefix (Expect: 自然语言, 自然语言处理)
err = c_NlpTrie_KeysWithPrefix(&trie, "自然", &matches);
RUN_TEST(err == C_ERR_OK && matches.count == 2, "KeysWithPrefix found 2 matches for '自然'");
c_NlpStringList_Destroy(&matches);
// Test Point 3: Search non-existent prefix (Expect: 0 items found, no memory leaks)
err = c_NlpTrie_KeysWithPrefix(&trie, "unknown", &matches);
RUN_TEST(err == C_ERR_OK && matches.count == 0, "KeysWithPrefix returned 0 items on empty mismatch");
c_NlpStringList_Destroy(&matches);
c_NlpTrie_Destroy(&trie);
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
c_NlpTrie_Init(&trie);
/* 13. Wildcard Query Traversal Validation (KeysThatMatch) */
c_NlpStringList_t query_results;
c_NlpTrie_Insert(&trie, "cat");
c_NlpTrie_Insert(&trie, "cot");
c_NlpTrie_Insert(&trie, "coat");
c_NlpTrie_Insert(&trie, "dog");
c_NlpTrie_Insert(&trie, "自然");
c_NlpTrie_Insert(&trie, "自燃");
// Test Case 1: Simple single wildcard slot match (Expect: "cat", "cot")
err = c_NlpTrie_KeysThatMatch(&trie, "c.t", &query_results);
RUN_TEST(err == C_ERR_OK && query_results.count == 2, "KeysThatMatch finds exactly 2 terms matching pattern 'c.t'");
RUN_TEST(strcmp(query_results.items[0], "cat") == 0, "First matching match found: 'cat'");
RUN_TEST(strcmp(query_results.items[1], "cot") == 0, "Second matching match found: 'cot'");
c_NlpStringList_Destroy(&query_results);
// Test Case 2: Full wildcard string constraint match length (Expect: "dog")
err = c_NlpTrie_KeysThatMatch(&trie, "...", &query_results);
RUN_TEST(err == C_ERR_OK && query_results.count == 3, "Pattern '...' pulls exact length matches ('cat', 'cot', 'dog')");
c_NlpStringList_Destroy(&query_results);
// Test Case 3: Complex multi-byte matching (Remember: in UTF-8, 1 Chinese Character = 3 Bytes)
// To match a single trailing Chinese character change on "自*", we need 3 dots "自..."
err = c_NlpTrie_KeysThatMatch(&trie, "自...", &query_results);
RUN_TEST(err == C_ERR_OK && query_results.count == 2, "Multi-byte pattern verification matches both '自然' and '自燃'");
c_NlpStringList_Destroy(&query_results);
// Test Case 4: Zero match behavior
err = c_NlpTrie_KeysThatMatch(&trie, "c..t", &query_results); // Matches "coat"
RUN_TEST(err == C_ERR_OK && query_results.count == 1, "Pattern 'c..t' correctly identifies structural length match 'coat'");
c_NlpStringList_Destroy(&query_results);
c_NlpTrie_Destroy(&trie);
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
/* 14. Dictionary Node Deletion & Path Pruning Verification (c_NlpTrie_Delete) */
c_NlpTrie_Init(&trie);
c_NlpTrie_Insert(&trie, "app");
c_NlpTrie_Insert(&trie, "apple");
c_NlpTrie_Insert(&trie, "banana");
// Test Point 1: Parameter validation checking
RUN_TEST(c_NlpTrie_Delete(NULL, "app") == C_ERR_PARAM, "Delete handles NULL self context pointer");
RUN_TEST(c_NlpTrie_Delete(&trie, NULL) == C_ERR_PARAM, "Delete handles NULL string inputs");
// Test Point 2: Deleting a word that is a prefix of another word ("app")
// Expected: "app" flag goes off, but "apple" nodes must stay intact
err = c_NlpTrie_Delete(&trie, "app");
RUN_TEST(err == C_ERR_OK, "Delete intermediate prefix word 'app' successfully");
RUN_TEST(c_NlpTrie_Contains(&trie, "app") == C_FALSE, "Word 'app' is no longer searchable");
RUN_TEST(c_NlpTrie_Contains(&trie, "apple") == C_TRUE, "Longer word 'apple' remains fully searchable");
// Test Point 3: Deleting a word that leaves isolated nodes behind ("apple")
// Expected: The path nodes matching 'l' and 'e' must be pruned to avoid memory leaks
err = c_NlpTrie_Delete(&trie, "apple");
RUN_TEST(err == C_ERR_OK, "Delete trailing leaf word 'apple' successfully");
RUN_TEST(c_NlpTrie_Contains(&trie, "apple") == C_FALSE, "Word 'apple' is no longer searchable");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "ap") == C_FALSE, "Prefix path 'ap' is completely pruned");
// Test Point 4: Non-existent word cleanup verification
err = c_NlpTrie_Delete(&trie, "orange");
RUN_TEST(err == C_ERR_OK, "Deleting non-existent word exits cleanly without modification");
RUN_TEST(c_NlpTrie_Contains(&trie, "banana") == C_TRUE, "Unrelated word 'banana' is unaffected");
c_NlpTrie_Destroy(&trie);
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
/* 15. Trie Flush Re-initialization Verification (c_NlpTrie_Clear) */
c_NlpTrie_Init(&trie);
c_NlpTrie_Insert(&trie, "nlp");
c_NlpTrie_Insert(&trie, "自然语言");
// Test Point 1: Parameter validation checking
RUN_TEST(c_NlpTrie_Clear(NULL) == C_ERR_PARAM, "Clear handles NULL self context pointer safely");
// Test Point 2: Execution clearance tracking
err = c_NlpTrie_Clear(&trie);
RUN_TEST(err == C_ERR_OK, "Clear flushes all sub-branch contents successfully");
RUN_TEST(trie.root != NULL, "Trie structural base root node is preserved");
RUN_TEST(c_NlpTrie_Contains(&trie, "nlp") == C_FALSE, "Previously stored word 'nlp' is no longer found");
RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自然") == C_FALSE, "Prefix indicators successfully cleared");
// Test Point 3: Verification of re-insertion stability post-clearance
err = c_NlpTrie_Insert(&trie, "reborn");
RUN_TEST(err == C_ERR_OK, "Trie accepts new entry additions seamlessly after being cleared");
RUN_TEST(c_NlpTrie_Contains(&trie, "reborn") == C_TRUE, "Newly added post-clearance key is fully searchable");
// Test Point 4: Idempotent clearing check (sequential empty clears)
err = c_NlpTrie_Clear(&trie);
err |= c_NlpTrie_Clear(&trie);
RUN_TEST(err == C_ERR_OK, "Continuous back-to-back clear calls execute safely with no side-effects");
c_NlpTrie_Destroy(&trie);
/* ------------------------------------------------------------------------------------------------------------------ */
/* */
// 允许重复销毁(幂等性保护)
c_NlpTrie_Destroy(&trie);
printf("========================================\n");
printf("\033[32mALL c_NlpTrie TESTS PASSED SUCCESSFULLY!\033[0m\n");
printf("========================================\n");
return C_ERR_OK;
}
/* --- 测试执行入口 --- */
int main(void) {
// 执行单元测试
c_err_t result = c_NlpTrie_UnitTest();
if (result != C_ERR_OK) {
return -1;
}
return 0;
}