第一次个人编程作业

GitHub 项目地址:linyanzhi591-afk/linyanzhi591-afk

第一次个人编程作业:论文查重

一、项目说明

本次作业使用 Python 3 实现一个论文查重程序。程序从命令行接收原文文件、抄袭版文件和答案文件的绝对路径,读取前两份文本,计算重复率,并将结果写入答案文件。

项目目录为 3124004184/,入口文件为 main.py

运行方式:

python main.py [原文文件绝对路径] [抄袭版文件绝对路径] [答案文件绝对路径]

例如:

python main.py C:\tests\orig.txt C:\tests\orig_add.txt C:\tests\ans.txt

答案文件输出 0 到 1 之间的浮点数,精确到小数点后两位,例如:

0.71

Python 项目仅使用标准库,requirements.txt 用于说明不需要额外的运行依赖。

二、需求分析

程序需要满足以下要求:

  1. 从命令行读取三个文件的绝对路径。
  2. 读取原文和抄袭版文本。
  3. 对增删改后的文本计算重复率。
  4. 将结果写入指定答案文件。
  5. 输出格式为保留两位小数的浮点数。
  6. 输入文件不存在、参数错误或答案文件无法写入时,程序不能异常退出。
  7. 程序不连接网络,不读写指定文件之外的其他业务文件。

三、算法设计

3.1 总体思路

程序将文本相似度转化为词频向量之间的余弦相似度。处理过程如下:

读取原文和抄袭版
        ↓
过滤标点、空白和无关字符
        ↓
中文按单字切分,英文单词和数字按 token 切分
        ↓
统计每个 token 的出现次数
        ↓
构造两个词频向量
        ↓
计算余弦相似度
        ↓
格式化为两位小数并写入答案文件

3.2 文本切分

中文文本没有天然空格,程序将每个中文字符作为一个 token。例如:

今天是晴天

会切分为:

今、天、是、晴、天

英文单词和数字作为完整 token,例如:

Python 3.12

会切分为:

python、3.12

英文统一转换为小写,使 Pythonpython 被视为相同内容。标点和空白不会参与相似度计算。

3.3 余弦相似度

设原文词频向量为 A,抄袭版词频向量为 B,重复率计算公式为:

similarity = A · B / (|A| × |B|)

其中:

  • A · B 是两个向量的点积;
  • |A||B| 是两个向量的模长;
  • 两份文本完全相同时结果为 1.00
  • 没有共同 token 或任意一份文本为空时结果为 0.00

这种方法能够处理文本中的增加、删除和部分修改,并且不依赖联网服务或第三方分词接口。

3.4 复杂度分析

设两份文本的 token 数量分别为 nm,不同 token 数量为 k

  • 时间复杂度:O(n + m + k)
  • 空间复杂度:O(k)

程序只进行有限次文本扫描和词频统计,适合本题的文件规模和 5 秒运行限制。

四、模块和接口设计

程序采用函数式模块划分,没有引入不必要的类层次。

函数 作用
tokenize(text) 将文本转换为 token 列表
vector_similarity(left, right) 根据两个 token 序列计算余弦相似度
plagiarism_rate(original_text, copied_text) 对外提供重复率计算接口
read_text(path_text) 读取 UTF-8 文本文件
write_result(path_text, rate) 将两位小数结果写入答案文件
main(arguments) 处理命令行参数并组织完整流程

其中 plagiarism_rate 是计算模块的主要接口,文件读写和命令行处理与计算逻辑分离,便于单元测试。

五、代码规范与质量检查

代码遵循以下规范:

  • 使用有意义的英文函数名和变量名;
  • 函数使用类型标注;
  • 关键函数包含文档字符串;
  • 输入输出异常通过自定义异常类型表达;
  • 主程序入口使用 if __name__ == "__main__"
  • 不使用网络请求、系统命令或与题目无关的文件读写。

已完成的本地检查:

python -m unittest discover -s tests -v
python -m compileall -q .

13 个单元测试全部通过,Python 文件编译检查通过。

如需补充课程要求的代码质量工具截图,可运行:

python -m pip install pylint
pylint main.py

image

六、性能分析与改进

6.1 性能分析

使用 Python 标准库 cProfile 对大规模重复文本进行本地性能检查,主要热点集中在:

  1. tokenize:正则表达式扫描文本;
  2. vector_similarity:建立词频统计并计算向量;
  3. plagiarism_rate:组织两个步骤。

性能分析结果符合算法预期,没有发现重复扫描原始文本或不必要的嵌套遍历。

6.2 性能改进

初始设计中,程序可能为每个 token 反复查找其出现次数。实现时改为使用 collections.Counter 一次性完成词频统计,并使用集合交集只计算两个文本共有的 token:

common_tokens = left_counter.keys() & right_counter.keys()

这样减少了无效查找,使计算复杂度保持在线性范围内,同时避免保存完整的稠密向量。

image

七、单元测试

测试文件为 3124004184/tests/test_main.py,共 13 个测试用例,超过题目要求的 10 个。

测试覆盖内容如下:

测试场景 验证内容
中文、英文和数字切分 验证基本 token 规则
两份文本完全相同 结果应为 1.00
两份文本完全不同 结果应为 0.00
一份文本为空 验证空输入处理
两份文本都为空 验证除零保护
重复 token 频率不同 验证词频会影响结果
英文大小写不同 验证大小写归一化
标点不同 验证标点不会影响结果
数字 token 验证数字处理
输入文件不存在 验证输入异常
答案文件格式 验证输出两位小数
命令行参数数量错误 验证参数异常
相似度边界 验证结果在 [0, 1] 范围内

运行测试:

python -m unittest discover -s tests -v

测试结果:

Ran 13 tests in 0.025s
OK

测试数据采用白盒测试和边界值测试相结合的方式,分别覆盖正常流程、空输入、完全相同、完全不同、非法路径和非法参数等分支。

image

八、异常处理

8.1 参数数量错误

当命令行参数不是 3 个时,程序输出使用说明并返回非零状态码,避免使用不存在的参数导致异常。

对应测试:test_main_rejects_wrong_argument_count

8.2 输入文件不存在或无法读取

read_text 在读取前检查文件是否存在,并捕获文件系统异常和编码异常,统一转换为 InputFileError

对应测试:test_missing_input_raises

8.3 答案文件无法写入

write_result 捕获答案文件创建和写入过程中的 OSError,转换为 OutputFileError,由主函数统一输出错误信息。

8.4 空文本

当任意一份文本没有有效 token 时,函数返回 0.0,避免余弦相似度计算中的除零异常。

对应测试:test_empty_text_is_zerotest_both_empty_texts_are_zero

九、GitHub 版本管理

本地项目按照功能划分进行了两次提交:

提交信息 内容
完成论文查重计算模块 完成 main.pyrequirements.txt
补充测试文档和性能记录 补充单元测试、README、PSP 和性能说明

最终代码已上传到 GitHub 仓库的 3124004184/ 文件夹中。

十、PSP 表格

阶段 预估耗时(分钟) 实际耗时(分钟)
需求分析 30 15
设计文档 30 20
设计复审 15 10
代码规范 10 5
具体设计 30 20
具体编码 60 45
代码复审 20 10
测试与修改 60 25
测试报告 30 10
工作量度量 10 5
事后总结与过程改进 20 5
合计 315 170

十一、总结

本项目完成了论文原文和抄袭版文件的命令行读取、文本预处理、词频统计、余弦相似度计算和结果文件输出。通过模块化函数设计,计算逻辑可以脱离文件系统单独测试;通过 Counter 和集合交集降低了重复查找开销;通过 13 个测试用例覆盖了主要正常分支和异常分支。

提交前还需要将实际的代码质量分析、性能分析和测试覆盖率截图补充到对应位置,并使用课堂正式样例验证 18 个测试点。

posted @ 2026-09-13 18:00  cxjtc  阅读(9)  评论(0)    收藏  举报