开始设计
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# ==============================================================================
|
||||
# 宏定义:CREATE_LIBRARY_FROM_DIRS
|
||||
# 参数 1: TARGET_NAME - 要生成的库的名称
|
||||
# 参数 2: LIB_TYPE - 库类型(STATIC 或 SHARED)
|
||||
# 参数 3: SOURCE_DIRS - 需要扫描源码的目录列表(分号分隔的字符串)
|
||||
# 参数 4: EXCLUDE_FILES - 需要额外过滤的文件名列表(分号分隔的字符串,支持正则)
|
||||
# ==============================================================================
|
||||
macro(create_library_from_dirs TARGET_NAME LIB_TYPE SOURCE_DIRS EXCLUDE_FILES)
|
||||
set(ALL_SOURCES "")
|
||||
add_library(${TARGET_NAME} ${LIB_TYPE} "")
|
||||
# 1. 遍历传入的每一个目录
|
||||
foreach(DIR ${SOURCE_DIRS})
|
||||
# 统一处理相对路径和绝对路径
|
||||
if(NOT IS_ABSOLUTE "${DIR}")
|
||||
set(DIR "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}")
|
||||
endif()
|
||||
|
||||
# 检查目录是否存在,防止写错路径导致报错
|
||||
if(EXISTS "${DIR}")
|
||||
# 2. 收集当前目录下所有的 .c, .s, .S 文件
|
||||
file(GLOB ALL_FILES
|
||||
"${DIR}/*.c"
|
||||
"${DIR}/*.s"
|
||||
"${DIR}/*.S"
|
||||
)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${DIR})
|
||||
message(STATUS "[${TARGET_NAME}] INCLUDE ${DIR}")
|
||||
|
||||
|
||||
foreach(FILE_PATH ${ALL_FILES})
|
||||
# 获取纯文件名(例如:Compiler_GNUC.c),方便精准匹配
|
||||
get_filename_component(FILE_NAME "${FILE_PATH}" NAME)
|
||||
|
||||
# 检查是否匹配自定义的排除列表
|
||||
set(SHOULD_EXCLUDE FALSE)
|
||||
foreach(EX_PATTERN ${EXCLUDE_FILES})
|
||||
# 支持完整文件名匹配或正则表达式匹配
|
||||
if("${FILE_NAME}" STREQUAL "${EX_PATTERN}" OR "${FILE_NAME}" MATCHES "${EX_PATTERN}")
|
||||
set(SHOULD_EXCLUDE TRUE)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# 4. 执行过滤判断
|
||||
if(FILE_PATH MATCHES "\\.t\\.c$")
|
||||
# 过滤单元测试文件
|
||||
list(APPEND UNIT_TEST_SOURCES ${FILE_PATH})
|
||||
elseif(SHOULD_EXCLUDE)
|
||||
# 过滤用户指定的特定文件
|
||||
message(STATUS "[${TARGET_NAME}] EXCLUDED specific file: ${FILE_NAME} from target ${TARGET_NAME}")
|
||||
else()
|
||||
# 留下的才是有效的库源码
|
||||
list(APPEND ALL_SOURCES ${FILE_PATH})
|
||||
endif()
|
||||
endforeach()
|
||||
else()
|
||||
message(WARNING "[${TARGET_NAME}] Directory does not exist: ${DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# 4. 确保找到了有效的源代码再创建库
|
||||
list(LENGTH ALL_SOURCES SOURCE_COUNT)
|
||||
if(SOURCE_COUNT GREATER 0)
|
||||
# add_library(${TARGET_NAME} ${LIB_TYPE} ${ALL_SOURCES})
|
||||
target_sources(${TARGET_NAME} PUBLIC ${ALL_SOURCES})
|
||||
message(STATUS "[${TARGET_NAME}] Created ${LIB_TYPE} library '${TARGET_NAME}' with ${SOURCE_COUNT} source files.")
|
||||
foreach (item IN LISTS ALL_SOURCES)
|
||||
message(STATUS "[${TARGET_NAME}] SOURCE: ${item}")
|
||||
endforeach ()
|
||||
else()
|
||||
message(FATAL_ERROR "No valid source files found for target: ${TARGET_NAME}")
|
||||
endif()
|
||||
|
||||
list(LENGTH UNIT_TEST_SOURCES UNIT_TEST_COUNT)
|
||||
if(UNIT_TEST_COUNT GREATER 0)
|
||||
foreach (item IN LISTS UNIT_TEST_SOURCES)
|
||||
get_filename_component(TestUnit ${item} NAME_WLE)
|
||||
message(STATUS "[${TARGET_NAME}] TEST: ${TestUnit} - ${item}")
|
||||
add_executable(${TestUnit} ${item})
|
||||
target_link_libraries(${TestUnit} PUBLIC ${TARGET_NAME})
|
||||
endforeach ()
|
||||
endif ()
|
||||
endmacro()
|
||||
@@ -0,0 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/CMake)
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
project(${FOLDER_NAME})
|
||||
enable_language(C)
|
||||
enable_language(ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
include(create_library_from_dirs)
|
||||
create_library_from_dirs(${PROJECT_NAME} STATIC
|
||||
"Library"
|
||||
"")
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC cKit)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
NLP DevKit
|
||||
Reference in New Issue
Block a user