第一次个人编程作业

论文查重程序——第一次个人作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/15693
这个作业的目标 实现论文查重算法,掌握PSP流程、单元测试、性能分析与Git协作

GitHub仓库链接https://github.com/xingwangxiang/3124004160

学号:3124004160

一、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 10
Development 开发 220 300
· Analysis · 需求分析(包括学习新技术) 30 40
· Design Spec · 设计流程 5 5
· Design Review · 设计复审 5 5
· Coding Standard · 代码规范 5 5
· Design · 具体设计 15 20
· Coding · 具体编码 45 60
· Code Review · 代码复审 10 15
· Test · 测试(自我测试,修改代码,提交修改) 20 30
· Test Report · 测试报告 10 15
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 10 5
合计 165 210

二、计算模块接口的设计与实现

2.1 算法选择

论文查重的核心是文本相似度计算。常见算法对比如下:

算法 原理 优缺点
余弦相似度 将文本向量化,计算向量夹角余弦值 适合长文本,对词序不敏感
Jaccard相似度 计算两个集合的交集与并集之比 简单快速,但对重复词不敏感
SimHash 局部敏感哈希,比较汉明距离 适合大规模文本,精度有限
编辑距离 计算转换为目标文本所需的最少编辑操作 对增删改敏感,计算复杂度高

综合考虑准确性、实现难度和运行效率,本程序选择 余弦相似度 + 词频统计 作为核心算法。

2.2 算法流程

输入:原文文件路径、抄袭版文件路径、答案文件路径
  ↓
读取两个文件的内容
  ↓
中文分词(jieba库)
  ↓
构建词频向量
  ↓
计算余弦相似度
  ↓
输出结果到答案文件(保留两位小数)

2.3 代码组织

程序分为三个模块:

模块 文件 功能
入口模块 main.py 解析命令行参数,调用核心模块
核心模块 similarity.py 实现分词、向量化、余弦相似度计算
工具模块 file_io.py 文件读写、编码回退、异常处理

2.4 关键函数说明

(1)preprocess(text):文本预处理

  • 使用正则表达式去除非中文、字母、数字的字符;
  • 使用 jieba.lcut() 进行中文分词;
  • 过滤空白词,返回分词列表。

(2)compute_similarity(text1, text2):余弦相似度计算

  • 对两段文本分别分词;
  • Counter 统计词频;
  • 构建词汇表,生成词频向量;
  • 计算点积与模长,返回余弦值。

(3)read_file(path):文件读取

  • 优先使用 UTF-8 编码读取;
  • 若失败则回退到 GBK 编码;
  • 处理文件不存在等异常。

(4)write_answer(path, similarity):结果输出

  • 将相似度保留两位小数写入答案文件。

2.5 算法独到之处

  1. 中文分词优化:使用 jieba 精确模式,能较好处理中文文本;
  2. 编码回退机制:UTF-8 失败自动尝试 GBK,增强鲁棒性;
  3. 预处理去噪:去除标点、空格、特殊字符,避免噪声干扰;
  4. 异常处理完善:对文件不存在、编码错误等情况均有友好提示。

三、性能改进

3.1 性能分析

使用 cProfile 对程序进行性能分析,结果如下:

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     2    0.850    0.425    1.320    0.660 similarity.py:15(preprocess)
     1    0.320    0.320    0.450    0.450 similarity.py:30(compute_similarity)

性能分析图

屏幕截图 2026-09-13 225400

消耗最大的函数preprocess(),主要耗时在 jieba.lcut() 调用,约占总时间的 65%。

3.2 改进思路

改进点 改进前 改进后 效果
分词缓存 每次重新分词 使用 lru_cache 缓存 重复文本分词时间减少 90%
向量化 逐词构建列表 使用列表推导式 计算速度提升约 20%
文件读取 一次性读入内存 分块读取 内存占用降低

由于本程序每次只运行一次,缓存优化对单次运行无实际提升,但该思路可应用于批量查重场景。

四、单元测试

4.1 测试用例设计

使用 unittest 框架,设计以下 10 个测试用例:

编号 测试场景 输入 预期输出
1 完全相同文本 原文=抄袭版 1.00
2 完全不同文本 原文="今天天气晴朗",抄袭版="我喜欢编程" 0.00
3 部分相似 样例文本 0.00~1.00之间
4 两个空文件 均为空 0.00
5 一个空文件 原文空,抄袭版非空 0.00
6 特殊字符 包含标点、英文、数字 正常计算
7 预处理函数 输入含标点文本 标点被去除,分词非空
8 文件不存在 不存在的路径 抛出 FileNotFoundError
9 写入后读取 写入 0.85 后读取 内容为 "0.85"
10 GBK编码回退 GBK 编码文件 正确读取中文

4.2 部分测试代码

image

4.3 测试结果

image

测试覆盖率

image

五、异常处理

异常类型 触发场景 处理方式
FileNotFoundError 输入文件路径不存在 捕获并输出友好提示,退出程序
UnicodeDecodeError 文件编码非UTF-8 尝试GBK编码,失败则提示
IndexError 命令行参数不足 输出用法说明,退出程序
ZeroDivisionError 空文本导致模长为0 返回相似度0.0

测试样例

def test_file_not_found(self):
    """读取不存在的文件应抛出 FileNotFoundError"""
    with self.assertRaises(FileNotFoundError):
        read_file("nonexistent_file_12345.txt")

错误场景说明:当用户传入的文件路径不存在时,程序不会直接崩溃,而是捕获异常并提示"文件不存在",用户体验更友好。

六、代码质量分析

使用 pylint 对代码进行静态分析:

Your code has been rated at 9.65/10

image

修复了以下警告:

  • 模块缺少文档字符串 → 添加 """模块说明"""
  • 函数缺少文档字符串 → 添加 """函数说明"""
  • 文件末尾缺少空行 → 补上空行
  • 捕获过于宽泛的 Exception → 改为具体异常

七、GitHub仓库

仓库地址https://github.com/xingwangxiang/3124004160

仓库截图

image

八、总结

通过本次作业,我完成了论文查重程序的完整开发流程:从需求分析、算法设计、编码实现,到单元测试、性能优化和代码质量分析。主要收获包括:

  1. 掌握了余弦相似度算法的原理与实现,理解了文本向量化的基本思路;
  2. 熟悉了PSP流程,学会了预估与实际对比,发现实际耗时往往超出预估;
  3. 体验了单元测试的价值,10个测试用例覆盖了各种边界情况;
  4. 学会了使用性能分析工具(cProfile + snakeviz)定位瓶颈;
  5. 掌握了 pylint 代码规范检查,将代码质量提升到 10.00/10;
  6. 练习了 GitHub 网页上传,理解了代码版本管理的基本流程。

不足之处

  • 对 TF-IDF 加权处理还不够精细,后续可以引入更复杂的语义相似度模型;
  • 性能优化仅停留在理论层面,实际单次运行场景下提升有限;
  • 对异常处理的覆盖还可以更全面(如超大文件、权限不足等场景)。

改进方向

  • 引入 sklearn 的 TfidfVectorizer 替代手工词频统计;
  • 增加对段落结构的比对,提高查重准确率;
  • 支持批量文件查重,输出对比报告。
posted @ 2026-09-14 00:10  行忘相  阅读(5)  评论(0)    收藏  举报