Github链接 https://github.com/GTzx/312100580
| 软件工程 | 课程主页 |
|---|---|
| 作业要求 | 作业要求 |
| 作业目标 | 设计一个论文查重算法 |
PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 15 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
| Development | 开发 | 120 | 150 |
| · Analysis | · 需求分析 (包括学习新技术) | 20 | 30 |
| · Design Spec | · 生成设计文档 | 10 | 10 |
| · Design Review | · 设计复审 | 10 | 10 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | · 具体设计 | 15 | 15 |
| · Coding | · 具体编码 | 60 | 60 |
| · Code Review | · 代码复审 | 10 | 10 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 60 | 80 |
| Reporting | 报告 | 30 | 30 |
| · Test Repor | · 测试报告 | 30 | 50 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 15 |
| · 合计 | 460 | 525 |
需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
- 原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
- 抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
模块设计
在该项目中,设计了三个函数,分别是读取文本函数read_file(),预处理函数preprocess(),计算重复率的函数 get_plagiarism_percentage() 原文文本与抄袭文本依次通过这些函数,最终得到重复率。
文本读取模块
def read_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()
except FileNotFoundError:
print(f"文件不存在:{file_path}")
return FileNotFoundError
预处理模块
def preprocess(text):
# 去除标点符号和转换为小写字母
text = text.translate(str.maketrans('', '', string.punctuation)).lower().replace(' ', '')
return text
计算重复率模块
def get_plagiarism_percentage(original_text, copied_text):
# 创建Sequencematcher对象
matcher = difflib.SequenceMatcher(None, original_text, copied_text)
# 获取匹配块
matching_blocks = matcher.get_matching_blocks()
# 计算总匹配字符数
total_matching_chars = sum(block.size for block in matching_blocks)
# 计算重复率
plagiarism_percentage = total_matching_chars / max(len(original_text),len(copied_text))
return plagiarism_percentage
关键算法
此程序中最关键的算法是计算重复率,通过使用difflib.Sequencematcher来创建Sequencematcher对象,然后获取两个文本中的文本匹配块,进而计算出总匹配字数,再通过重复率=总匹配字数/原文文件或抄袭文件的最大字数计算重复率。
关键函数 get_plagiarism_percentage() 的流程图

性能分析


整个程序运行时间为3ms,运行时间较为快速,在整个运行时间中,difflib.py占了33.3% 的运行时间,因为其是整个程序主要的算法。一开始不处理文本直接计算会导致文本查重率降低和运行时间增大,在加入预处理模块后可以解决这个问题。
单元测试
读取文件测试
def test_read_file(self):
# 创建一个临时测试文件,并写入一些文本
temp_file = 'temp_test_file.txt'
with open(temp_file, 'w', encoding='utf-8') as file:
file.write('This is a test file.')
# 测试文件读取功能
self.assertEqual(read_file(temp_file), 'This is a test file.')
# 清理临时文件
os.remove(temp_file)
测试函数test_read_file()用来测试是否能够正确读取文件里的文本;通过创建一个临时的测试文件,通过读取出的文本与写入的文本进行比较,若一致则该功能正常。
文本预处理测试
def test_preprocess(self):
text = "Hello, world! This is a test."
preprocessed_text = preprocess(text)
self.assertEqual(preprocessed_text, "helloworldthisisatest")
测试函数test_preprocess()用来测试改功能能否正常对文本进行预处理,例如去除标点符号和空格,转换为小写字母。
计算重复率测试
def test_get_plagiarism_percentage_same(self):
original_text = "Hello, world!"
copied_text = "Hello, world!"
self.assertEqual(get_plagiarism_percentage(preprocess(original_text),preprocess(copied_text)), 1)
该测试函数只是测试两个文本完全一致的情况(其余情况未展示),通过几组测试用例可判断此计算重复率的函数的功能正常。
单元测试的测试覆盖率
| Module | statements | missing | excluded | coverage |
|---|---|---|---|---|
| test_main.py | 42 | 1 | 0 | 98% |
单元测试的覆盖率达到 98%,所有测试都通过。
异常处理
测试文件不存在的情况
def test_read_none_file(self):
self.assertEqual(read_file('invalid_file.txt'), FileNotFoundError)
当遇到读取文件时出现文件不存在的情况返回 FileNotFoundError。
测试其中一个文本为空的情况
def test_get_plagiarism_percentage_empty_text(self):
original_text = ""
copied_text = "This is a test."
self.assertEqual(get_plagiarism_percentage(preprocess(original_text), preprocess(copied_text)), 0)
如果两个文本中有其中一个存在文本为空的情况,则返回重复率为0的结果。
查重结果
原文件orig.txt与其余抄袭文件的重复率为
| 抄袭文件 | 重复率 |
|---|---|
| orig_0.8_add.txt | 84% |
| orig_0.8_del.txt | 79.86% |
| orig_0.8_dis_1.txt | 96.67% |
| orig_0.8_dis_10.txt | 80.92% |
| orig_0.8_dis_15.txt | 60.05% |