第一次个人编程作业
个人项目
一、作业概述
| 课程 | 软件工程 |
|---|---|
| 作业要求 | 完成论文查重算法 |
| 作业目标 | 设计一个论文查重算法,给出原文和抄袭文件的相似度 |
| GitHub链接 | https://github.com/moon-mark/3121005292 |
二 、psp表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 20 |
| · Estimate | 估计这个任务需要多少时间 | 10 | 10 |
| Development | 开发 | 240 | 278 |
| · Analysis | 需求分析 (包括学习新技术) | 15 | 30 |
| · Design Spec | 生成设计文档 | 15 | 5 |
| · Design Review | 设计复审 | 5 | 5 |
| · Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
| · Design | 具体设计 | 10 | 18 |
| · Coding | 具体编码 | 40 | 30 |
| · Code Review | 代码复审 | 5 | 14 |
| · Test | 测试(自我测试,修改代码,提交修改) | 30 | 60 |
| Reporting | 报告 | 20 | 80 |
| · Test Report | 测试报告 | 15 | 20 |
| · Size Measurement | 计算工作量 | 5 | 35 |
| · Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 10 | 10 |
| 合计 | 445 | 615 |
三、需求分析
1. 功能性需求:
- 文件读取:程序需要能够读取两种类型的文件:原文文件和抄袭版论文文件。这些文件将作为命令行参数提供。
- 文本处理:程序需要能够处理中文文本,并能够正确地分词。
- 计算重复率:程序需要计算出原文和抄袭版论文之间的重复率。这可以通过比较两个文件中的共同词汇来实现。
- 文件写入:程序需要将计算出的重复率写入到一个新的文件中,该文件的路径也将作为命令行参数提供。
2. 非功能性需求: - 性能:程序需要能够快速地计算出重复率。在处理大型文件时,程序的运行时间应尽可能短。
- 可用性:程序应该容易使用。用户应能够通过简单地提供文件路径和命令行参数来运行程序。
- 可靠性:程序应该能够在各种情况下正确地计算出重复率。即使在处理有问题的文件或遇到其他异常情况时,程序也应该能够适当地处理。
以上就是对这个论文查重算法需求的分析。
四、设计思路
这个论文查重程序是由两个主要函数组成的。一个是calculate_similarity,它负责计算重复率。另一个是main函数,它处理文件路径的输入并调用calculate_similarity函数。
1. calculate_similarity函数
这个函数接受两个参数,即原文和抄袭论文的文件路径。它首先打开并读取这两个文件的内容。接着,使用jieba库进行中文分词,并将结果存储在集合中。然后,它计算这两个集合的交集,即原文和抄袭论文中都出现的词。最后,它返回重复率,即交集的大小除以原文词汇的大小。
流程如下:
接收两个文件路径参数 ->
读取原文和抄袭论文的内容->
对原文和抄袭论文进行分词,存储在两个集合中->
计算两个集合的交集->
计算并返回重复率
2. main函数
main函数处理输入和输出。它从命令行参数获取原文、抄袭论文和输出文件的路径,然后调用calculate_similarity函数计算重复率,并将结果写入到输出文件中。
流程如下:
获取命令行参数->
调用calculate_similarity函数计算重复率->
将重复率写入到输出文件中
这个程序的关键在于使用jieba库进行中文分词,并使用集合来存储词汇和计算交集。
这使得程序能够高效地计算重复率。
独到之处在于,这个程序通过命令行参数处理输入和输出,使得它可以非常方便地处理文件输入和输出。同时,它也可以轻松扩展以处理多个抄袭论文。
五、 性能分析图与性能改进方法


以下是性能最差部分代码
def calculate_similarity(orig_path, copy_path):
with open(orig_path, 'r', encoding='utf-8') as f:
orig_text = f.read()
with open(copy_path, 'r', encoding='utf-8') as f:
copy_text = f.read()
orig_words = set(jieba.cut(orig_text))
copy_words = set(jieba.cut(copy_text))
common_words = orig_words & copy_words
return len(common_words) / len(orig_words)
性能瓶颈在于文件读取和jieba分词的时间。以下是我对性能改进进行的一些思考和尝试:
1. 并行处理和分词
如果需要处理的文件非常大或者有很多文件需要处理,那么我们可以使用并行处理来加速。Python的multiprocessing库可以让我们利用多核CPU并行处理多个任务。我们可以将文件读取和分词的任务分开处理,即当一个文件正在被读取时,另一个文件可以进行分词。
2. 使用更快的分词库
虽然jieba是一个非常流行的中文分词库,但它可能不是最快的。如果性能是一个关键问题,我们可以考虑使用速度更快的分词库,如THULAC或者FastHan。
3. 使用高效的数据结构
在我们的程序中,我们使用集合来存储词语和计算交集。这是因为集合在Python中是基于哈希表实现的,所以它的查询和插入操作都非常快。但是,如果我们要处理的词汇数量非常大,那么集合可能会占用大量的内存。在这种情况下,我们可能需要使用更高效的数据结构,如Bloom Filter。
六、测试代码和结果
import unittest
import os
from main import calculate_similarity
class TestCalculateSimilarity(unittest.TestCase):
#设置原始文件路径,及其对应的测试文件路径
def setUp(self):
self.orig_path = 'orig.txt'
self.copy_paths = [
'orig_0.8_add.txt',
'orig_0.8_del.txt',
'orig_0.8_dis_1.txt',
'orig_0.8_dis_10.txt',
'orig_0.8_dis_15.txt'
]
def test_calculate_similarity(self):
for copy_path in self.copy_paths:
with self.subTest(copy_path=copy_path):
similarity = calculate_similarity(self.orig_path, copy_path)
print(f" {self.orig_path}对于 {copy_path}相似度为 {similarity:.2f}")
# 输出保留两位小数的相似度
if __name__ == '__main__':
unittest.main()
测试结果:

更改原始函数和测试函数后的结果2:

其它测试结果此处不再列出
覆盖率:

七、异常处理
在计算模块中,我们主要需要处理以下几种异常情况:
1. 文件不存在或无法打开
当我们试图打开一个不存在或者因为某些原因(比如权限问题)无法打开的文件时,Python会抛出一个FileNotFoundError或PermissionError。我们需要捕获这个异常,并给出适当的错误信息。
try:
with open(orig_path, 'r', encoding='utf-8') as f:
orig_text = f.read()
except FileNotFoundError:
print(f"无法找到文件:{orig_path}")
return
except PermissionError:
print(f"无法打开文件:{orig_path}")
return
2. 空文件
如果输入的原文或抄袭论文文件是空的,那么我们在计算重复率时可能会遇到除以零的错误。我们需要检查文件内容,如果文件是空的,可以直接返回0作为重复率,或者给出适当的错误信息。
if not orig_text or not copy_text:
print("原文或抄袭论文文件为空")
return 0
3. 命令行参数错误
如果用户没有提供足够的命令行参数,或者提供的参数不符合预期(例如,提供的不是一个有效的文件路径),那么我们需要给出适当的错误信息。我们可以通过argparse库来更好地处理命令行参数。
import argparse
parser = argparse.ArgumentParser(description="计算论文重复率")
parser.add_argument("orig_path", help="原文文件路径")
parser.add_argument("copy_path", help="抄袭论文文件路径")
parser.add_argument("output_path", help="输出文件路径")
args = parser.parse_args()
通过以上的异常处理,我们可以让程序在遇到异常时给出更友好的错误信息,并且避免程序因为未处理的异常而崩溃。

浙公网安备 33010602011771号