第一次个人编程作业

个人项目

一、作业概述

课程 软件工程
作业要求 完成论文查重算法
作业目标 设计一个论文查重算法,给出原文和抄袭文件的相似度
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()

通过以上的异常处理,我们可以让程序在遇到异常时给出更友好的错误信息,并且避免程序因为未处理的异常而崩溃。

posted @ 2023-09-17 23:52  陈勇佳  阅读(58)  评论(0)    收藏  举报