软工个人项目

论文查重系统设计与实现

GitHub作业链接: https://github.com/Playerhh/playerhh/tree/main/3223004773

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477

PSP表格

PSP阶段 任务内容 预估耗时(分钟) 实际耗时(分钟)
计划 明确需求和其他因素,估计每个阶段的时间成本 30 35
开发 310 330
› 需求分析 阅读题目要求,理解论文查重的业务场景 20 25
› 技术选型 选择C语言实现,确定使用n-gram+Jaccard算法 30 25
› 设计文档 设计系统架构和核心算法流程 40 45
› 代码规范 制定编码规范和注释标准 20 15
› 具体设计 设计数据结构、函数接口和模块划分 40 50
› 具体编码 编写实现代码 120 130
› 代码复审 检查代码质量和规范符合情况 40 40
测试 80 90
› 测试设计 设计测试用例和测试方案 30 35
› 测试执行 执行单元测试、集成测试和性能测试 40 45
› 测试报告 整理测试结果和问题记录 10 10
报告 60 65
› 测试报告 总结测试过程和结果 20 25
› 计算报告 记录工作量并总结经验教训 20 20
› 总结报告 撰写项目总结和技术反思 20 20
合计 480 520

计算模块接口的设计与实现过程

系统架构设计

本论文查重系统采用模块化设计,主要包含以下几个核心模块:

  1. 文件处理模块:负责读取原文和抄袭版文件内容
  2. 文本预处理模块:进行大小写转换和标点符号去除
  3. n-gram生成模块:将文本分割为固定长度的字符片段
  4. 哈希表管理模块:高效存储和查询n-gram特征
  5. 相似度计算模块:基于Jaccard系数计算文本相似度
  6. 结果输出模块:将结果写入指定文件

函数关系:
main()
├── fopen() [系统调用]
├── fread() [系统调用]
├── fclose() [系统调用]
├── to_lower_case()
├── remove_punctuation()
├── create_hash_table() [2次]
├── generate_ngrams() [2次]
├── calculate_jaccard_similarity()
│ ├── get_intersection_count()
│ │ ├── hash_function()
│ │ └── strcmp() [系统调用]
│ └── get_union_count()
├── fopen() [系统调用]
├── fprintf() [系统调用]
├── fclose() [系统调用]
└── free_hash_table() [2次]

核心数据结构

// n-gram节点结构
typedef struct NGramNode {
    char gram[N_GRAM + 1];  // n-gram字符串
    int count;              // 出现次数
    struct NGramNode *next; // 下一个节点(处理哈希冲突)
} NGramNode;

// 哈希表结构
typedef struct {
    NGramNode **table;      // 哈希表数组
    int size;               // 哈希表大小
} HashTable;

关键算法流程

n-gram生成算法

输入: 预处理后的文本字符串
输出: 包含所有n-gram的哈希表

  1. 初始化空哈希表
  2. 使用滑动窗口遍历文本
  3. 对于每个位置,提取长度为N的子串
  4. 将子串插入哈希表(已存在则计数+1,否则新建节点)
  5. 返回填充完成的哈希表

Jaccard相似度计算

Jaccard(A,B) = |A ∩ B| / |A ∪ B|
= |A ∩ B| / (|A| + |B| - |A ∩ B|)

其中:

  • A ∩ B: 两个文本共有的n-gram数量(取最小计数)
  • A ∪ B: 两个文本所有n-gram的数量总和
独到之处

高效的哈希表设计:使用DJB2哈希算法和链表法解决冲突,保证O(1)的平均查找时间

内存优化:通过合理的结构设计和内存管理,避免内存泄漏
中文支持:特殊处理中文字符,避免误判
容错性强:对空文件和异常输入有良好的处理机制

计算模块接口部分的性能改进

性能优化策略

在开发过程中,我主要从以下几个方面进行了性能优化:

哈希表大小选择:使用质数100003作为哈希表大小,减少哈希冲突

内存预分配:一次性读取文件内容,避免频繁的I/O操作

算法优化:使用高效的DJB2哈希算法,提高查找速度

循环优化:减少不必要的循环和函数调用

性能分析

使用gprof工具进行性能分析,得到以下结果:

% cumulative self self total
time seconds seconds calls ms/call ms/call name
45.3 0.15 0.15 1000000 0.00 0.00 hash_function
25.8 0.23 0.08 500000 0.00 0.00 add_to_hash_table
12.9 0.27 0.04 200000 0.00 0.00 get_intersection_count
9.7 0.30 0.03 2 15.00 15.00 generate_ngrams
6.5 0.32 0.02 1 20.00 125.00 calculate_jaccard_similarity

覆盖率:
File 'main.c'
Lines executed: 95.34% of 215
Branches executed: 92.67% of 150
Taken at least once: 88.33% of 150
Calls executed: 96.77% of 62

使用时为避免中文编码问题:在终端先设置Active code page: 65001

计算模块部分单元测试展示

单元测试代码用例


// 测试1: 标点符号去除功能
void test_remove_punctuation()
{
    printf("\n=== 测试标点符号去除 ===\n");

    char test_str1[] = "Hello, World!";
    char expected1[] = "Hello World";
    remove_punctuation(test_str1);
    TEST_ASSERT_EQUAL_STRING(expected1, test_str1, "英文标点去除");

    char test_str2[] = "你好,世界!";
    char expected2[] = "你好世界";
    remove_punctuation(test_str2);
    TEST_ASSERT_EQUAL_STRING(expected2, test_str2, "中文标点去除");

    char test_str3[] = "测试123,456!789";
    char expected3[] = "测试123456789";
    remove_punctuation(test_str3);
    TEST_ASSERT_EQUAL_STRING(expected3, test_str3, "数字和标点混合");
}

// 测试2: 小写转换功能
void test_to_lower_case()
{
    printf("\n=== 测试小写转换 ===\n");

    char test_str1[] = "Hello WORLD";
    char expected1[] = "hello world";
    to_lower_case(test_str1);
    TEST_ASSERT_EQUAL_STRING(expected1, test_str1, "英文大小写转换");

    char test_str2[] = "HELLO World 中文";
    char expected2[] = "hello world 中文";
    to_lower_case(test_str2);
    TEST_ASSERT_EQUAL_STRING(expected2, test_str2, "中英文混合大小写转换");
}

标点符号测试失败,后改进

void remove_punctuation(char *str)
{
    char *src = str;
    char *dst = str;

    while (*src)
    {
        // 检查是否是ASCII字母、数字或空格
        if (isalnum((unsigned char)*src) || *src == ' ')
        {
            *dst++ = *src;
            src++;
        }
        // 检查是否是中文字符(UTF-8编码)
        else if ((unsigned char)*src & 0x80)
        {
            // 获取完整的UTF-8字符
            unsigned char byte1 = (unsigned char)*src;
            unsigned char byte2 = (unsigned char)*(src + 1);

            // 常见中文标点符号的UTF-8编码范围
           
            if (byte1 == 0xEF && byte2 == 0xBC)
            {
                // 这是中文标点,跳过3个字节
                src += 3;
            }
            else
            {
                // 这是中文字符,保留3个字节
                *dst++ = *src++;
                *dst++ = *src++;
                *dst++ = *src++;
            }
        }
        else
        {
            // 跳过ASCII标点符号
            src++;
        }
    }
    *dst = '\0';
}
posted @ 2025-09-20 23:51  Helen-haha  阅读(31)  评论(0)    收藏  举报