c_NlpCharGraph: 分析一篇文章,建立字与字的关系
c_NlpNgramGraph: 基于 n 个已经出现的词,预估后续可能出现的词 c_NlpLanguageModel: 如果有的词组合形式没有出现过,找到之前学习过最长类似的词句进行预测
This commit is contained in:
@@ -5,6 +5,14 @@
|
||||
#include <c_Base.h>
|
||||
#endif /*INCLUDED_C_BASE_H*/
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
#ifndef C_UNICODE_MAX
|
||||
#define C_UNICODE_MAX 0x10FFFFU
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
@@ -15,6 +23,14 @@ typedef uint16_t c_uint16_t;
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
bool c_is_valid_unicode(uint32_t code) {
|
||||
// Unicode 码点不能超过 0x10FFFF,且必须排除 UTF-16 代理对范围 (0xD800 ~ 0xDFFF)
|
||||
if (code > C_UNICODE_MAX) return false;
|
||||
if (code >= 0xD800 && code <= 0xDFFF) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取一个 UTF-8 字符在当前指针位置所占用的实际字节数 (1 ~ 4 字节)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,704 @@
|
||||
#include <c_NlpCharGraph.h>
|
||||
#include <c_Memory.h>
|
||||
#include <stdio.h>
|
||||
#include "c_StringBuffer.h"
|
||||
#include "c_utf8_file.h"
|
||||
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_size_t c_NlpGraph_Hash(c_ucs4_t cp, c_size_t capacity) {
|
||||
return (c_size_t)(cp % capacity);
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_err_t c_NlpOffsetList_Init(c_NlpOffsetList_t* list) {
|
||||
list->count = 0;
|
||||
list->capacity = 4;
|
||||
list->offsets = (c_size_t*)C_ALLOC(sizeof(c_size_t) * list->capacity);
|
||||
return list->offsets ? C_ERR_OK : C_ERR_NOMEM;
|
||||
}
|
||||
|
||||
static c_err_t c_NlpOffsetList_Add(c_NlpOffsetList_t* list, c_size_t offset) {
|
||||
if (list->count >= list->capacity) {
|
||||
c_size_t new_cap = list->capacity * 2;
|
||||
c_size_t* new_offsets = (c_size_t*)C_ALLOC(sizeof(c_size_t) * new_cap);
|
||||
if (!new_offsets) return C_ERR_NOMEM;
|
||||
memcpy(new_offsets, list->offsets, sizeof(c_size_t) * list->count);
|
||||
C_FREE(list->offsets);
|
||||
list->offsets = new_offsets;
|
||||
list->capacity = new_cap;
|
||||
}
|
||||
list->offsets[list->count++] = offset;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* --- Dynamic Node Instantiation Engine --- */
|
||||
static c_NlpCharNode_t* c_NlpCharNode_Create(c_ucs4_t cp, c_size_t relation_capacity) {
|
||||
if (relation_capacity == 0) return NULL;
|
||||
|
||||
c_NlpCharNode_t* node = (c_NlpCharNode_t*)C_ALLOC(sizeof(c_NlpCharNode_t));
|
||||
if (!node) return NULL;
|
||||
|
||||
node->codepoint = cp;
|
||||
node->frequency = 0;
|
||||
node->relation_capacity = relation_capacity;
|
||||
|
||||
if (c_NlpOffsetList_Init(&node->offset_list) != C_ERR_OK) {
|
||||
C_FREE(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate variable-width hash array tables on the heap
|
||||
node->next_chars = (c_NlpCharEdge_t**)C_ALLOC(sizeof(c_NlpCharEdge_t*) * relation_capacity);
|
||||
node->prev_chars = (c_NlpCharEdge_t**)C_ALLOC(sizeof(c_NlpCharEdge_t*) * relation_capacity);
|
||||
|
||||
if (!node->next_chars || !node->prev_chars) {
|
||||
C_FREE(node->next_chars);
|
||||
C_FREE(node->prev_chars);
|
||||
C_FREE(node->offset_list.offsets);
|
||||
C_FREE(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Zero out pointers across all newly configured custom slots
|
||||
for (c_size_t i = 0; i < relation_capacity; i++) {
|
||||
node->next_chars[i] = NULL;
|
||||
node->prev_chars[i] = NULL;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static c_NlpCharNode_t* c_NlpCharGraph_GetOrCreateNode(c_NlpCharGraph_t* self, c_ucs4_t cp) {
|
||||
c_size_t idx = c_NlpGraph_Hash(cp, self->bucket_count);
|
||||
c_NlpCharNode_t* curr = self->buckets[idx];
|
||||
c_NlpCharNode_t* prev = NULL;
|
||||
|
||||
// 沿拉链向下查找匹配的码点
|
||||
while (curr != NULL) {
|
||||
if (curr->codepoint == cp) {
|
||||
return curr; // 完美命中已有节点
|
||||
}
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
// 未命中冲突,创建全新节点并通过头插法/尾插法挂入拉链
|
||||
c_NlpCharNode_t* new_node = c_NlpCharNode_Create(cp, self->relation_default_capacity);
|
||||
if (!new_node) return NULL;
|
||||
new_node->next = NULL;
|
||||
|
||||
if (prev == NULL) {
|
||||
self->buckets[idx] = new_node; // 桶内第一个节点
|
||||
} else {
|
||||
prev->next = new_node; // 挂在冲突链条末尾
|
||||
}
|
||||
|
||||
self->total_unique++;
|
||||
return new_node;
|
||||
}
|
||||
|
||||
static c_err_t c_NlpCharNode_AddRelation(c_NlpCharEdge_t** edge_buckets, c_size_t capacity, c_ucs4_t target_cp) {
|
||||
c_size_t idx = c_NlpGraph_Hash(target_cp, capacity);
|
||||
c_NlpCharEdge_t* curr = edge_buckets[idx];
|
||||
|
||||
while (curr != NULL) {
|
||||
if (curr->target_cp == target_cp) {
|
||||
curr->co_count++;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
c_NlpCharEdge_t* new_edge = (c_NlpCharEdge_t*)C_ALLOC(sizeof(c_NlpCharEdge_t));
|
||||
if (!new_edge) return C_ERR_NOMEM;
|
||||
|
||||
new_edge->target_cp = target_cp;
|
||||
new_edge->co_count = 1;
|
||||
new_edge->next = edge_buckets[idx];
|
||||
edge_buckets[idx] = new_edge;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpCharGraph_Init(c_NlpCharGraph_t* self, c_size_t bucket_count, c_size_t relation_default_capacity) {
|
||||
if (!self || bucket_count == 0 || relation_default_capacity == 0) return C_ERR_PARAM;
|
||||
|
||||
self->bucket_count = bucket_count;
|
||||
self->relation_default_capacity = relation_default_capacity;
|
||||
self->total_unique = 0;
|
||||
self->total_words = 0;
|
||||
|
||||
self->buckets = (c_NlpCharNode_t**)C_ALLOC(sizeof(c_NlpCharNode_t*) * bucket_count);
|
||||
if (!self->buckets) return C_ERR_NOMEM;
|
||||
|
||||
for (c_size_t i = 0; i < bucket_count; i++) {
|
||||
self->buckets[i] = NULL;
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_NlpCharNode_t* c_NlpCharGraph_GetNode(c_NlpCharGraph_t* self, c_ucs4_t cp) {
|
||||
if (!self || !self->buckets) return NULL;
|
||||
c_size_t idx = c_NlpGraph_Hash(cp, self->bucket_count);
|
||||
c_NlpCharNode_t* curr = self->buckets[idx];
|
||||
while (curr != NULL) {
|
||||
if (curr->codepoint == cp) return curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c_err_t c_NlpCharGraph_Process(c_NlpCharGraph_t* self, const c_ucs4_t* unicode_array, c_size_t length) {
|
||||
if (!self || !self->buckets || !unicode_array || length == 0) return C_ERR_PARAM;
|
||||
|
||||
self->total_words += length;
|
||||
|
||||
for (c_size_t i = 0; i < length; i++) {
|
||||
c_ucs4_t curr_cp = unicode_array[i];
|
||||
|
||||
// 1. 通过拉链机制获取/创建当前节点
|
||||
c_NlpCharNode_t* curr_node = c_NlpCharGraph_GetOrCreateNode(self, curr_cp);
|
||||
if (!curr_node) return C_ERR_NOMEM;
|
||||
|
||||
curr_node->frequency++;
|
||||
c_err_t err = c_NlpOffsetList_Add(&curr_node->offset_list, i);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
// 2. 建立前序边,同时保证前序字在拉链表中不丢失
|
||||
if (i > 0) {
|
||||
c_ucs4_t prev_cp = unicode_array[i - 1];
|
||||
c_NlpCharNode_t* p_node = c_NlpCharGraph_GetOrCreateNode(self, prev_cp);
|
||||
if (!p_node) return C_ERR_NOMEM;
|
||||
|
||||
err = c_NlpCharNode_AddRelation(curr_node->prev_chars, curr_node->relation_capacity, prev_cp);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
|
||||
// 3. 建立后续边(如“大”关联“模”),同时通过拉链确保“模”字被注册激活
|
||||
if (i < length - 1) {
|
||||
c_ucs4_t next_cp = unicode_array[i + 1];
|
||||
c_NlpCharNode_t* n_node = c_NlpCharGraph_GetOrCreateNode(self, next_cp);
|
||||
if (!n_node) return C_ERR_NOMEM;
|
||||
|
||||
err = c_NlpCharNode_AddRelation(curr_node->next_chars, curr_node->relation_capacity, next_cp);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
static void c_NlpCharEdge_FreeBuckets(c_NlpCharEdge_t** edge_buckets, c_size_t capacity) {
|
||||
if (!edge_buckets) return;
|
||||
for (c_size_t i = 0; i < capacity; i++) {
|
||||
c_NlpCharEdge_t* curr = edge_buckets[i];
|
||||
while (curr != NULL) {
|
||||
c_NlpCharEdge_t* temp = curr->next;
|
||||
C_FREE(curr);
|
||||
curr = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void c_NlpCharGraph_Destroy(c_NlpCharGraph_t* self) {
|
||||
if (!self || !self->buckets || self->bucket_count==0) return;
|
||||
|
||||
for (c_size_t i = 0; i < self->bucket_count; i++) {
|
||||
c_NlpCharNode_t* node = self->buckets[i];
|
||||
|
||||
// 沿拉链释放所有冲突节点的深层堆空间
|
||||
while (node != NULL) {
|
||||
c_NlpCharNode_t* next_node = node->next; // 暂存下一个指针
|
||||
|
||||
C_FREE(node->offset_list.offsets);
|
||||
c_NlpCharEdge_FreeBuckets(node->next_chars, node->relation_capacity);
|
||||
c_NlpCharEdge_FreeBuckets(node->prev_chars, node->relation_capacity);
|
||||
|
||||
C_FREE(node->next_chars);
|
||||
C_FREE(node->prev_chars);
|
||||
C_FREE(node);
|
||||
|
||||
node = next_node;
|
||||
}
|
||||
self->buckets[i] = NULL;
|
||||
}
|
||||
C_FREE(self->buckets);
|
||||
self->bucket_count = 0;
|
||||
self->total_unique = 0;
|
||||
self->total_words = 0;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
c_err_t c_NlpCharGraph_AnalyzeDistanceWindow(c_NlpCharGraph_t* self, c_ucs4_t cp1, c_ucs4_t cp2, c_size_t window_size,
|
||||
c_size_t* out_min_dist, double* out_avg_dist, c_size_t* out_co_occurrences,
|
||||
c_size_t* out_cp1_precedes_count, c_size_t* out_cp2_precedes_count) {
|
||||
// 1. Defend against parameter null configurations or invalid window constraints
|
||||
if (!self || !out_min_dist || !out_avg_dist || !out_co_occurrences ||
|
||||
!out_cp1_precedes_count || !out_cp2_precedes_count || window_size == 0) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// Initialize all metrics to zero out out-parameters safely
|
||||
*out_min_dist = (c_size_t)-1;
|
||||
*out_avg_dist = 0.0;
|
||||
*out_co_occurrences = 0;
|
||||
*out_cp1_precedes_count = 0;
|
||||
*out_cp2_precedes_count = 0;
|
||||
|
||||
// 2. Fetch the structural node contexts from the master hash bucket allocations
|
||||
c_NlpCharNode_t* n1 = c_NlpCharGraph_GetNode(self, cp1);
|
||||
c_NlpCharNode_t* n2 = c_NlpCharGraph_GetNode(self, cp2);
|
||||
if (!n1 || !n2 || n1->offset_list.count == 0 || n2->offset_list.count == 0) {
|
||||
return C_ERR_OK; // Return cleanly with zeroed structures if any token is absent
|
||||
}
|
||||
|
||||
c_size_t* o1 = n1->offset_list.offsets;
|
||||
c_size_t* o2 = n2->offset_list.offsets;
|
||||
c_size_t len1 = n1->offset_list.count;
|
||||
c_size_t len2 = n2->offset_list.count;
|
||||
|
||||
c_size_t min_dist = (c_size_t)-1;
|
||||
double total_dist_sum = 0.0;
|
||||
c_size_t window_crossings = 0;
|
||||
c_size_t cp1_precedes = 0;
|
||||
c_size_t cp2_precedes = 0;
|
||||
|
||||
// 3. Deploy the Bounded Look-Ahead Sliding Scanner
|
||||
// Loop through every single occurrence of cp1
|
||||
for (c_size_t i = 0; i < len1; i++) {
|
||||
c_size_t pos1 = o1[i];
|
||||
|
||||
// Find the first index in o2 where the element might be in range: pos2 >= pos1 - window_size
|
||||
// We initialize look_idx using a basic optimization strategy, or start from 0
|
||||
c_size_t look_idx = 0;
|
||||
|
||||
while (look_idx < len2) {
|
||||
c_size_t pos2 = o2[look_idx];
|
||||
|
||||
// Condition A: pos2 is too far behind pos1, keep advancing the scan
|
||||
if (pos2 < pos1 && (pos1 - pos2) > window_size) {
|
||||
look_idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Condition B: pos2 is too far ahead of pos1, we can stop evaluating for this pos1
|
||||
if (pos2 > pos1 && (pos2 - pos1) > window_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Condition C: pos2 falls within the safe sliding context window boundary limit!
|
||||
c_size_t dist = (pos1 > pos2) ? (pos1 - pos2) : (pos2 - pos1);
|
||||
|
||||
if (dist < min_dist) {
|
||||
min_dist = dist;
|
||||
}
|
||||
|
||||
total_dist_sum += (double)dist;
|
||||
window_crossings++;
|
||||
|
||||
if (pos1 < pos2) {
|
||||
cp1_precedes++; // cp1 appears BEFORE cp2
|
||||
} else if (pos1 > pos2) {
|
||||
cp2_precedes++; // cp2 appears BEFORE cp1
|
||||
} else {
|
||||
// Identity overlaps count as neutral crossings (rare for unique terms)
|
||||
}
|
||||
|
||||
look_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Populate analytics result payloads accurately
|
||||
if (window_crossings > 0) {
|
||||
*out_min_dist = min_dist;
|
||||
*out_avg_dist = total_dist_sum / (double)window_crossings;
|
||||
*out_co_occurrences = window_crossings;
|
||||
*out_cp1_precedes_count = cp1_precedes;
|
||||
*out_cp2_precedes_count = cp2_precedes;
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpCharGraph_ExportJSON(c_NlpCharGraph_t* self, const char* filepath) {
|
||||
if (!self || !self->buckets || !filepath) return C_ERR_PARAM;
|
||||
|
||||
c_StringBuffer_t sb;
|
||||
c_err_t err = c_StringBuffer_Init(&sb, 4096);
|
||||
if (err != C_ERR_OK) return err;
|
||||
|
||||
err = c_StringBuffer_AppendStr(&sb, "{\n");
|
||||
err |= c_StringBuffer_AppendStr(&sb, " \"meta\": {\n");
|
||||
|
||||
char meta_buf[128];
|
||||
snprintf(meta_buf, sizeof(meta_buf), " \"total_words\": %lu,\n \"total_unique\": %lu\n",
|
||||
(unsigned long)self->total_words, (unsigned long)self->total_unique);
|
||||
err |= c_StringBuffer_AppendStr(&sb, meta_buf);
|
||||
err |= c_StringBuffer_AppendStr(&sb, " },\n");
|
||||
err |= c_StringBuffer_AppendStr(&sb, " \"nodes\": [\n");
|
||||
if (err != C_ERR_OK) { c_StringBuffer_Destroy(&sb); return C_ERR_FAIL; }
|
||||
|
||||
c_size_t nodes_serialized = 0;
|
||||
|
||||
// 外层遍历所有桶
|
||||
for (c_size_t i = 0; i < self->bucket_count; i++) {
|
||||
c_NlpCharNode_t* node = self->buckets[i];
|
||||
|
||||
// 核心修正:内层沿着冲突拉链链表深度追踪,把每一个被掩埋的字(如“模”)都全部挖出来导出
|
||||
while (node != NULL) {
|
||||
char utf8_word[8];
|
||||
c_size_t written_len = 0;
|
||||
if (c_utf8_from_unicode(node->codepoint, utf8_word, &written_len) != C_ERR_OK) {
|
||||
node = node->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodes_serialized > 0) {
|
||||
err |= c_StringBuffer_AppendStr(&sb, ",\n");
|
||||
}
|
||||
|
||||
err |= c_StringBuffer_AppendStr(&sb, " {\n");
|
||||
|
||||
char node_info[256];
|
||||
if (utf8_word[0] == '"' || utf8_word[0] == '\\') {
|
||||
snprintf(node_info, sizeof(node_info),
|
||||
" \"char\": \"\\%s\",\n \"codepoint\": %u,\n \"frequency\": %lu,\n",
|
||||
utf8_word, node->codepoint, (unsigned long)node->frequency);
|
||||
} else {
|
||||
snprintf(node_info, sizeof(node_info),
|
||||
" \"char\": \"%s\",\n \"codepoint\": %u,\n \"frequency\": %lu,\n",
|
||||
utf8_word, node->codepoint, (unsigned long)node->frequency);
|
||||
}
|
||||
err |= c_StringBuffer_AppendStr(&sb, node_info);
|
||||
|
||||
// 序列化 offsets
|
||||
err |= c_StringBuffer_AppendStr(&sb, " \"offsets\": [");
|
||||
for (c_size_t k = 0; k < node->offset_list.count; k++) {
|
||||
char val_buf[32];
|
||||
snprintf(val_buf, sizeof(val_buf), "%lu%s",
|
||||
(unsigned long)node->offset_list.offsets[k],
|
||||
(k + 1 < node->offset_list.count) ? ", " : "");
|
||||
err |= c_StringBuffer_AppendStr(&sb, val_buf);
|
||||
}
|
||||
err |= c_StringBuffer_AppendStr(&sb, "],\n");
|
||||
|
||||
// 序列化 next_chars
|
||||
err |= c_StringBuffer_AppendStr(&sb, " \"next_chars\": {");
|
||||
c_bool_t edge_written = C_FALSE;
|
||||
|
||||
for (c_size_t bucket_idx = 0; bucket_idx < node->relation_capacity; bucket_idx++) {
|
||||
c_NlpCharEdge_t* edge = node->next_chars[bucket_idx];
|
||||
while (edge != NULL) {
|
||||
char edge_utf8[8];
|
||||
c_size_t e_len = 0;
|
||||
if (c_utf8_from_unicode(edge->target_cp, edge_utf8, &e_len) == C_ERR_OK) {
|
||||
char edge_buf[128];
|
||||
const char* comma_prefix = edge_written ? ", " : "";
|
||||
|
||||
if (edge_utf8[0] == '"' || edge_utf8[0] == '\\') {
|
||||
snprintf(edge_buf, sizeof(edge_buf), "%s\"\\%s\": %lu",
|
||||
comma_prefix, edge_utf8, (unsigned long)edge->co_count);
|
||||
} else {
|
||||
snprintf(edge_buf, sizeof(edge_buf), "%s\"%s\": %lu",
|
||||
comma_prefix, edge_utf8, (unsigned long)edge->co_count);
|
||||
}
|
||||
err |= c_StringBuffer_AppendStr(&sb, edge_buf);
|
||||
edge_written = C_TRUE;
|
||||
}
|
||||
edge = edge->next;
|
||||
}
|
||||
}
|
||||
err |= c_StringBuffer_AppendStr(&sb, "}\n");
|
||||
err |= c_StringBuffer_AppendStr(&sb, " }");
|
||||
|
||||
nodes_serialized++;
|
||||
|
||||
// 顺着冲突拉链移动到下一个发生哈希冲突的字节点
|
||||
node = node->next;
|
||||
}
|
||||
if (err != C_ERR_OK) { c_StringBuffer_Destroy(&sb); return C_ERR_FAIL; }
|
||||
}
|
||||
|
||||
err |= c_StringBuffer_AppendStr(&sb, "\n ]\n}\n");
|
||||
if (err != C_ERR_OK) { c_StringBuffer_Destroy(&sb); return C_ERR_FAIL; }
|
||||
|
||||
FILE* file = fopen(filepath, "wb");
|
||||
if (!file) { c_StringBuffer_Destroy(&sb); return C_ERR_FAIL; }
|
||||
|
||||
c_size_t expected_size = sb.size;
|
||||
size_t written = fwrite(sb.buffer, 1, expected_size, file);
|
||||
fclose(file);
|
||||
c_StringBuffer_Destroy(&sb);
|
||||
|
||||
if (written != expected_size) return C_ERR_FAIL;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/**
|
||||
* @brief Helper utility to safely extract value payloads trapped between specific JSON keys and brackets.
|
||||
* Example: transforms ' "char": "大", ' into '大'
|
||||
*/
|
||||
static void c_Nlp_ExtractJsonValue(const char* line, const char* key, char* dest_val, size_t dest_max) {
|
||||
dest_val[0] = '\0';
|
||||
char* key_pos = strstr(line, key);
|
||||
if (!key_pos) return;
|
||||
|
||||
char* start = strchr(key_pos + strlen(key), ':');
|
||||
if (!start) return;
|
||||
start++; // Step past ':'
|
||||
|
||||
// Skip white spaces and opening quotes
|
||||
while (*start == ' ' || *start == '\t' || *start == '"') {
|
||||
start++;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
while (*start != '\0' && *start != '"' && *start != ',' && *start != '\n' && *start != '\r' && *start != ']' && idx < dest_max - 1) {
|
||||
// Handle escaped JSON string properties
|
||||
if (*start == '\\' && *(start + 1) != '\0') {
|
||||
start++;
|
||||
}
|
||||
dest_val[idx++] = *start++;
|
||||
}
|
||||
dest_val[idx] = '\0';
|
||||
|
||||
// Trim trailing tracking spaces if any present
|
||||
while (idx > 0 && (dest_val[idx - 1] == ' ' || dest_val[idx - 1] == '\t' || dest_val[idx - 1] == '}')) {
|
||||
dest_val[--idx] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
c_err_t c_NlpCharGraph_ImportJSON(c_NlpCharGraph_t* self, const char* filepath, c_size_t bucket_count) {
|
||||
if (!self || !filepath || bucket_count == 0) return C_ERR_PARAM;
|
||||
|
||||
FILE* file = fopen(filepath, "rb");
|
||||
if (!file) return C_ERR_FAIL;
|
||||
|
||||
// Detect and safely strip standard UTF-8 BOM if present
|
||||
unsigned char check_bom[3];
|
||||
if (fread(check_bom, 1, 3, file) == 3 && check_bom[0] == 0xEF && check_bom[1] == 0xBB && check_bom[2] == 0xBF) {
|
||||
// BOM skipped successfully
|
||||
} else {
|
||||
fseek(file, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
c_StringBuffer_t line_sb;
|
||||
c_err_t err = c_StringBuffer_Init(&line_sb, 512);
|
||||
if (err != C_ERR_OK) {
|
||||
fclose(file);
|
||||
return err;
|
||||
}
|
||||
|
||||
c_size_t total_words = 0;
|
||||
c_size_t relation_cap = 16; // Default initialization relation table width
|
||||
|
||||
// Fixed Buffer Upgrade: Secure 4096-byte stack arrays to completely negate truncation risks
|
||||
char val_buf[4096];
|
||||
|
||||
// Phase 1: Parse Global Metadata Attributes
|
||||
while (c_utf8_file_readline(file, &line_sb) == C_ERR_OK) {
|
||||
if (strstr(line_sb.buffer, "\"total_words\"") != NULL) {
|
||||
c_Nlp_ExtractJsonValue(line_sb.buffer, "\"total_words\"", val_buf, sizeof(val_buf));
|
||||
total_words = (c_size_t)strtoul(val_buf, NULL, 10);
|
||||
}
|
||||
if (strstr(line_sb.buffer, "\"nodes\"") != NULL) {
|
||||
break; // Metadata block parsed, moving onto array entries
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Structural Graph Memory Instantiation
|
||||
err = c_NlpCharGraph_Init(self, bucket_count, relation_cap);
|
||||
if (err != C_ERR_OK) {
|
||||
c_StringBuffer_Destroy(&line_sb);
|
||||
fclose(file);
|
||||
return err;
|
||||
}
|
||||
self->total_words = total_words;
|
||||
|
||||
c_NlpCharNode_t* active_node = NULL;
|
||||
|
||||
// Phase 3: Sequential Structural Node Deserialization
|
||||
while (c_utf8_file_readline(file, &line_sb) == C_ERR_OK) {
|
||||
// Ignore standalone array boundaries markers
|
||||
if (strstr(line_sb.buffer, "{") != NULL && strstr(line_sb.buffer, "\"char\"") == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse individual token parameters
|
||||
if (strstr(line_sb.buffer, "\"codepoint\"") != NULL) {
|
||||
c_Nlp_ExtractJsonValue(line_sb.buffer, "\"codepoint\"", val_buf, sizeof(val_buf));
|
||||
c_ucs4_t cp = (c_ucs4_t)strtoul(val_buf, NULL, 10);
|
||||
|
||||
// Reconstruct utilizing standard chaining allocator to secure the linked pointers list
|
||||
active_node = c_NlpCharGraph_GetOrCreateNode(self, cp);
|
||||
if (!active_node) {
|
||||
c_NlpCharGraph_Destroy(self);
|
||||
c_StringBuffer_Destroy(&line_sb);
|
||||
fclose(file);
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
else if (strstr(line_sb.buffer, "\"frequency\"") != NULL && active_node) {
|
||||
c_Nlp_ExtractJsonValue(line_sb.buffer, "\"frequency\"", val_buf, sizeof(val_buf));
|
||||
active_node->frequency = (c_size_t)strtoul(val_buf, NULL, 10);
|
||||
}
|
||||
else if (strstr(line_sb.buffer, "\"offsets\"") != NULL && active_node) {
|
||||
c_Nlp_ExtractJsonValue(line_sb.buffer, "\"offsets\"", val_buf, sizeof(val_buf));
|
||||
|
||||
// Safe continuous array chunk tokenization using reentrant strtok mechanics
|
||||
char* save_ptr = NULL;
|
||||
char* token = c_utf8_strtok(val_buf, ", ", &save_ptr);
|
||||
while (token != NULL) {
|
||||
if (*token != '\0') {
|
||||
c_size_t offset_val = (c_size_t)strtoul(token, NULL, 10);
|
||||
c_NlpOffsetList_Add(&active_node->offset_list, offset_val);
|
||||
}
|
||||
token = c_utf8_strtok(NULL, ", ", &save_ptr);
|
||||
}
|
||||
}
|
||||
else if (strstr(line_sb.buffer, "\"next_chars\"") != NULL && active_node) {
|
||||
c_Nlp_ExtractJsonValue(line_sb.buffer, "\"next_chars\"", val_buf, sizeof(val_buf));
|
||||
|
||||
// Process directional map entries: e.g., '"I": 1, "型": 1'
|
||||
char* save_ptr = NULL;
|
||||
char* pair_token = c_utf8_strtok(val_buf, ", ", &save_ptr);
|
||||
while (pair_token != NULL) {
|
||||
char* colon = strchr(pair_token, ':');
|
||||
if (colon) {
|
||||
*colon = '\0';
|
||||
char edge_char[4096]; // Secure localized tracking array width
|
||||
strncpy(edge_char, pair_token, sizeof(edge_char) - 1);
|
||||
edge_char[sizeof(edge_char) - 1] = '\0';
|
||||
|
||||
// Strip enclosing quote strings out of target boundaries
|
||||
char* edge_clean = edge_char;
|
||||
while (*edge_clean == ' ' || *edge_clean == '\t' || *edge_clean == '"') {
|
||||
edge_clean++;
|
||||
}
|
||||
size_t elen = strlen(edge_clean);
|
||||
while (elen > 0 && (edge_clean[elen - 1] == '"' || edge_clean[elen - 1] == ' ' || edge_clean[elen - 1] == '\t')) {
|
||||
edge_clean[--elen] = '\0';
|
||||
}
|
||||
|
||||
if (elen > 0) {
|
||||
c_ucs4_t target_cp = 0;
|
||||
c_size_t bytes_step = 0;
|
||||
// Transform character text string representation straight back into standard 32-bit integer points
|
||||
if (c_utf8_to_unicode(edge_clean, &target_cp, &bytes_step) == C_ERR_OK) {
|
||||
c_size_t edge_freq = (c_size_t)strtoul(colon + 1, NULL, 10);
|
||||
|
||||
// Rehydrate co-occurrence link matrices
|
||||
for (c_size_t f = 0; f < edge_freq; f++) {
|
||||
c_NlpCharNode_AddRelation(active_node->next_chars, active_node->relation_capacity, target_cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pair_token = c_utf8_strtok(NULL, ", ", &save_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c_StringBuffer_Destroy(&line_sb);
|
||||
fclose(file);
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpCharGraph_PredictNext(c_NlpCharGraph_t* self, c_ucs4_t current_char, c_NlpPredictMode_t mode,
|
||||
c_ucs4_t* out_next_char, double* out_confidence) {
|
||||
if (!self || !out_next_char || !out_confidence) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 1. 在主哈希拉链表中定位当前字的节点
|
||||
c_NlpCharNode_t* node = c_NlpCharGraph_GetNode(self, current_char);
|
||||
if (!node || node->frequency == 0) {
|
||||
return C_ERR_FAIL; // 图中不存在这个字,或者该字从未作为主词出现过
|
||||
}
|
||||
|
||||
// 2. 遍历该节点的所有 next_chars 边,统计后续字的总权重和最大频次
|
||||
c_size_t total_co_count = 0;
|
||||
c_size_t max_co_count = 0;
|
||||
c_ucs4_t best_target_cp = 0;
|
||||
|
||||
for (c_size_t i = 0; i < node->relation_capacity; i++) {
|
||||
c_NlpCharEdge_t* edge = node->next_chars[i];
|
||||
while (edge != NULL) {
|
||||
total_co_count += edge->co_count;
|
||||
|
||||
// 贪婪模式所需的条件:记录最大频次
|
||||
if (edge->co_count > max_co_count) {
|
||||
max_co_count = edge->co_count;
|
||||
best_target_cp = edge->target_cp;
|
||||
}
|
||||
edge = edge->next;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果该字没有任何后续关联(比如它是文章的最后一个字且仅出现了一次)
|
||||
if (total_co_count == 0) {
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
// 3. 根据预测模式确定输出
|
||||
if (mode == C_PREDICT_MODE_GREEDY) {
|
||||
// 【贪婪模式】:直接选择后序关联最强、出现次数最多的字
|
||||
*out_next_char = best_target_cp;
|
||||
*out_confidence = (double)max_co_count / (double)total_co_count;
|
||||
}
|
||||
else if (mode == C_PREDICT_MODE_SAMPLING) {
|
||||
// 【随机抽样模式】:基于一阶马尔可夫链的条件概率分布进行轮盘赌抽样
|
||||
// 生成一个 0.0 到 1.0 之间的随机数
|
||||
double random_pivot = (double)rand() / (double)RAND_MAX;
|
||||
double current_accumulator = 0.0;
|
||||
c_bool_t sampled = C_FALSE;
|
||||
|
||||
for (c_size_t i = 0; i < node->relation_capacity; i++) {
|
||||
c_NlpCharEdge_t* edge = node->next_chars[i];
|
||||
while (edge != NULL) {
|
||||
double probability = (double)edge->co_count / (double)total_co_count;
|
||||
current_accumulator += probability;
|
||||
|
||||
// 落在当前随机区间内,命中该字符
|
||||
if (random_pivot <= current_accumulator) {
|
||||
*out_next_char = edge->target_cp;
|
||||
*out_confidence = probability;
|
||||
sampled = C_TRUE;
|
||||
break;
|
||||
}
|
||||
edge = edge->next;
|
||||
}
|
||||
if (sampled) break;
|
||||
}
|
||||
|
||||
// 兜底保护,防止浮点数精度截断导致未命中
|
||||
if (!sampled) {
|
||||
*out_next_char = best_target_cp;
|
||||
*out_confidence = (double)max_co_count / (double)total_co_count;
|
||||
}
|
||||
} else {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
#ifndef INCLUDED_C_NLPCHARGRAPH_H
|
||||
#define INCLUDED_C_NLPCHARGRAPH_H
|
||||
|
||||
|
||||
#ifndef INCLUDED_C_BASE_H
|
||||
#include <c_Base.h>
|
||||
#endif /*INCLUDED_C_BASE_H*/
|
||||
|
||||
#ifndef INCLUDED_C_UTF8_H
|
||||
#include <c_utf8.h>
|
||||
#endif /*INCLUDED_C_UTF8_H*/
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
typedef enum {
|
||||
C_PREDICT_MODE_GREEDY = 0, /* 贪婪模式:总是选择频次最高的最优解 */
|
||||
C_PREDICT_MODE_SAMPLING = 1 /* 抽样模式:根据概率分布进行随机选择(更具多样性) */
|
||||
} c_NlpPredictMode_t;
|
||||
|
||||
/* --- 1. 动态位置数组(记录某个字在文章中出现的所有索引位置) --- */
|
||||
typedef struct {
|
||||
c_size_t* offsets; // 存储绝对位置的数组(如:第0, 15, 234个字)
|
||||
c_size_t count; // 出现频次 (次数)
|
||||
c_size_t capacity; // 动态数组容量
|
||||
} c_NlpOffsetList_t;
|
||||
|
||||
/* --- 2. 邻接边结构体(记录当前字与前后字的关系及共现次数) --- */
|
||||
typedef struct c_NlpCharEdge_t {
|
||||
c_ucs4_t target_cp; // 关联字的 Unicode 码点
|
||||
c_size_t co_count; // 在邻接上下文中共同出现的次数
|
||||
struct c_NlpCharEdge_t* next; // 冲突链表指针(使用哈希拉链法或链表)
|
||||
} c_NlpCharEdge_t;
|
||||
|
||||
/* --- 3. 核心:Unicode 字符语义拓扑节点 --- */
|
||||
typedef struct c_NlpCharNode_t{
|
||||
c_ucs4_t codepoint;
|
||||
c_size_t frequency;
|
||||
c_NlpOffsetList_t offset_list;
|
||||
|
||||
// Refactored pointers managing arbitrarily sized hash bucket tables
|
||||
c_NlpCharEdge_t** next_chars; // Forward Bigram Hash Buckets array
|
||||
c_NlpCharEdge_t** prev_chars; // Backward Bigram Hash Buckets array
|
||||
c_size_t relation_capacity; // Current allocated size for next_chars and prev_chars
|
||||
|
||||
struct c_NlpCharNode_t* next;
|
||||
} c_NlpCharNode_t;
|
||||
|
||||
typedef struct {
|
||||
c_NlpCharNode_t** buckets; // 主哈希表:存储文章中所有出现过的唯一字节点
|
||||
c_size_t bucket_count; // 主哈希桶大小(如 1024 或 4096,取决于去重后的字数)
|
||||
c_size_t relation_default_capacity; // The default bucket capacity configured for sub-nodes
|
||||
c_size_t total_unique; // 文章中不重复的汉字总量
|
||||
c_size_t total_words; // 整篇文章的总字数(包括标点)
|
||||
} c_NlpCharGraph_t;
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpCharGraph_Init(c_NlpCharGraph_t* self, c_size_t bucket_count, c_size_t relation_default_capacity);
|
||||
c_NlpCharNode_t* c_NlpCharGraph_GetNode(c_NlpCharGraph_t* self, c_ucs4_t cp);
|
||||
c_err_t c_NlpCharGraph_Process(c_NlpCharGraph_t* self, const c_ucs4_t* unicode_array, c_size_t length);
|
||||
void c_NlpCharGraph_Destroy(c_NlpCharGraph_t* self);
|
||||
|
||||
/**
|
||||
* @brief Analyzes structural proximity metrics between two tokens within a sliding context window.
|
||||
* @param self Pointer to the active initialized graph context block.
|
||||
* @param cp1 The reference source Unicode character point.
|
||||
* @param cp2 The target matching Unicode character point to measure.
|
||||
* @param window_size Maximum token span distance threshold allowed (e.g., 5 tokens).
|
||||
* @param out_min_dist Destination tracking pointer for the absolute minimum token distance.
|
||||
* @param out_avg_dist Destination tracking pointer for the calculated average token distance.
|
||||
* @param out_co_occurrences Destination tracking pointer counting total window crossing events.
|
||||
* @param out_cp1_precedes_count Tracks how many times cp1 appeared BEFORE cp2 within the window.
|
||||
* @param out_cp2_precedes_count Tracks how many times cp2 appeared BEFORE cp1 within the window.
|
||||
* @return c_err_t C_ERR_OK on successful analytical completion, or C_ERR_PARAM if arguments are invalid.
|
||||
*/
|
||||
c_err_t c_NlpCharGraph_AnalyzeDistanceWindow(c_NlpCharGraph_t* self, c_ucs4_t cp1, c_ucs4_t cp2, c_size_t window_size,
|
||||
c_size_t* out_min_dist, double* out_avg_dist, c_size_t* out_co_occurrences,
|
||||
c_size_t* out_cp1_precedes_count, c_size_t* out_cp2_precedes_count);
|
||||
|
||||
/**
|
||||
* @brief Exports the entire Unicode semantics graph structure to a file in valid JSON format.
|
||||
* Automatically translates 32-bit codepoints back to human-readable UTF-8 strings.
|
||||
* @param self Pointer to the active processed graph context block.
|
||||
* @param filepath Destination path on disk where the JSON file will be saved.
|
||||
* @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid parameters, or C_ERR_FAIL on file access errors.
|
||||
*/
|
||||
c_err_t c_NlpCharGraph_ExportJSON(c_NlpCharGraph_t* self, const char* filepath);
|
||||
|
||||
/**
|
||||
* @brief Parses an exported JSON text file and reconstructs the full Unicode graph structure in memory.
|
||||
* @param self Pointer to an uninitialized or empty c_NlpCharGraph_t instance.
|
||||
* @param filepath Path to the source JSON file on disk.
|
||||
* @param bucket_count Main hash bucket size to use for the new graph context.
|
||||
* @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL on structural parsing failures.
|
||||
*/
|
||||
c_err_t c_NlpCharGraph_ImportJSON(c_NlpCharGraph_t* self, const char* filepath, c_size_t bucket_count);
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/**
|
||||
* @brief 根据当前已出现的字,预测并确定后续最应该输出什么字
|
||||
* @param self 指向已完成语料分析的拓扑图实例指针
|
||||
* @param current_char 当前最新出现的字(Unicode 码点)
|
||||
* @param mode 选择的预测模式(贪婪 或 抽样)
|
||||
* @param out_next_char 接收预测出的下一个字的指针
|
||||
* @param out_confidence 接收该预测字的置信度(概率值,0.0 ~ 1.0)
|
||||
* @return c_err_t C_ERR_OK 表示成功预测,C_ERR_PARAM 表示参数错误,C_ERR_FAIL 表示该字是生僻词/无后续关联
|
||||
*/
|
||||
c_err_t c_NlpCharGraph_PredictNext(c_NlpCharGraph_t* self, c_ucs4_t current_char, c_NlpPredictMode_t mode,
|
||||
c_ucs4_t* out_next_char, double* out_confidence);
|
||||
|
||||
|
||||
#endif /*INCLUDED_C_NLPCHARGRAPH_H*/
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "c_NlpCharGraph.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "c_StringBuffer.h"
|
||||
#include "c_utf8_file.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)
|
||||
|
||||
static c_err_t test(void) {
|
||||
/* 29. Arbitrary-Width Character Relation Graph Testing */
|
||||
c_NlpCharGraph_t var_graph;
|
||||
|
||||
// Initialize graph with 512 main buckets and an arbitrary custom relation layout size of 128 channels
|
||||
c_err_t err = c_NlpCharGraph_Init(&var_graph, 512, 128);
|
||||
RUN_TEST(err == C_ERR_OK, "Variable-width graph initialization returns C_ERR_OK");
|
||||
|
||||
c_ucs4_t sample_text[] = { 0x540C, 0x6B65, 0x540C }; // '同', '步', '同'
|
||||
err = c_NlpCharGraph_Process(&var_graph, sample_text, 3);
|
||||
RUN_TEST(err == C_ERR_OK && var_graph.total_unique == 2, "Processed text array inside flexible capacity container");
|
||||
|
||||
c_NlpCharNode_t* node_tong = c_NlpCharGraph_GetNode(&var_graph, 0x540C); // '同'
|
||||
RUN_TEST(node_tong != NULL && node_tong->relation_capacity == 128, "Verified node dynamically inherited the arbitrary 128-bucket relationship size setting");
|
||||
RUN_TEST(node_tong->next_chars != NULL && node_tong->prev_chars != NULL, "relational heap allocation arrays successfully bound");
|
||||
c_NlpCharGraph_Destroy(&var_graph);
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* 30. Corrected Context Window Proximity Analytical Verification */
|
||||
c_NlpCharGraph_t win_graph;
|
||||
c_NlpCharGraph_Init(&win_graph, 1024, C_UNICODE_MAX);
|
||||
|
||||
// Corpus structural token layout: "A B B C A" -> Test multi-point matching
|
||||
// Let 0x0041 = 'A', 0x0042 = 'B'
|
||||
c_ucs4_t cluster_corpus[] = { 0x0041, 0x0042, 0x0042, 0x0043, 0x0041 };
|
||||
err = c_NlpCharGraph_Process(&win_graph, cluster_corpus, 5);
|
||||
RUN_TEST(err == C_ERR_OK, "Cluster corpus parsed inside window context analyzer container");
|
||||
|
||||
c_size_t min_d = 0;
|
||||
double avg_d = 0.0;
|
||||
c_size_t cross_count = 0;
|
||||
c_size_t count_precedes_1 = 0;
|
||||
c_size_t count_precedes_2 = 0;
|
||||
|
||||
// Analyze token proximity between 'A' (Index 0, 4) and 'B' (Index 1, 2) with a window threshold size of 3
|
||||
// Target matches:
|
||||
// For A at 0: B at 1 (dist 1, A precedes), B at 2 (dist 2, A precedes) -> 2 pairs
|
||||
// For A at 4: B at 1 (dist 3, B precedes), B at 2 (dist 2, B precedes) -> 2 pairs
|
||||
// Total co-occurrences expected = 4 crossings. Min distance = 1.
|
||||
// Total distance sum = 1 + 2 + 3 + 2 = 8. Avg distance = 8 / 4 = 2.0.
|
||||
err = c_NlpCharGraph_AnalyzeDistanceWindow(&win_graph, 0x0041, 0x0042, 3,
|
||||
&min_d, &avg_d, &cross_count,
|
||||
&count_precedes_1, &count_precedes_2);
|
||||
|
||||
RUN_TEST(err == C_ERR_OK, "Corrected window distance execution completed successfully");
|
||||
RUN_TEST(cross_count == 4, "Accurately extracted exactly 4 window crossing combinations across the cluster");
|
||||
RUN_TEST(min_d == 1, "Accurately isolated absolute minimum distance down to 1 token ('A' at 0 vs 'B' at 1)");
|
||||
RUN_TEST(avg_d == 2.0, "Accurately evaluated average mathematical mean distance at 2.0 tokens precisely");
|
||||
RUN_TEST(count_precedes_1 == 2, "Verified 'A' preceded 'B' exactly 2 times across window evaluation frames");
|
||||
RUN_TEST(count_precedes_2 == 2, "Verified 'B' preceded 'A' exactly 2 times across window evaluation frames");
|
||||
|
||||
c_NlpCharGraph_Destroy(&win_graph);
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* 31. Unicode Character Graph JSON Export Verification */
|
||||
c_NlpCharGraph_t json_graph;
|
||||
const char* output_json_path = "nlp_graph_export.json";
|
||||
|
||||
// Initialize graph context
|
||||
err = c_NlpCharGraph_Init(&json_graph, 16, 4);
|
||||
RUN_TEST(err == C_ERR_OK, "JSON Graph context initialized successfully");
|
||||
|
||||
// Input string: "AI大模型" -> Translated into explicit unicode codepoint stream arrays
|
||||
c_ucs4_t export_corpus[] = { 0x0041, 0x0049, 0x5927, 0x6A21, 0x578B };
|
||||
err = c_NlpCharGraph_Process(&json_graph, export_corpus, 5);
|
||||
RUN_TEST(err == C_ERR_OK, "Export corpus strings parsed smoothly inside graph matrices");
|
||||
|
||||
// Execute JSON Export API pipeline
|
||||
err = c_NlpCharGraph_ExportJSON(&json_graph, output_json_path);
|
||||
RUN_TEST(err == C_ERR_OK, "c_NlpCharGraph_ExportJSON successfully generated JSON file on disk");
|
||||
|
||||
// Read file back to verify it contains valid structural characters
|
||||
c_StringBuffer_t read_verify_sb;
|
||||
c_StringBuffer_Init(&read_verify_sb, 1024);
|
||||
err = c_utf8_file_read(output_json_path, &read_verify_sb);
|
||||
|
||||
RUN_TEST(err == C_ERR_OK, "Successfully read generated JSON output file back into workspace");
|
||||
RUN_TEST(strstr(read_verify_sb.buffer, "\"total_words\": 5") != NULL, "JSON metadata section successfully parsed");
|
||||
RUN_TEST(strstr(read_verify_sb.buffer, "\"char\": \"大\"") != NULL, "Unicode codepoint 0x5927 successfully serialized to UTF-8 character string '大'");
|
||||
RUN_TEST(strstr(read_verify_sb.buffer, "\"next_chars\":") != NULL, "Directional adjacency edge transition attributes successfully embedded");
|
||||
|
||||
// Clean up temporary files and memory structures
|
||||
c_StringBuffer_Destroy(&read_verify_sb);
|
||||
c_NlpCharGraph_Destroy(&json_graph);
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* 32. Unicode Character Graph JSON Import Validation */
|
||||
c_NlpCharGraph_t imported_graph;
|
||||
const char* target_json_src = "nlp_graph_export.json";
|
||||
|
||||
// Dependencies checkpoint: Ensure file exists before running verification passes
|
||||
// (Generated by calling c_NlpCharGraph_ExportJSON in the previous step)
|
||||
err = c_NlpCharGraph_ImportJSON(&imported_graph, target_json_src, 1024);
|
||||
RUN_TEST(err == C_ERR_OK, "c_NlpCharGraph_ImportJSON parsed JSON text stream and built the graph successfully");
|
||||
|
||||
// Validate Structural Reconstitution Fidelity Metrics
|
||||
RUN_TEST(imported_graph.total_words == 5, "Reconstructed metadata 'total_words' matches value: 5");
|
||||
|
||||
// Fetch and check nodes internally
|
||||
c_NlpCharNode_t* check_da = c_NlpCharGraph_GetNode(&imported_graph, 0x5927); // '大'
|
||||
RUN_TEST(check_da != NULL, "Unicode Node 0x5927 ('大') successfully verified in bucket storage");
|
||||
if (check_da) {
|
||||
RUN_TEST(check_da->frequency == 1, "Frequency counter holds correct historical state value: 1");
|
||||
RUN_TEST(check_da->offset_list.count == 1, "Offsets dynamic list successfully loaded elements");
|
||||
|
||||
// Trace and check directional bigram linkages
|
||||
c_size_t min_d = 0;
|
||||
double avg_d = 0.0;
|
||||
c_size_t crossings = 0;
|
||||
c_size_t p1 = 0, p2 = 0;
|
||||
err = c_NlpCharGraph_AnalyzeDistanceWindow(&imported_graph, 0x5927, 0x6A21, 2, &min_d, &avg_d, &crossings, &p1, &p2); // '大' vs '模'
|
||||
RUN_TEST(err == C_ERR_OK && crossings == 1, "Relational bigram matching layers successfully operational post-import");
|
||||
}
|
||||
|
||||
c_NlpCharGraph_Destroy(&imported_graph);
|
||||
|
||||
// remove(output_json_path); // Remove transient file from disk space
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* 31. 语言模型后续字预测验证 (c_NlpCharGraph_PredictNext) */
|
||||
c_NlpCharGraph_t pred_graph;
|
||||
c_NlpCharGraph_Init(&pred_graph, 1024, 16);
|
||||
|
||||
// 载入高重复、高确定性的测试语料:"大模型大模型大项目"
|
||||
// '大' 后面出现了 2 次 '模',1 次 '项'。总后续数 = 3
|
||||
c_ucs4_t train_data[] = {
|
||||
0x5927, 0x6A21, 0x578B, // 大模型
|
||||
0x5927, 0x6A21, 0x578B, // 大模型
|
||||
0x5927, 0x9879, 0x76EE // 大项目
|
||||
};
|
||||
err = c_NlpCharGraph_Process(&pred_graph, train_data, 9);
|
||||
RUN_TEST(err == C_ERR_OK, "Prediction training data parsed smoothly");
|
||||
|
||||
c_ucs4_t predicted_char = 0;
|
||||
double confidence = 0.0;
|
||||
|
||||
// 进行确定性的贪婪模式预测:输入 '大' (0x5927)
|
||||
// 预期:'模' (0x6A21) 出现的概率是 2/3 = 66.67%,远高于 '项' 的 1/3
|
||||
err = c_NlpCharGraph_PredictNext(&pred_graph, 0x5927, C_PREDICT_MODE_GREEDY, &predicted_char, &confidence);
|
||||
|
||||
RUN_TEST(err == C_ERR_OK, "PredictNext completed execution path successfully");
|
||||
RUN_TEST(predicted_char == 0x6A21, "Greedy mode correctly deterministically chooses '模' following '大'");
|
||||
// 校验置信度 2.0 / 3.0 近似等于 0.666667
|
||||
RUN_TEST(confidence > 0.66 && confidence < 0.67, "Confidence value evaluated exactly at 66.67%%");
|
||||
|
||||
// 测试生僻词/无后续关联的边界安全
|
||||
err = c_NlpCharGraph_PredictNext(&pred_graph, 0x76EE, C_PREDICT_MODE_GREEDY, &predicted_char, &confidence); // '目' 是文章末尾
|
||||
RUN_TEST(err == C_ERR_FAIL, "PredictNext safely rejects character with zero trailing context links");
|
||||
|
||||
c_NlpCharGraph_Destroy(&pred_graph);
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv){
|
||||
return test();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <c_NlpLanguageModel.h>
|
||||
#include <c_Memory.h>
|
||||
|
||||
|
||||
c_err_t c_NlpLanguageModel_Init(c_NlpLanguageModel_t* self, c_size_t max_n, c_size_t bucket_count) {
|
||||
if (!self || max_n < 2 || bucket_count == 0) return C_ERR_PARAM;
|
||||
|
||||
self->max_n = max_n;
|
||||
self->backoff_alpha = 0.4; // Industrial standard alpha decay baseline configuration
|
||||
|
||||
// Allocate space for all N-gram tiers down to 2-gram (size = max_n - 1)
|
||||
c_size_t graph_count = max_n - 1;
|
||||
self->levels = (c_NlpNgramGraph_t*)C_ALLOC(sizeof(c_NlpNgramGraph_t) * graph_count);
|
||||
if (!self->levels) return C_ERR_NOMEM;
|
||||
|
||||
// Initialize sub-graphs from highest order down to lowest (Bigram)
|
||||
for (c_size_t i = 0; i < graph_count; i++) {
|
||||
c_size_t current_n = max_n - i;
|
||||
c_err_t err = c_NlpNgramGraph_Init(&self->levels[i], current_n, bucket_count, 16);
|
||||
if (err != C_ERR_OK) {
|
||||
// Rollback previously initialized sub-graphs on allocation failure
|
||||
for (c_size_t k = 0; k < i; k++) {
|
||||
c_NlpNgramGraph_Destroy(&self->levels[k]);
|
||||
}
|
||||
C_FREE(self->levels);
|
||||
self->levels = NULL;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_err_t c_NlpLanguageModel_Train(c_NlpLanguageModel_t* self, const c_ucs4_t* unicode_array, c_size_t length) {
|
||||
if (!self || !self->levels || !unicode_array || length == 0) return C_ERR_PARAM;
|
||||
|
||||
c_size_t graph_count = self->max_n - 1;
|
||||
// Feed data to train all nested sub-graphs simultaneously
|
||||
for (c_size_t i = 0; i < graph_count; i++) {
|
||||
c_err_t err = c_NlpNgramGraph_Process(&self->levels[i], unicode_array, length);
|
||||
if (err != C_ERR_OK) return err;
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
c_err_t c_NlpLanguageModel_PredictSmooth(c_NlpLanguageModel_t* self, const c_ucs4_t* history_context, c_size_t history_len,
|
||||
c_ucs4_t* out_next_char, double* out_confidence) {
|
||||
if (!self || !self->levels || !history_context || history_len == 0 || !out_next_char || !out_confidence) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
c_size_t graph_count = self->max_n - 1;
|
||||
double current_alpha_multiplier = 1.0;
|
||||
|
||||
// Step down from the highest-order N-gram down to the baseline Bigram level
|
||||
for (c_size_t i = 0; i < graph_count; i++) {
|
||||
c_NlpNgramGraph_t* current_graph = &self->levels[i];
|
||||
c_size_t expected_history_len = current_graph->n_value - 1;
|
||||
|
||||
// If our current available history sequence matches the tier's required context length
|
||||
if (history_len >= expected_history_len) {
|
||||
// Extract a sliced pointer matching exactly the trailing edge suffix of our history
|
||||
c_size_t offset_start = history_len - expected_history_len;
|
||||
const c_ucs4_t* sliced_history = &history_context[offset_start];
|
||||
|
||||
double baseline_confidence = 0.0;
|
||||
// Attempt a retrieval match execution path
|
||||
c_err_t err = c_NlpNgramGraph_PredictNext(current_graph, sliced_history, out_next_char, &baseline_confidence);
|
||||
|
||||
if (err == C_ERR_OK) {
|
||||
// High-order match successful! Deduct confidence with our decay penalty multipliers
|
||||
*out_confidence = baseline_confidence * current_alpha_multiplier;
|
||||
return C_ERR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// If the query falls through (C_ERR_FAIL), cascade lower and apply the alpha smoothing penalty
|
||||
current_alpha_multiplier *= self->backoff_alpha;
|
||||
}
|
||||
|
||||
return C_ERR_FAIL; // Completely out of vocabulary context indicators across all internal graphs
|
||||
}
|
||||
|
||||
void c_NlpLanguageModel_Destroy(c_NlpLanguageModel_t* self) {
|
||||
if (!self || !self->levels) return;
|
||||
|
||||
c_size_t graph_count = self->max_n - 1;
|
||||
for (c_size_t i = 0; i < graph_count; i++) {
|
||||
c_NlpNgramGraph_Destroy(&self->levels[i]);
|
||||
}
|
||||
C_FREE(self->levels);
|
||||
self->levels = NULL;
|
||||
self->max_n = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef INCLUDED_C_NLPLANGUAGEMODEL_H
|
||||
#define INCLUDED_C_NLPLANGUAGEMODEL_H
|
||||
|
||||
#ifndef INCLUDED_C_NLPNGRAMGRAPH_H
|
||||
#include <c_NlpNgramGraph.h>
|
||||
#endif /*INCLUDED_C_NLPNGRAMGRAPH_H*/
|
||||
|
||||
|
||||
typedef struct {
|
||||
c_NlpNgramGraph_t* levels; /* Array of N-gram graphs sorted from highest order down to 2-gram */
|
||||
c_size_t max_n; /* The maximum configuration order N of the model (e.g., 3 for Trigram) */
|
||||
double backoff_alpha; /* Proportional smoothing decay factor (typically 0.4) used per back-off step */
|
||||
} c_NlpLanguageModel_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes a multi-tier cascaded Language Model environment supporting back-off smoothing.
|
||||
* @param self Pointer to the uninitialized language model structure.
|
||||
* @param max_n Maximum model order threshold (e.g., 3 creates a Trigram + Bigram cascading mesh).
|
||||
* @param bucket_count Main hash allocation array size mapped down to sub-graphs.
|
||||
* @return c_err_t C_ERR_OK on successful initialization, or dynamic memory OOM codes.
|
||||
*/
|
||||
c_err_t c_NlpLanguageModel_Init(c_NlpLanguageModel_t* self, c_size_t max_n, c_size_t bucket_count);
|
||||
|
||||
/**
|
||||
* @brief Core Prediction: Evaluates histories and executes automated back-off smoothing cascades.
|
||||
*/
|
||||
c_err_t c_NlpLanguageModel_PredictSmooth(c_NlpLanguageModel_t* self, const c_ucs4_t* history_context, c_size_t history_len,
|
||||
c_ucs4_t* out_next_char, double* out_confidence);
|
||||
|
||||
/**
|
||||
* @brief Destroys and flushes all cascaded hierarchy sub-graphs completely.
|
||||
*/
|
||||
void c_NlpLanguageModel_Destroy(c_NlpLanguageModel_t* self);
|
||||
|
||||
|
||||
c_err_t c_NlpLanguageModel_Train(c_NlpLanguageModel_t* self, const c_ucs4_t* unicode_array, c_size_t length);
|
||||
|
||||
#endif /*INCLUDED_C_NLPLANGUAGEMODEL_H*/
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "c_NlpLanguageModel.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.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)
|
||||
|
||||
static c_err_t test(void) {
|
||||
/* 34. Katz Back-off Smoothing Cascade Verification */
|
||||
c_NlpLanguageModel_t lm;
|
||||
// Instantiate a 3-tier system (Trigram + Bigram cascaded backend framework mesh)
|
||||
c_err_t err = c_NlpLanguageModel_Init(&lm, 3, 512);
|
||||
RUN_TEST(err == C_ERR_OK, "Cascaded Language Model successfully initialized");
|
||||
|
||||
// Training set containing: "大模型" and "微模型"
|
||||
c_ucs4_t text_stream[] = {
|
||||
0x5927, 0x6A21, 0x578B, /* 大模型 */
|
||||
0x5FAE, 0x6A21, 0x578B /* 微模型 */
|
||||
};
|
||||
err = c_NlpLanguageModel_Train(&lm, text_stream, 6);
|
||||
RUN_TEST(err == C_ERR_OK, "Cascaded models successfully ingested corpus dataset streams");
|
||||
|
||||
c_ucs4_t next_prediction = 0;
|
||||
double confidence_score = 0.0;
|
||||
|
||||
// --- Scenario A: Perfect high-order match ---
|
||||
// Query context: "大模" -> Expected result: "型" (100% confidence from Trigram tier)
|
||||
c_ucs4_t perfect_history[] = { 0x5927, 0x6A21 };
|
||||
err = c_NlpLanguageModel_PredictSmooth(&lm, perfect_history, 2, &next_prediction, &confidence_score);
|
||||
RUN_TEST(err == C_ERR_OK && next_prediction == 0x578B, "Scenario A: High-order match correctly resolves to '型'");
|
||||
RUN_TEST(confidence_score == 1.0, "Scenario A: Direct hit returns full unpenalized confidence metrics");
|
||||
|
||||
// --- Scenario B: Unseen Trigram history forces a Back-off step ---
|
||||
// Query context: "新模" -> The 3-gram "新模" is completely unseen in our training text
|
||||
// Expected behavior: Trigram lookup fails. Model smoothly backs off to the Bigram tier matching only "模" -> "型".
|
||||
// Mathematical projection: 1.0 baseline confidence * 0.4 alpha penalty = 0.4 adjusted score.
|
||||
c_ucs4_t unseen_trigram_history[] = { 0x65B0, 0x6A21 }; // "新模"
|
||||
err = c_NlpLanguageModel_PredictSmooth(&lm, unseen_trigram_history, 2, &next_prediction, &confidence_score);
|
||||
|
||||
RUN_TEST(err == C_ERR_OK, "Scenario B: Back-off routine successfully processes the unseen 3-gram phrase");
|
||||
RUN_TEST(next_prediction == 0x578B, "Scenario B: Lower-order bigram successfully resolves '模' ➔ '型'");
|
||||
RUN_TEST(confidence_score == 0.4, "Scenario B: Confidence safely records the 0.4 alpha smoothing decay penalty");
|
||||
|
||||
c_NlpLanguageModel_Destroy(&lm);
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv){
|
||||
return test();
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
#include <c_NlpNgramGraph.h>
|
||||
#include <c_Memory.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* FNV-1a 32位 滾動哈希,用於對變長 Unicode 陣列計算主桶索引 */
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_size_t c_NlpNgram_HashContext(const c_ucs4_t* context, c_size_t len, c_size_t bucket_count) {
|
||||
unsigned int hash = 2166136261U;
|
||||
for (c_size_t i = 0; i < len; i++) {
|
||||
hash ^= context[i];
|
||||
hash *= 16777619U;
|
||||
}
|
||||
return (c_size_t)(hash % bucket_count);
|
||||
}
|
||||
|
||||
/* 比較兩個上下文陣列是否完全一致 */
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_bool_t c_NlpNgram_ContextEquals(const c_ucs4_t* ctx1, const c_ucs4_t* ctx2, c_size_t len) {
|
||||
for (c_size_t i = 0; i < len; i++) {
|
||||
if (ctx1[i] != ctx2[i]) return C_FALSE;
|
||||
}
|
||||
return C_TRUE;
|
||||
}
|
||||
|
||||
/* 建立並初始化一個全新的歷史狀態節點 */
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_NgramNode_t* c_NgramNode_Create(const c_ucs4_t* context, c_size_t ctx_len, c_size_t rel_cap) {
|
||||
c_NgramNode_t* node = (c_NgramNode_t*)C_ALLOC(sizeof(c_NgramNode_t));
|
||||
if (!node) return NULL;
|
||||
|
||||
node->frequency = 0;
|
||||
node->relation_capacity = rel_cap;
|
||||
node->next = NULL;
|
||||
|
||||
// 深拷貝上下文狀態陣列
|
||||
node->context = (c_ucs4_t*)C_ALLOC(sizeof(c_ucs4_t) * ctx_len);
|
||||
node->next_chars = (c_NgramEdge_t**)C_ALLOC(sizeof(c_NgramEdge_t*) * rel_cap);
|
||||
|
||||
if (!node->context || !node->next_chars) {
|
||||
C_FREE(node->context);
|
||||
C_FREE(node->next_chars);
|
||||
C_FREE(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(node->context, context, sizeof(c_ucs4_t) * ctx_len);
|
||||
for (c_size_t i = 0; i < rel_cap; i++) {
|
||||
node->next_chars[i] = NULL;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpNgramGraph_Init(c_NlpNgramGraph_t* self, c_size_t n_value, c_size_t bucket_count, c_size_t relation_default_capacity) {
|
||||
if (!self || n_value < 2 || bucket_count == 0 || relation_default_capacity == 0) return C_ERR_PARAM;
|
||||
|
||||
self->n_value = n_value;
|
||||
self->bucket_count = bucket_count;
|
||||
self->relation_default_capacity = relation_default_capacity;
|
||||
self->total_states = 0;
|
||||
|
||||
self->buckets = (c_NgramNode_t**)C_ALLOC(sizeof(c_NgramNode_t*) * bucket_count);
|
||||
if (!self->buckets) return C_ERR_NOMEM;
|
||||
|
||||
for (c_size_t i = 0; i < bucket_count; i++) {
|
||||
self->buckets[i] = NULL;
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
/* 拉鏈法安全獲取或建立上下文狀態節點 */
|
||||
static c_NgramNode_t* c_NlpNgramGraph_GetOrCreate(c_NlpNgramGraph_t* self, const c_ucs4_t* context) {
|
||||
c_size_t ctx_len = self->n_value - 1;
|
||||
c_size_t idx = c_NlpNgram_HashContext(context, ctx_len, self->bucket_count);
|
||||
|
||||
c_NgramNode_t* curr = self->buckets[idx];
|
||||
c_NgramNode_t* prev = NULL;
|
||||
|
||||
while (curr != NULL) {
|
||||
if (c_NlpNgram_ContextEquals(curr->context, context, ctx_len)) {
|
||||
return curr; // 命中已有歷史狀態
|
||||
}
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
// 未命中,建立新歷史狀態節點
|
||||
c_NgramNode_t* new_node = c_NgramNode_Create(context, ctx_len, self->relation_default_capacity);
|
||||
if (!new_node) return NULL;
|
||||
|
||||
if (prev == NULL) {
|
||||
self->buckets[idx] = new_node;
|
||||
} else {
|
||||
prev->next = new_node;
|
||||
}
|
||||
self->total_states++;
|
||||
return new_node;
|
||||
}
|
||||
|
||||
/* 核心注入:滑動視窗掃描整篇文章語料 */
|
||||
c_err_t c_NlpNgramGraph_Process(c_NlpNgramGraph_t* self, const c_ucs4_t* unicode_array, c_size_t length) {
|
||||
if (!self || !self->buckets || !unicode_array || length == 0) return C_ERR_PARAM;
|
||||
|
||||
c_size_t n = self->n_value;
|
||||
// 如果文章字數小於 N 元模型所需的長度,無法建立狀態
|
||||
if (length < n) return C_ERR_OK;
|
||||
|
||||
// 滑動視窗上界為 length - n + 1
|
||||
for (c_size_t i = 0; i <= length - n; i++) {
|
||||
// 1. 當前滑動視窗的起點指標,指向長度為 N-1 的歷史上下文
|
||||
const c_ucs4_t* current_context = &unicode_array[i];
|
||||
|
||||
// 2. 獲取或建立對應的多元歷史節點
|
||||
c_NgramNode_t* node = c_NlpNgramGraph_GetOrCreate(self, current_context);
|
||||
if (!node) return C_ERR_NOMEM;
|
||||
|
||||
node->frequency++;
|
||||
|
||||
// 3. 提取第 N 個字作為預測轉移目標
|
||||
c_ucs4_t target_char = unicode_array[i + n - 1];
|
||||
|
||||
// 4. 將預測目標掛入該歷史節點的 next_chars 邊哈希拉鏈中
|
||||
c_size_t edge_idx = (c_size_t)(target_char % node->relation_capacity);
|
||||
c_NgramEdge_t* edge = node->next_chars[edge_idx];
|
||||
c_bool_t edge_found = C_FALSE;
|
||||
|
||||
while (edge != NULL) {
|
||||
if (edge->target_cp == target_char) {
|
||||
edge->co_count++;
|
||||
edge_found = C_TRUE;
|
||||
break;
|
||||
}
|
||||
edge = edge->next;
|
||||
}
|
||||
|
||||
if (!edge_found) {
|
||||
c_NgramEdge_t* new_edge = (c_NgramEdge_t*)C_ALLOC(sizeof(c_NgramEdge_t));
|
||||
if (!new_edge) return C_ERR_NOMEM;
|
||||
new_edge->target_cp = target_char;
|
||||
new_edge->co_count = 1;
|
||||
new_edge->next = node->next_chars[edge_idx];
|
||||
node->next_chars[edge_idx] = new_edge;
|
||||
}
|
||||
}
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
void c_NlpNgramGraph_Destroy(c_NlpNgramGraph_t* self) {
|
||||
if (!self || !self->buckets || self->bucket_count == 0) return;
|
||||
|
||||
c_NgramNode_t** local_buckets = self->buckets;
|
||||
c_size_t local_count = self->bucket_count;
|
||||
|
||||
// 搶先阻斷外部二次訪問漏洞
|
||||
self->buckets = NULL;
|
||||
self->bucket_count = 0;
|
||||
self->total_states = 0;
|
||||
|
||||
for (c_size_t i = 0; i < local_count; i++) {
|
||||
c_NgramNode_t* node = local_buckets[i];
|
||||
|
||||
while (node != NULL) {
|
||||
c_NgramNode_t* next_node = node->next;
|
||||
|
||||
// 1. 釋放深層歷史上下文陣列
|
||||
if (node->context != NULL) {
|
||||
C_FREE(node->context);
|
||||
}
|
||||
|
||||
// 2. 釋放後續預測鄰接邊拉鏈
|
||||
if (node->next_chars != NULL) {
|
||||
for (c_size_t k = 0; k < node->relation_capacity; k++) {
|
||||
c_NgramEdge_t* edge = node->next_chars[k];
|
||||
while (edge != NULL) {
|
||||
c_NgramEdge_t* temp_edge = edge->next;
|
||||
C_FREE(edge);
|
||||
edge = temp_edge;
|
||||
}
|
||||
}
|
||||
C_FREE(node->next_chars);
|
||||
}
|
||||
|
||||
// 3. 釋放狀態節點本身
|
||||
C_FREE(node);
|
||||
node = next_node;
|
||||
}
|
||||
}
|
||||
C_FREE(local_buckets);
|
||||
}
|
||||
|
||||
c_err_t c_NlpNgramGraph_PredictNext(c_NlpNgramGraph_t* self, const c_ucs4_t* history_context,
|
||||
c_ucs4_t* out_next_char, double* out_confidence) {
|
||||
if (!self || !self->buckets || !history_context || !out_next_char || !out_confidence) {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
c_size_t ctx_len = self->n_value - 1;
|
||||
c_size_t idx = c_NlpNgram_HashContext(history_context, ctx_len, self->bucket_count);
|
||||
|
||||
// 1. 定位多元歷史狀態
|
||||
c_NgramNode_t* node = self->buckets[idx];
|
||||
while (node != NULL) {
|
||||
if (c_NlpNgram_ContextEquals(node->context, history_context, ctx_len)) {
|
||||
break;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
// 如果語料庫中從未出現過這段連續的歷史短语(未命中)
|
||||
if (!node || node->frequency == 0) {
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
// 2. 統計該狀態下的邊權重,找出概率最高的轉移字符
|
||||
c_size_t total_co_count = 0;
|
||||
c_size_t max_co_count = 0;
|
||||
c_ucs4_t best_target_cp = 0;
|
||||
|
||||
for (c_size_t i = 0; i < node->relation_capacity; i++) {
|
||||
c_NgramEdge_t* edge = node->next_chars[i];
|
||||
while (edge != NULL) {
|
||||
total_co_count += edge->co_count;
|
||||
if (edge->co_count > max_co_count) {
|
||||
max_co_count = edge->co_count;
|
||||
best_target_cp = edge->target_cp;
|
||||
}
|
||||
edge = edge->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (total_co_count == 0) return C_ERR_FAIL;
|
||||
|
||||
// 输出預測最優解與馬爾可夫最大似然概率
|
||||
*out_next_char = best_target_cp;
|
||||
*out_confidence = (double)max_co_count / (double)total_co_count;
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef INCLUDED_C_NLPNGRAMGRAPH_H
|
||||
#define INCLUDED_C_NLPNGRAMGRAPH_H
|
||||
|
||||
#ifndef INCLUDED_C_UTF8_H
|
||||
#include <c_utf8.h>
|
||||
#endif /*INCLUDED_C_UTF8_H*/
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
/* --- 1. N-gram 預測邊(記錄後續可能出現的單個字及頻次) --- */
|
||||
typedef struct c_NgramEdge_t {
|
||||
c_ucs4_t target_cp; /* 後續預測出的第 N 個字 */
|
||||
c_size_t co_count; /* 該字在當前上下文後出現的次數 */
|
||||
struct c_NgramEdge_t* next; /* 鄰接邊哈希衝突拉鏈 */
|
||||
} c_NgramEdge_t;
|
||||
|
||||
/* --- 2. N-gram 上下文節點(代表長度為 N-1 的歷史狀態) --- */
|
||||
typedef struct c_NgramNode_t {
|
||||
c_ucs4_t* context; /* 歷史上下文陣列,長度為 N-1 */
|
||||
c_size_t frequency; /* 該上下文總出現次數 */
|
||||
|
||||
c_NgramEdge_t** next_chars; /* 後續字哈希桶(動態分配) */
|
||||
c_size_t relation_capacity; /* 邊哈希桶容量 */
|
||||
|
||||
struct c_NgramNode_t* next; /* 主哈希表衝突拉鏈指针(解決主桶碰撞) */
|
||||
} c_NgramNode_t;
|
||||
|
||||
/* --- 3. 多元馬爾可夫鏈模型主控結構体 --- */
|
||||
typedef struct {
|
||||
c_NgramNode_t** buckets; /* 主哈希表(儲存所有不重複的歷史上下文狀態) */
|
||||
c_size_t bucket_count; /* 主哈希桶大小 */
|
||||
c_size_t n_value; /* 模型階數 N (例如:3 代表 Trigram 模型) */
|
||||
c_size_t relation_default_capacity; /* 子節點邊哈希桶默認容量 */
|
||||
c_size_t total_states; /* 記錄圖中唯一存在的歷史狀態總數 */
|
||||
} c_NlpNgramGraph_t;
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_err_t c_NlpNgramGraph_Init(c_NlpNgramGraph_t* self, c_size_t n_value, c_size_t bucket_count, c_size_t relation_default_capacity);
|
||||
|
||||
void c_NlpNgramGraph_Destroy(c_NlpNgramGraph_t* self);
|
||||
|
||||
c_err_t c_NlpNgramGraph_Process(c_NlpNgramGraph_t* self, const c_ucs4_t* unicode_array, c_size_t length);
|
||||
|
||||
c_err_t c_NlpNgramGraph_PredictNext(c_NlpNgramGraph_t* self, const c_ucs4_t* history_context,
|
||||
c_ucs4_t* out_next_char, double* out_confidence);
|
||||
|
||||
#endif /*INCLUDED_C_NLPNGRAMGRAPH_H*/
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "c_NlpNgramGraph.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.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)
|
||||
|
||||
static c_err_t test(void) {
|
||||
/* 33. 多元馬爾可夫鏈(Trigram)核心通路驗證 */
|
||||
c_NlpNgramGraph_t trigram;
|
||||
// 建立 3元 模型:需要根據 2 个字的历史預測第 3 个字
|
||||
c_err_t err = c_NlpNgramGraph_Init(&trigram, 3, 512, 8);
|
||||
RUN_TEST(err == C_ERR_OK, "Trigram Graph initialized cleanly");
|
||||
|
||||
// 載入預測語料:"自然语言、自然界、自然语言"
|
||||
// 當已知歷史短语是 "自然" 时,"语" 出现了 2 次,"界" 出现了 1 次
|
||||
c_ucs4_t corpus_stream[] = {
|
||||
0x81EA, 0x7136, 0x8BED, 0x8A00, // 自然语言
|
||||
0x81EA, 0x7136, 0x754C, // 自然界
|
||||
0x81EA, 0x7136, 0x8BED, 0x8A00 // 自然语言
|
||||
};
|
||||
err = c_NlpNgramGraph_Process(&trigram, corpus_stream, 11);
|
||||
RUN_TEST(err == C_ERR_OK && trigram.total_states > 0, "Trigram processed training data stream");
|
||||
|
||||
// 構建查詢歷史:[0x81EA, 0x7136] ➔ 代表 "自然"
|
||||
c_ucs4_t query_history[] = { 0x81EA, 0x7136 };
|
||||
c_ucs4_t pred_res = 0;
|
||||
double pred_conf = 0.0;
|
||||
|
||||
err = c_NlpNgramGraph_PredictNext(&trigram, query_history, &pred_res, &pred_conf);
|
||||
RUN_TEST(err == C_ERR_OK, "PredictNext completed high-order context traversal paths");
|
||||
|
||||
// 預期:準確命中 "语" (0x8BED),置信度為 2 / 3 = 66.67%
|
||||
RUN_TEST(pred_res == 0x8BED, "Trigram language model predicts '语' given advanced context '自然'");
|
||||
RUN_TEST(pred_conf > 0.66 && pred_conf < 0.67, "Context-bound model confidence targets 66.67%% correctly");
|
||||
|
||||
c_NlpNgramGraph_Destroy(&trigram);
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv){
|
||||
return test();
|
||||
}
|
||||
Reference in New Issue
Block a user