第一次个人编程作业
第一次个人编程作业
| 这个作业属于哪个课程 | 软件工程 |
|---|---|
| 这个作业要求在哪里 | 第二次作业要求 |
| 这个作业的目标 | 完成个人编程作业编码部分 |
作业 GitHub 链接:https://github.com/SY-lin123/plagiarism-check/tree/main/3124004476
论文查重
软件工程课程个人项目 —— 基于 TF-IDF 余弦相似度的论文查重工具(Python 3)
一、使用说明
pip install -r requirements.txt
python main.py [原文文件] [抄袭版论文文件] [答案文件]
示例:
python main.py C:\tests\orig.txt C:\tests\orig_add.txt C:\tests\ans.txt
ans.txt 中输出重复率(百分数,保留两位小数,如 85.23)。
若老师要求输出 0~1 的小数,把 main.py 中的 format_rate(rate) 改为 f"{rate:.2f}" 即可。
二、算法设计
模块组织
| 文件 | 职责 |
|---|---|
main.py |
命令行入口:解析参数 → 读文件 → 调用查重 → 写答案;统一捕获异常并返回退出码 |
core.py |
核心计算:tokenize() 分词、_tfidf_weights() 加权、similarity() 计算余弦相似度;自定义异常体系 |
main.main() 是唯一对外接口,core.similarity() 是计算模块唯一被外部调用的函数,耦合度低、便于单元测试。
计算流程
读入两篇文本
│
▼
tokenize() jieba 精确模式分词,英文统一小写,标点/空白作为分隔符
│ (jieba 不可用时自动退化为字符 bigram,保证鲁棒性)
▼
Counter 统计词频 → 亚线性词频 tf = 1 + ln(count)
│
▼
TF-IDF 加权 idf = ln((1+N)/(1+df)) + 1 (sklearn 平滑形式,N=2)
│
▼
余弦相似度 = Σ(w₁·w₂) / (‖w₁‖·‖w₂‖) (点积只遍历较小词典,加快速度)
│
▼
×100 保留两位小数 → 写入答案文件
算法关键与独到之处
- 亚线性词频 + 平滑 IDF:抑制"的、了"等高频词和超长抄袭段落的支配作用,增删改情形下相似度变化更平滑、更符合直觉。
- 鲁棒降级:jieba 未安装时自动切换为字符 bigram(
今天是→今天/是天/天是/是星/星期/期天/天,),对插入/删除字符依然稳健。 - 性能优化:点积遍历时交换两词典保证遍历较小者;
Counter+ C 实现的_count_elements统计词频;13.5 万字文档全流程约 0.18 秒(见doc/性能分析图.png)。 - 异常安全:所有文件/参数/空文档错误均被捕获,按题意空文档输出
0.00而不崩溃,保证评测程序稳定拿到答案文件。
三、性能分析
使用 cProfile 对 13.5 万字长文档的相似度计算进行剖析(完整数据见 doc/profile.txt):
- 总耗时 0.178 s,其中
tokenize()占 0.155 s(87%),是绝对瓶颈; - 进一步分解:
str.lower()0.055 s、正则切分 0.040 s、词频统计 0.021 s; - 结论:分词为 CPU 密集环节,已通过「仅遍历较小词典」把余弦计算压到 3 ms 以内;如需再提速可关闭 jieba 的 HMM 新词发现(
jieba.cut(..., HMM=False)),代价是新词召回略降。
性能分析图:doc/性能分析图.png
四、单元测试
python -m unittest discover -s tests -v # 运行 15 个测试用例
coverage run --source=core,main -m unittest discover -s tests
coverage report -m # 查看分支/语句覆盖率
测试设计(白盒为主,共 15 个用例):
| # | 用例 | 覆盖场景 |
|---|---|---|
| 1 | 相同文本 | 相似度恒为 1.0(边界) |
| 2 | 题目样例增删改版 | 业务正确性,结果 > 0.5 |
| 3 | 完全无关文本 | 结果趋近 0 |
| 4 | 插入大段新内容 | 相似度下降但保持较高 |
| 5 | 空字符串 | EmptyDocumentError |
| 6 | 纯空白文档 | EmptyDocumentError(tokenize 分支) |
| 7 | 英文大小写/空格差异 | 归一化正确性 |
| 8 | 数字与标点 | 非中文字符不引发异常 |
| 9 | 2 万字长文本 | 性能回归(< 1 s) |
| 10 | 格式化边界值 | 0.0/1.0 → 0.00/100.00 |
| 11 | 越界相似度 | ValueError |
| 12 | 入口正常流程 | 返回 0,答案符合两位小数格式 |
| 13 | 参数个数错误 | 返回 2(CliArgumentError 场景) |
| 14 | 文件不存在 | 返回 1(FileAccessError 场景) |
| 15 | 空文件输入 | 返回 0 且写入 0.00(异常兜底) |
五、异常处理说明
| 异常 | 设计目标 | 触发场景(对应测试用例) |
|---|---|---|
CliArgumentError |
参数不是 3 个时给出明确提示 | 用例 13 |
FileAccessError |
文件不存在/不可读时不让程序崩溃 | 用例 14 |
EmptyDocumentError |
空文档/纯空白文档的明确语义 | 用例 5、6 |
| 兜底策略 | 抄袭版为空时按 0% 处理并正常写出答案 | 用例 15 |
六、PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 15 |
| Estimate | 估计这个任务需要多少时间 | 20 | 15 |
| Development | 开发 | 400 | 545 |
| Analysis | 需求分析(包括学习新技术) | 60 | 80 |
| Design Spec | 生成设计文档 | 30 | 25 |
| Design Review | 设计复审(和同事审核设计文档) | 20 | 15 |
| Coding Standard | 代码规范(为目前的开发制定合适的规范) | 15 | 10 |
| Coding | 具体编码 | 120 | 150 |
| Code Review | 代码复审 | 30 | 35 |
| Test | 测试(自我测试,修改代码,提交修改) | 125 | 230 |
| Reporting | 报告 | 110 | 125 |
| Test Report | 测试报告 | 30 | 30 |
| Size Measurement | 计算工作量 | 15 | 10 |
| Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 65 | 85 |
| Total | 合计 | 530 | 685 |

浙公网安备 33010602011771号