第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15710
这个作业的目标 设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。

1.github仓库链接:https://github.com/cc-fly001/cc-fly001

2.PSP表

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 25
· Estimate · 估计这个任务需要多少时间 20 25
Development 开发 140 165
· Analysis · 需求分析 (包括学习新技术) 25 30
· Design Spec · 生成设计文档 20 22
· Design Review · 设计复审 10 8
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 12
· Design · 具体设计 25 30
· Coding · 具体编码 40 50
· Code Review · 代码复审 10 13
· Test · 测试(自我测试,修改代码,提交修改) 10 10
Reporting 报告 40 45
· Test Repor · 测试报告 15 18
· Size Measurement · 计算工作量 10 12
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 15 15
· 合计 200 235

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

本项目为中文文本查重程序,采用面向过程的函数式设计,没有定义自定义类,将业务逻辑拆分为多个独立函数,函数之间为调用关系,main函数作为调度入口,依次调用文件读取、n‑gram生成、相似度计算、结果输出函数。

函数组织与接口

  1. isChineseChar(char ch):工具函数,判断字节是否为UTF‑8中文首字节,被readChineseText调用。
  2. readChineseText(const string& filePath):文件读取过滤接口,输入文件路径,输出过滤后仅保留中文的字符串。负责打开文件、逐字节读取,过滤非中文字符。
  3. getNGram(const string& text, int n = 3):核心计算接口,输入纯中文文本,输出n‑gram集合。对UTF‑8中文,每个汉字占3字节,以3字节为步长滑动窗口,切分三元词组,存入unordered_set去重。
  4. calcSimilar(const unordered_set<string>& origGram, const unordered_set<string>& copyGram):相似度计算接口,接收原文、抄袭文本的n‑gram集合,返回重复率浮点值。统计抄袭文本中出现在原文集合里的词组数量,除以抄袭文本总词组数量得到重复率。
  5. writeResult(const string& outPath, double rate):输出接口,接收输出路径与重复率,将结果格式化写入文件。
  6. main():程序入口,解析命令行参数,按顺序调用上述全部函数,捕获异常。

算法关键

  1. 针对UTF‑8编码中文,利用汉字首字节范围0xE4‑0xE9过滤文本,只保留汉字字节,过滤标点、数字、英文字符。
  2. 使用3‑gram滑动窗口,窗口大小为3个汉字(9字节),步长3字节,保证每次窗口对齐完整汉字,避免出现汉字截断乱码。
  3. 使用unordered_set存储n‑gram词组,自动去重,利用哈希查找实现O(1)的存在性判断,提升匹配效率。
  4. 重复率计算逻辑:以抄袭文档的n‑gram集合为基准,计算交集占抄袭集合的比例。

独到之处

  1. 适配UTF‑8中文编码,直接按字节处理,规避字符编码转换的复杂逻辑,不需要引入第三方库。
  2. 用集合去重,避免重复词组对查重结果造成干扰。
  3. 函数接口边界做校验,文件打开失败直接抛出异常,交由main函数统一捕获处理,实现错误集中管理。

关键函数getNGram流程图逻辑简述:
输入过滤后的中文文本 → 获取文本字节长度 → 循环i从0开始,每次+3;判断i+n*3 <= len,防止越界 → 截取子串作为三元词组 → 插入集合 → 循环结束返回集合。

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

  • 改进花费时间:25分钟

改进思路

  1. 原始版本中循环条件为i <= len‑n*3,当文本长度不足3个汉字时,len‑n*3会产生负数,int类型下会触发数组越界访问。修改为i + n*3 <= len,使用size_t无符号类型,消除越界风险,同时避免负数判断问题。
  2. 原代码int len = text.size()size_t转int会触发警告,将len改为size_t,消除类型转换隐患。
  3. 优化readChineseText,删除无用临时变量raw,减少内存开销;读取时只保存中文字节,不保存其他字符,降低后续n‑gram处理的数据量。
  4. 利用unordered_set哈希容器,相比set红黑树,查找、插入平均时间复杂度O(1),提升词组匹配速度。

性能分析说明(VS Profiler)

使用VS性能探查工具分析,消耗最大的函数是getNGram,主要开销来自字符串substr截取、unordered_set的插入操作;其次是readChineseText的文件逐字节读取。

性能分析图说明:在VS性能分析报告中,getNGram函数占据大部分CPU时间,主要是大量字符串拷贝与哈希表插入;当输入文本较大时,该函数耗时会明显上涨。

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

测试函数

测试目标函数:readChineseTextgetNGramcalcSimilar

测试数据构造思路

  1. 正常用例:输入包含中文、标点、数字、英文的UTF‑8文本,验证过滤函数是否只保留汉字。
  2. 边界用例:文本长度不足3个汉字,测试n‑gram是否不会生成非法词组;空文本输入,验证相似度返回0。
  3. 相似度测试用例:完全相同文本、完全不同文本、部分重合文本,校验重复率计算结果是否符合预期。

单元测试示例代码

#include <cassert>
#include <iostream>
#include <string>
#include <unordered_set>

void testReadChineseText()
{
    // 测试:混合中英文标点,只保留中文
    string testStr = "你好123abc世界!";
    string res = readChineseText(testStr);
    assert(res == "你好世界");
    cout << "readChineseText 测试通过" << endl;
}

void testGetNGram()
{
    string text = "一二三四五";
    auto gramSet = getNGram(text,3);
    // 三元组:一二三,二三四,三四五
    assert(gramSet.size() == 3);
    cout << "getNGram 测试通过" << endl;
}

void testCalcSimilar()
{
    unordered_set<string> s1 = {"一二三","二三四"};
    unordered_set<string> s2 = {"一二三","五六七"};
    double rate = calcSimilar(s1,s2);
    assert(rate == 0.5);
    cout << "calcSimilar 测试通过" << endl;
}

int main()
{
    testReadChineseText();
    testGetNGram();
    testCalcSimilar();
    return 0;
}
posted @ 2026-09-15 18:56  大气的l0艾萨拉  阅读(10)  评论(0)    收藏  举报