第一次编程作业
作业链接:https://github.com/dwjdyx/3224004116.git
一.任务
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/ |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/15693 |
| 这个作业的目标 | 编码实现项目“论文查重”,通过github进行版本管理 |
二.PSP表格
| PSP2.1 | 预估耗时(分钟) |
|---|---|
| Planning | |
| · Estimate(估计这个任务需要多少时间) | 300 |
| Development | |
| · Analysis(需求分析,包括学习新技术) | 30 |
| · Design Spec(生成设计文档) | 20 |
| · Design Review(设计复审) | 15 |
| · Coding Standard(代码规范) | 10 |
| · Design(具体设计) | 30 |
| · Coding(具体编码) | 120 |
| · Code Review(代码复审) | 30 |
| · Test(自我测试,修改代码,提交修改) | 70 |
| Reporting | |
| · Test Report(测试报告) | 30 |
| · Size Measurement(计算工作量) | 15 |
| · Postmortem & Process Improvement Plan | 25 |
三.计算模块接口的设计与实现过程
1. 代码组织结构设计
本项目采用模块化设计思想,将论文查重过程划分为文本处理模块、特征提取模块、相似度计算模块以及程序入口模块。
程序主要由以下几个函数组成:
| 模块 | 主要函数 | 功能 |
|---|---|---|
| 文件读取模块 | read_file() | 负责读取原文和抄袭版论文文件,并返回文本内容 |
| 文本预处理模块 | clean_text() | 去除标点符号及无关字符,降低文本格式差异对结果的影响 |
| 特征提取模块 | create_ngram() | 将文本转换为 N‑gram 特征集合,用于表示文本内容 |
| 重复率计算模块 | calculate_similarity() | 根据两个文本的特征集合计算重复率 |
| 程序控制模块 | main() | 负责读取命令行参数,调用各模块,并将结果写入指定文件 |
各模块之间通过函数参数传递数据,降低了模块之间的耦合性,便于后续测试和维护。
2. 关键算法设计
本项目采用基于 N-gram文本特征和集合相似度计算 的论文查重算法。
算法主要流程如下:
(1)文本预处理
由于论文中可能存在:
标点符号不同;
空格、换行不同;
格式差异;
因此首先对文本进行归一化处理。
处理过程:
读取文本;
删除无意义符号;
保留中文字符、英文字符和数字。
例如:
原文:
今天是星期天,天气晴。
处理后:
今天是星期天天气晴
这样可以减少格式变化对检测结果的影响。
(2)N-gram特征提取
将连续的字符组合转换为文本特征。
例如:
文本:
机器人控制
生成二元和三元特征:
机器人
器人控
人控制
通过这种方式,可以将文本转换为集合形式:{机器人,器人控,人控制}
即使论文进行了部分修改,仍然可以通过部分相同片段发现重复内容。
(3)相似度计算
将原文和抄袭版论文分别转换为 N-gram 特征集合后,程序统计两个集合中相同的文本特征数量,并计算这些重复特征在抄袭版论文全部特征中的比例。该比例能够反映抄袭版论文中与原文相似的内容占比,作为最终的重复率结果。
相比传统的整体文本比较方法,该方法能够适应一定程度的文字修改。
3. 算法特点与创新点
本算法主要特点如下:
(1)基于文本局部特征匹配
不同于简单字符串匹配,N-gram可以检测连续文本片段,即使论文进行了部分修改,也可以发现相似内容。
(2)对文本格式变化具有一定鲁棒性
通过预处理去除标点、空格等无关信息,使算法关注文本内容本身。
(3)模块化设计便于扩展
当前算法基于字符N-gram实现,后续可以进一步扩展:
使用词级分词;
引入TF-IDF权重;
使用余弦相似度;
增加语义分析模型。
四.计算模块接口部分的性能改进
1. 性能分析过程
为了分析程序运行过程中的性能瓶颈,使用 Python 自带的 cProfile 性能分析工具对论文查重程序进行性能测试。
由于测试论文规模较小,各模块执行时间均较短。以下为测试用例文字扩大100倍后的部分关键测试结果
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.057 0.057 {built-in method builtins.exec}
1 0.000 0.000 0.057 0.057 main.py:1(
1 0.000 0.000 0.057 0.057 main.py:66(main)
1 0.000 0.000 0.052 0.052 main.py:36(calculate_similarity)
2 0.034 0.017 0.052 0.026 main.py:24(create_ngram)
265194 0.018 0.000 0.018 0.000 {method 'add' of 'set' objects}
2 0.000 0.000 0.003 0.002 main.py:11(clean_text)
2 0.000 0.000 0.003 0.002 re.py:202(sub)
2 0.002 0.001 0.002 0.001 {method 'sub' of 're.Pattern' objects}
2 0.000 0.000 0.001 0.001 re.py:288(_compile)
1 0.000 0.000 0.001 0.001 sre_compile.py:783(compile)
1 0.000 0.000 0.001 0.001 sre_compile.py:622(_code)
2 0.001 0.001 0.001 0.001 sre_compile.py:292(_optimize_charset)
2 0.000 0.000 0.001 0.000 main.py:4(read_file)
2 0.000 0.000 0.001 0.000 {method 'read' of '_io.TextIOWrapper' objects}
1 0.000 0.000 0.001 0.001 sre_compile.py:560(_compile_info)
1 0.000 0.000 0.001 0.001 sre_compile.py:87(_compile)
3 0.001 0.000 0.001 0.000 {built-in method builtins.print}
通过性能分析发现,程序主要耗时集中在create_ngram函数中的字符串切片和集合插入操作。因此对该函数进行了局部优化,减少循环中的重复计算,并将集合方法引用提前保存。由于测试规模有限,优化前后整体时间差异较小,但在较大文本规模下可以降低函数调用开销。
2. 性能优化思路
针对 create_ngram() 函数中的循环效率问题,对计算模块进行了优化。
优化前代码主要流程:
遍历不同长度的 N-gram;
在循环中重复计算文本长度;
通过集合 add() 方法保存生成的文本片段。
优化前:
for n in [2,3]:
for i in range(len(text)-n+1):
result.add(text[i:i+n])
优化后:
def create_ngram(text):
result = set()
add = result.add
length = len(text)
for i in range(length-1):
add(text[i:i+2])
for i in range(length-2):
add(text[i:i+3])
return result
优化内容:
提前保存文本长度,减少循环过程中的重复计算;
将不同长度的 N-gram 分开处理,减少循环内部判断;
降低循环中的额外计算开销。
同时,对集合操作进行了局部优化:add = result.add
将集合添加方法保存为局部变量,减少对象属性查找开销。
3. 性能优化时间记录
| 阶段 | 预计时间 | 实际时间 |
|---|---|---|
| 性能分析 | 15 分钟 | 20 分钟 |
| 定位瓶颈函数 | 15 分钟 | 15 分钟 |
| 优化 create_ngram 函数 | 20 分钟 | 15 分钟 |
| 重新测试 | 20 分钟 | 15 分钟 |
五.计算模块部分单元测试展示
1. 单元测试设计
为了验证论文查重计算模块的正确性,使用 Python 自带的 unittest 框架对程序中的核心函数进行了单元测试。
测试主要针对以下几个模块:
| 测试函数 | 测试目的 |
|---|---|
| clean_text() | 验证文本预处理功能是否正确 |
| create_ngram() | 验证文本特征提取是否符合预期 |
| calculate_similarity() | 验证重复率计算结果是否正确 |
| main() | 验证完整文件输入输出流程 |
测试文件:test_main.py
通过构造不同类型的测试数据,验证程序对于正常情况、边界情况以及异常输入的处理能力。
2. 部分单元测试代码展示
(1)文本清洗测试
测试函数:clean_text()
测试目的:验证程序是否能够正确删除标点符号,只保留有效文本内容。
测试代码:
def test_clean_text(self):
text = "今天是星期天,天气晴!"
result = clean_text(text)
self.assertEqual(
result,
"今天是星期天天气晴"
)
测试数据设计:选择包含中文、标点符号的文本作为输入,模拟论文中常见的标点差异情况。
(2)N-gram特征生成测试
测试函数:create_ngram()
测试目的:验证文本是否能够正确转换为字符特征集合。
测试代码:
def test_ngram_length(self):
对字符串生成ngram集合
result = create_ngram(
"abcdef"
)
判断生成结果不为空
self.assertGreater(
len(result),
0
)
测试数据设计:选择较短文本,方便人工计算预期结果,确认生成的N-gram集合包含正确的文本片段。
(3)重复率计算测试
测试函数:calculate_similarity()
测试目的:验证不同文本相似程度下,程序是否能够返回正确结果。
测试代码:
def test_same_text(self):
text1 = "机器人控制系统"
text2 = "机器人控制系统"
result = calculate_similarity(
text1,
text2
)
self.assertEqual(
result,
1.0
)
测试数据设计:使用完全相同的文本作为输入,理论重复率应为100%,用于验证算法基本正确性。
(4)不同文本测试
测试代码:
def test_different_text(self):
text1 = "机器人控制"
text2 = "天气情况"
result = calculate_similarity(
text1,
text2
)
self.assertEqual(
result,
0
)
测试数据设计:构造完全不同的文本,验证算法能够识别无重复内容。
(5)主程序流程测试
测试函数:main()
测试目的:验证从命令行读取文件路径,到最终生成答案文件的完整流程。
测试代码:
def test_main_run(self):
sys.argv = [
"main.py",
"test/orig.txt",
"test/orig_add.txt",
"test/result.txt"
]
main()
with open(
"test/result.txt",
encoding="utf-8"
) as f:
result = f.read()
self.assertTrue(
len(result) > 0
)
测试数据设计:使用实际论文文件作为输入,模拟用户真实运行程序的过程。
3. 测试数据构造思路
为了保证程序能够正确处理不同情况,测试数据主要分为以下几类:
类型 设计目的
完全相同文本 验证最高重复率情况
完全不同文本 验证无重复情况
部分修改文本 模拟论文增删改后的情况
包含标点文本 验证文本清洗功能
短文本 验证边界情况
空文本 验证异常输入处理
真实论文文件 验证完整程序流程
通过不同类型的数据组合,可以覆盖程序中的主要逻辑分支。
4. 测试覆盖率结果
使用 coverage 工具统计测试覆盖情况。
执行:coverage run --branch -m unittest test_main.py
查看覆盖率:coverage report
测试结果:
覆盖率截图如下:

测试结果表明:
核心计算模块覆盖率达到96%;
主要函数均被测试覆盖;
程序能够正确处理不同输入情况下的论文重复率计算。
六.计算模块部分异常处理说明
为了提高程序的稳定性,在设计论文查重计算模块时,对可能出现的异常情况进行了分析,并增加相应的处理机制。
主要考虑以下异常场景:
命令行参数数量错误;
输入文件不存在;
输入文件为空;
抄袭文本为空;
输出文件写入异常。
通过异常处理机制,可以避免程序因输入错误直接崩溃,提高程序的健壮性。
1. 命令行参数数量异常
异常设计目标
程序通过命令行参数获取:
原文路径;
抄袭版论文路径;
输出文件路径。
如果用户没有按照要求输入三个参数,程序无法正常运行。因此需要检测参数数量,并给出提示信息。
错误场景
用户输入:
python main.py
缺少文件路径参数。
异常处理代码
if len(sys.argv) != 4:
print(
"Usage: python main.py 原文路径 抄袭路径 输出路径"
)
return
单元测试样例
def test_wrong_argument(self):
import sys
sys.argv = [
"main.py"
]
main()
测试目标:模拟用户未输入完整参数的情况,验证程序能够正常提示并退出,而不是异常崩溃。
2. 文件不存在异常
异常设计目标:用户输入的文件路径可能错误,例如:
文件名拼写错误;
文件移动位置改变;
输入不存在路径。
如果直接读取,会产生 FileNotFoundError。因此需要捕获文件读取异常。
错误场景
D:\test\none.txt
文件不存在。
异常处理代码
try:
with open(
path,
"r",
encoding="utf-8"
) as f:
text = f.read()
except FileNotFoundError:
print("文件不存在")
return ""
单元测试样例
def test_file_not_exist(self):
result = read_file(
"not_exist.txt"
)
self.assertEqual(
result,
""
)
测试目标:验证程序遇到错误路径时能够返回空结果并继续执行,而不是直接退出。
3. 空文本异常
异常设计目标:论文文件可能为空。
如果直接进行 N-gram 生成:text[i:i+n]
可能导致无意义计算。
因此需要对空文本进行判断。
错误场景
输入文件内容:
(空文件)
异常处理代码
if len(text) == 0:
return set()
单元测试样例
def test_empty_text(self):
result = create_ngram("")
self.assertEqual(
len(result),
0
)
测试目标:验证空字符串输入时,程序能够正常返回空集合。
4. 抄袭文本为空异常
异常设计目标:当抄袭文本为空时:会导致除零错误。
因此需要提前判断。
错误场景
输入:(空)
原文:
机器人控制系统
抄袭版:
(空)
异常处理代码
if len(copy_ngram)==0:
return 0
单元测试样例
def test_empty_copy(self):
result = calculate_similarity(
"机器人控制系统",
""
)
self.assertEqual(
result,
0
)
测试目标:验证空抄袭文本情况下,程序不会出现除零异常。
5. 输出文件异常
异常设计目标:程序最后需要将计算结果写入指定文件。如果输出路径不可写,需要避免程序异常退出。
错误场景
输出路径不存在;
文件权限不足。
异常处理方式
使用异常捕获:
try:
with open(
output_path,
"w",
encoding="utf-8"
) as f:
f.write(result)
except Exception:
print("输出文件失败")
通过以上异常设计,程序能够处理常见错误输入情况,提高论文查重系统的稳定性和可靠性。同时,所有异常情况均通过单元测试进行了验证,保证程序在异常条件下仍能正常运行。
七.任务结束PSP表格
| PSP2.1 | 实际耗时(分钟) |
|---|---|
| Development | |
| · Analysis(需求分析,包括学习新技术) | 10 |
| · Design Spec(生成设计文档) | 20 |
| · Design Review(设计复审) | 15 |
| · Coding Standard(代码规范) | 10 |
| · Design(具体设计) | 30 |
| · Coding(具体编码) | 150 |
| · Code Review(代码复审) | 20 |
| · Test(自我测试,修改代码,提交修改) | 100 |
| Reporting | |
| · Test Report(测试报告) | 40 |
| · Size Measurement(计算工作量) | 15 |
| · Postmortem & Process Improvement Plan | 35 |

浙公网安备 33010602011771号