这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13468 |
这个作业的目标 | 设计编写并测试一个论文查重程序,体会软件开发的全流程。 |
作业仓库
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 320 | 370 |
· Analysis | · 需求分析(包括学习新技术) | 100 | 80 |
· Design Spec | · 生成技术文档 | 30 | 20 |
· Design Review | · 设计复审 | 30 | 20 |
· Coding Standard | · 代码规范(为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 20 | 20 |
· Coding | · 具体编码 | 120 | 130 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交代码) | 30 | 60 |
Reporting | 报告 | 50 | 60 |
· Test Repor | · 测试报告 | 30 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 10 | 20 |
合计 | 450 | 450 |
作业要求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。注意:答案文件中输出的答案为浮点型,精确到小数点后两位
计算模块接口的设计与实现过程
程序流程图
主要函数及调用关系
函数名 | 作用 |
---|---|
text_process | 进行文本预处理。去除输入文本中的标点、空格,并对其分词 |
consine_similarity | 计算文本余弦相似度 |
jaccard_simila | 计算文本 jaccard 相似度 |
levenshtein_similarity | 计算文本 levenshtein 相似度 |
main | 实现程序主要逻辑 |
算法关键
文本预处理
· 将英文统一转换为小写,减少大小写差异带来的影响。
· 使用正则表达式移除标点符号。
· 使用 jieba 进行中文分词。
· 使用 filter 和 strip() 除去分词后产生的空格。
文本相似度计算
· 使用三种方法计算本文相似度,最终的综合相似度为三种相似度的平均值。
计算模块接口部分的性能改进
· 使用库 line_profiler 测试代码性能
可以发现整个程序中耗时最长的函数是处理文本函数。
在该函数中,分词操作消耗的时间最长。
在网络上查询得知可以通过预加载的方式缩短运行时间,但实际测试中并无效果。
计算模块部分单元测试展示
本程序使用 pytest 进行测试。
import pytest
import main
import math
class TestTextProcess:
# 测试文本处理
def test_text_process_normal(self):
# 正常文本
text = "今天天气真好!Hello World!"
result = main.text_process(text)
assert isinstance(result,list)
def test_text_process_empty(self):
# 空文本
text = ""
result = main.text_process(text)
assert result == []
def test_text_process_only_punctuation(self):
# 只有标点符号的文本
text = "!@#$%^&*()"
result = main.text_process(text)
assert result == []
def test_text_process_mixed_case(self):
# 混合大小写的英文
text = "Hello WORLD Python"
result = main.text_process(text)
print(result)
assert result == ['hello', 'world', 'python']
class TestCosineSimilarity:
# 测试余弦相似度函数
def test_cosine_similarity_identical(self):
# 完全相同文本
words1 = ['今天', '天气', '真好']
words2 = ['今天', '天气', '真好']
result = main.cosine_similarity(words1, words2)
assert abs(result - 1.0) < 0.0001
def test_cosine_similarity_different(self):
# 完全不同文本
words1 = ['苹果', '香蕉']
words2 = ['汽车', '房子']
result = main.cosine_similarity(words1, words2)
assert abs(result - 0.0) < 0.0001
def test_cosine_similarity_partial(self):
# 部分相似文本
words1 = ['苹果', '香蕉', '橙子']
words2 = ['苹果', '梨', '葡萄']
result = main.cosine_similarity(words1, words2)
assert 0 < result < 1
def test_cosine_similarity_empty_first(self):
# 第一个向量为空"
words1 = []
words2 = ['苹果', '香蕉']
result = main.cosine_similarity(words1, words2)
assert result == 0
def test_cosine_similarity_empty_second(self):
# 第二个向量为空
words1 = ['苹果', '香蕉']
words2 = []
result = main.cosine_similarity(words1, words2)
assert result == 0
def test_cosine_similarity_both_empty(self):
# 两个向量都为空
words1 = []
words2 = []
result = main.cosine_similarity(words1, words2)
assert result == 0
class TestJaccardSimilarity:
# 测试Jaccard相似度函数
def test_jaccard_similarity_identical(self):
# 完全相同文本
words1 = ['今天', '天气', '真好']
words2 = ['今天', '天气', '真好']
result = main.jaccard_similarity(words1, words2)
assert abs(result - 1.0) < 0.0001
def test_jaccard_similarity_different(self):
# 完全不同文本
words1 = ['苹果', '香蕉']
words2 = ['汽车', '房子']
result = main.jaccard_similarity(words1, words2)
assert abs(result - 0.0) < 0.0001
def test_jaccard_similarity_partial(self):
# 部分相似文本
words1 = ['苹果', '香蕉', '橙子']
words2 = ['苹果', '梨', '葡萄']
result = main.jaccard_similarity(words1, words2)
assert abs(result - 0.2) < 0.0001
def test_jaccard_similarity_empty_first(self):
# 第一个集合为空
words1 = []
words2 = ['苹果', '香蕉']
result = main.jaccard_similarity(words1, words2)
assert result == 0
def test_jaccard_similarity_empty_second(self):
# 第二个集合为空
words1 = ['苹果', '香蕉']
words2 = []
result = main.jaccard_similarity(words1, words2)
assert result == 0
def test_jaccard_similarity_both_empty(self):
# 两个集合都为空
words1 = []
words2 = []
result = main.jaccard_similarity(words1, words2)
assert result == 0
class TestLevenshteinSimilarity:
# 测试Levenshtein相似度函数
def test_levenshtein_similarity_identical(self):
# 完全相同文本
words1 = ['今天', '天气', '真好']
words2 = ['今天', '天气', '真好']
result = main.levenshtein_similarity(words1, words2)
assert abs(result - 1.0) < 0.0001
def test_levenshtein_similarity_different(self):
# 完全不同文本
words1 = ['苹果', '香蕉']
words2 = ['汽车', '房子', '电脑']
result = main.levenshtein_similarity(words1, words2)
assert abs(result - 0.0) < 0.0001
def test_levenshtein_similarity_partial(self):
# 部分相似文本
words1 = ['苹果', '香蕉', '橙子']
words2 = ['苹果', '梨', '葡萄']
result = main.levenshtein_similarity(words1, words2)
assert 0 < result < 1
def test_levenshtein_similarity_empty_first(self):
# 第一个序列为空
words1 = []
words2 = ['苹果', '香蕉']
result = main.levenshtein_similarity(words1, words2)
assert result == 0
def test_levenshtein_similarity_empty_second(self):
# 第二个序列为空
words1 = ['苹果', '香蕉']
words2 = []
result = main.levenshtein_similarity(words1, words2)
assert result == 0
def test_levenshtein_similarity_both_empty(self):
# 两个序列都为空
words1 = []
words2 = []
result = main.levenshtein_similarity(words1, words2)
assert result == 1.0
if __name__ == "__main__":
pytest.main([__file__, "-v", "--cov=main", "--cov-report=term-missing"])
测试结果
计算模块部分异常处理说明
· 用户输入不正确时
if len(sys.argv) != 4:
print("错误:请传入三个文件路径!")
print("使用格式:python main.py 原文件路径 对比文件路径 答案文件路径")
sys.exit(1)
· 打开文件错误时
for file_path in [originalPath, addPath]:
if not os.path.exists(file_path):
print(f"错误:文件 '{file_path}' 不存在!")
sys.exit(1)
if not os.path.isfile(file_path):
print(f"错误:'{file_path}' 不是有效的文件!")
sys.exit(1)
# 打开文件
try:
with open(originalPath, 'r', encoding='utf8') as f:
originalText = f.read()
with open(addPath, 'r', encoding='utf8') as f:
addText = f.read()
except FileNotFoundError as e:
print(f"文件未找到错误:{e}")
sys.exit(1)
except PermissionError as e:
print(f"文件权限错误:{e}")
sys.exit(1)
except UnicodeDecodeError as e:
print(f"文件编码错误:{e}")
sys.exit(1)
except Exception as e:
print(f"读取文件时发生未知错误:{e}")
sys.exit(1)
· 输入文本其一或其二为空时
if not originalTextList and not addTextList:
print("输入文本均为空,相似度为1。")
output = "输入文本均为空,相似度为1。"
cosineSimilarity = jaccardSimilarity = levenshteinSimilarity = avgSimilarity = 1.0
elif not originalTextList or not addTextList:
print("输入文本之一为空,相似度为0。")
output = "输入文本之一为空,相似度为0。"
cosineSimilarity = jaccardSimilarity = levenshteinSimilarity = avgSimilarity = 0.0