作业基本信息
这个作业属于哪个课程 |
班级 |
这个作业要求在哪里 |
个人项目作业 |
这个作业的目标 |
使用PSP表格、熟悉项目的整体流程 |
gitcode链接
链接
PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
20 |
20 |
Estimate |
估计这个任务需要多少时间 |
20 |
10 |
Development |
开发 |
200 |
240 |
Analysis |
需求分析 (包括学习新技术) |
60 |
60 |
Design Spec |
生成设计文档 |
10 |
10 |
Design Review |
设计复审 |
10 |
15 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
10 |
10 |
Design |
具体设计 |
20 |
20 |
Coding |
具体编码 |
200 |
300 |
Code Review |
代码复审 |
30 |
50 |
Test |
测试(自我测试,修改代码,提交修改) |
120 |
120 |
Reporting |
报告 |
20 |
20 |
Test Repor |
测试报告 |
20 |
20 |
Size Measurement |
计算工作量 |
10 |
10 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
20 |
20 |
---- |
合计 |
770 |
925 |
计算模块接口的设计与实现过程
模块设计与代码组织
- 文件读取模块:read_file 函数负责将文件内容读取为字符串。
// 读取文件内容并返回字符串
char* read_file(const char* file_path) {
FILE* file = fopen(file_path, "r");
if (!file) {
perror("Failed to open file");
exit(EXIT_FAILURE);
}
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
char* buffer = (char*)malloc(length + 1);
if (!buffer) {
perror("Failed to allocate memory");
fclose(file);
exit(EXIT_FAILURE);
}
fread(buffer, 1, length, file);
buffer[length] = '\0';
fclose(file);
return buffer;
}
- 分词模块:split_into_words 函数将文本按非字母字符分割为单词,并统一转为小写。
// 将字符串分割成数组
int split_into_words(const char* text, wchar_t words[MAX_WORDS][MAX_WORD_LENGTH]) {
size_t len = mbstowcs(NULL, text, 0); // 计算宽字符长度
wchar_t* wtext = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
mbstowcs(wtext, text, len + 1); // 转换为宽字符
int word_count = 0;
for (int i = 0; wtext[i] != L'\0'; i++) {
// 判断是否是中文字符(Unicode范围:0x4E00-0x9FFF)
if (wtext[i] >= 0x4E00 && wtext[i] <= 0x9FFF) {
words[word_count][0] = wtext[i];
words[word_count][1] = L'\0';
word_count++;
}
}
free(wtext);
return word_count;
}
- 词频统计模块:calculate_word_frequency 函数统计每个单词的出现频率。
// 计算词频向量
void calculate_word_frequency(const wchar_t words[MAX_WORDS][MAX_WORD_LENGTH], int word_count, int* frequency) {
for (int i = 0; i < word_count; i++) {
frequency[i] = 1;
for (int j = i + 1; j < word_count; j++) {
if (wcscmp(words[i], words[j]) == 0) {
frequency[i]++;
frequency[j] = 0; // 标记为已处理
}
}
}
}
- 相似度计算模块:cosine_similarity 函数基于词频向量计算余弦相似度。
// 计算余弦相似度
double cosine_similarity(const int* freq1, const int* freq2, int size) {
double dot_product = 0.0;
double magnitude1 = 0.0;
double magnitude2 = 0.0;
for (int i = 0; i < size; i++) {
dot_product += freq1[i] * freq2[i];
magnitude1 += freq1[i] * freq1[i];
magnitude2 += freq2[i] * freq2[i];
}
magnitude1 = sqrt(magnitude1);
magnitude2 = sqrt(magnitude2);
if (magnitude1 == 0 || magnitude2 == 0) {
return 0.0;
}
return dot_product / (magnitude1 * magnitude2);
}
改进思路
- 目前的分词逻辑是通过 mbstowcs 将多字节字符串转换为宽字符字符串,然后遍历宽字符字符串提取中文字符。这种方法宽字符转换开销大,mbstowcs 的转换过程需要额外的内存和时间。而且手动分词效率低,遍历字符串并逐个字符判断是否为中文字符,效率较低。改进可以使用更高效的分词库。
- 词频计算逻辑是通过双重循环遍历单词数组,统计每个单词的频率。这种方法的时间复杂度为 O(n^2),效率较低。
计算模块部分单元测试
void test_read_file() {
char* text = read_file("test.txt");
assert(text != NULL);
free(text);
}
void test_split_into_words() {
wchar_t words[MAX_WORDS][MAX_WORD_LENGTH];
int count = split_into_words("这是一个测试文本", words);
assert(count == 5);
}
void test_calculate_word_frequency() {
wchar_t words[MAX_WORDS][MAX_WORD_LENGTH] = {L"测试", L"文本", L"测试"};
int frequency[MAX_WORDS] = {0};
calculate_word_frequency(words, 3, frequency);
assert(frequency[0] == 2);
assert(frequency[1] == 1);
}
void test_cosine_similarity() {
int freq1[] = {1, 2, 3};
int freq2[] = {1, 2, 3};
double similarity = cosine_similarity(freq1, freq2, 3);
assert(similarity == 1.0);
}
int main() {
test_read_file();
test_split_into_words();
test_calculate_word_frequency();
test_cosine_similarity();
printf("所有测试通过!\n");
return 0;
}
总结
第一次完整的走了一遍项目的整体流程,啊好难啊,还是代码编写的问题,这次还学到一些工具的运用方法,好难啊。