| 这个作业属于哪个课程 | 计科24级6班 |
|---|---|
| 这个作业要求在哪里 | 第一次个人编程作业:论文查重 |
| 这个作业的目标 | 独立完成小型软件开发全流程,掌握算法设计、性能分析(cProfile)、Python单元测试(unittest)及异常处理。 |
我的 GitHub 仓库地址:【3124004146】
一、PSP 2.1 表格
在正式开始编码前,我对各模块的开发时间进行了预估。得益于 Python 强大的内置库和简练的语法,实际在“具体编码”环节节省了大量时间,主要的精力花费在了测试用例的构思和性能分析上。
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 20 |
| · Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
| Development | 开发 | ||
| · Analysis | · 需求分析 (包括学习新技术) | 40 | 30 |
| · Design Spec | · 生成设计文档 | 20 | 20 |
| · Design Review | · 设计复审 | 10 | 10 |
| · Coding Standard | · 代码规范 | 10 | 10 |
| · Design | · 具体设计 | 30 | 30 |
| · Coding | · 具体编码 | 90 | 50 |
| · Code Review | · 代码复审 | 20 | 15 |
| · Test | · 测试(单元测试、覆盖率) | 60 | 80 |
| Reporting | 报告 | ||
| · Test Report | · 测试报告 | 30 | 40 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 25 |
| 合计 | 360 | 340 |
二、计算模块接口的设计与实现过程
1. 算法思路
针对中文文本的查重,相比于单纯计算字频,基于“词语”维度的比对更加精确。本项目采用 结巴分词 (jieba) + TF(词频统计) + 余弦相似度 (Cosine Similarity) 的算法模型。
核心思想:将整篇论文拆解为一个包含词汇与词频的高维空间向量。两篇论文的相似度,即转化为两个多维向量在空间中的夹角余弦值。夹角越小(余弦值越接近1),重复率越高。
2. 代码组织与函数设计
代码遵循“单一职责”原则,划分为独立的函数模块:
read_file(file_path):安全读取文本,内置utf-8编码处理,避免乱码。get_tf_vector(text):特征提取核心。独到之处在于使用jieba.cut获取分词结果,再结合 Python 标准库的collections.Counter,只需两行代码即可完美完成词频哈希表的构建。cosine_similarity(vec1, vec2):数学计算模块,找出两个词典的并集,利用公式计算并返回浮点数。
流程图如下:
命令行参数输入 -> 检查参数合法性 -> read_file 读取两文件 -> get_tf_vector 提取特征向量 -> cosine_similarity 计算余弦相似度 -> 结果格式化输出至答案文件 -> 结束。
三、计算模块接口部分的性能改进
完成代码后,我使用 Python 内置的 cProfile 性能分析工具对程序进行了探测。在终端运行命令:python -m cProfile -s time main.py orig.txt copy.txt ans.txt。

性能瓶颈与改进思路:
从 cProfile 的输出报告中可以看到,耗时最大的函数毫无悬念地落在了 jieba.cut 内部的正则表达式匹配和词典加载阶段。
由于 jieba 属于第三方强依赖库,底层优化空间较小。但在自身代码层面,我做了如下优化以压榨性能:
- 摒弃传统 for 循环嵌套:在计算向量点积和模长时,全面改用 Python 的生成器表达式 (Generator Expression),如
sum(vec1.get(w, 0) * vec2.get(w, 0) for w in words)。这种做法不仅代码优雅,而且避免了中间列表的内存开销,对于动辄上万词的长篇论文解析十分友好。 - 一次性读写内存:避免了多次 I/O 操作,文件内容一次性加载入内存处理,显著降低了磁盘读写开销。
四、计算模块部分单元测试展示
我使用 Python 自带的 unittest 库编写了测试类。针对核心的相似度计算与文件读取逻辑,构造了多种边界数据。
单元测试核心代码展示:
import unittest
from main import get_tf_vector, cosine_similarity, read_file
class TestPaperCheck(unittest.TestCase):
def test_identical_text(self):
# 测试:完全相同的文本,期望值为 1.0
txt1 = get_tf_vector("软件工程是一门实践性很强的学科")
txt2 = get_tf_vector("软件工程是一门实践性很强的学科")
sim = cosine_similarity(txt1, txt2)
self.assertAlmostEqual(sim, 1.0, places=2)
def test_totally_different(self):
# 测试:完全不相关的文本,期望值为 0.0
txt1 = get_tf_vector("今天天气不错")
txt2 = get_tf_vector("我喜欢吃苹果")
sim = cosine_similarity(txt1, txt2)
self.assertEqual(sim, 0.0)
def test_partial_overlap(self):
# 测试:部分抄袭修改,期望值在 (0, 1) 之间
txt1 = get_tf_vector("今天是星期天,天气晴,今天晚上我要去看电影。")
txt2 = get_tf_vector("今天是周天,天气晴朗,我晚上要去看电影。")
sim = cosine_similarity(txt1, txt2)
self.assertTrue(0.5 < sim < 0.99)

五、计算模块部分异常处理说明
在一个规范的工程项目中,异常处理机制必不可少。本项目设计了 3 种主要的异常处理机制:
1. 命令行参数缺失 (IndexError 级拦截)
- 设计目标:防止用户少传或不传文件路径参数,导致后续取参时数组越界。
- 对应场景:用户直接双击
main.py或仅输入python main.py orig.txt。 - 应对方案:在程序入口检查
len(sys.argv) != 4,若不满足则打印用法提示并正常退出(sys.exit(1)),避免向用户抛出代码 Traceback。
2. 文件不存在异常 (FileNotFoundError)
- 设计目标:防止程序试图读取不存在的文件或无权限访问的路径。
- 对应场景:传入了错误的路径,或者原文件已被删除。
- 测试用例与表现:
在test_main.py中构造测试:调用read_file("not_exist.txt")。
实际运行时,终端会捕获并友好提示:错误:找不到文件 not_exist.txt,随后安全退出,不会抛出红色的报错代码。
3. 空文件除零异常 (ValueError 与防御性编码)
- 设计目标:计算空文本的余弦相似度会导致分母为 0,程序会直接崩溃。
- 对应场景:提供的比对文档或抄袭文档内容全为空格,或大小为 0 KB。
- 应对方案:
在read_file阶段拦截:if not text.strip(): raise ValueError(...);
同时在cosine_similarity核心计算函数末尾加入防御性代码:if mag1 * mag2 == 0: return 0.0,彻底杜绝了由于零向量导致除零崩溃的隐患。
浙公网安备 33010602011771号