treec34

导航

 
这个作业属于哪个课程 计科24级6班
这个作业要求在哪里 第一次个人编程作业:论文查重
这个作业的目标 独立完成小型软件开发全流程,掌握算法设计、性能分析(cProfile)、Python单元测试(unittest)及异常处理。

我的 GitHub 仓库地址【3124004146】

一、PSP 2.1 表格

在正式开始编码前,我对各模块的开发时间进行了预估。得益于 Python 强大的内置库和简练的语法,实际在“具体编码”环节节省了大量时间,主要的精力花费在了测试用例的构思和性能分析上。

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 20
· Estimate · 估计这个任务需要多少时间 20 20
Development 开发
· Analysis · 需求分析 (包括学习新技术) 40 30
· Design Spec · 生成设计文档 20 20
· Design Review · 设计复审 10 10
· Coding Standard · 代码规范 10 10
· Design · 具体设计 30 30
· Coding · 具体编码 90 50
· Code Review · 代码复审 20 15
· Test · 测试(单元测试、覆盖率) 60 80
Reporting 报告
· Test Report · 测试报告 30 40
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 25
合计 360 340

二、计算模块接口的设计与实现过程

1. 算法思路

针对中文文本的查重,相比于单纯计算字频,基于“词语”维度的比对更加精确。本项目采用 结巴分词 (jieba) + TF(词频统计) + 余弦相似度 (Cosine Similarity) 的算法模型。
核心思想:将整篇论文拆解为一个包含词汇与词频的高维空间向量。两篇论文的相似度,即转化为两个多维向量在空间中的夹角余弦值。夹角越小(余弦值越接近1),重复率越高。

2. 代码组织与函数设计

代码遵循“单一职责”原则,划分为独立的函数模块:

  • read_file(file_path):安全读取文本,内置 utf-8 编码处理,避免乱码。
  • get_tf_vector(text):特征提取核心。独到之处在于使用 jieba.cut 获取分词结果,再结合 Python 标准库的 collections.Counter,只需两行代码即可完美完成词频哈希表的构建。
  • cosine_similarity(vec1, vec2):数学计算模块,找出两个词典的并集,利用公式计算并返回浮点数。

流程图如下:
命令行参数输入 -> 检查参数合法性 -> read_file 读取两文件 -> get_tf_vector 提取特征向量 -> cosine_similarity 计算余弦相似度 -> 结果格式化输出至答案文件 -> 结束。


三、计算模块接口部分的性能改进

完成代码后,我使用 Python 内置的 cProfile 性能分析工具对程序进行了探测。在终端运行命令:python -m cProfile -s time main.py orig.txt copy.txt ans.txt

image

性能瓶颈与改进思路:
从 cProfile 的输出报告中可以看到,耗时最大的函数毫无悬念地落在了 jieba.cut 内部的正则表达式匹配和词典加载阶段。
由于 jieba 属于第三方强依赖库,底层优化空间较小。但在自身代码层面,我做了如下优化以压榨性能:

  1. 摒弃传统 for 循环嵌套:在计算向量点积和模长时,全面改用 Python 的生成器表达式 (Generator Expression),如 sum(vec1.get(w, 0) * vec2.get(w, 0) for w in words)。这种做法不仅代码优雅,而且避免了中间列表的内存开销,对于动辄上万词的长篇论文解析十分友好。
  2. 一次性读写内存:避免了多次 I/O 操作,文件内容一次性加载入内存处理,显著降低了磁盘读写开销。

四、计算模块部分单元测试展示

我使用 Python 自带的 unittest 库编写了测试类。针对核心的相似度计算与文件读取逻辑,构造了多种边界数据。

单元测试核心代码展示:

import unittest
from main import get_tf_vector, cosine_similarity, read_file

class TestPaperCheck(unittest.TestCase):
    
    def test_identical_text(self):
        # 测试:完全相同的文本,期望值为 1.0
        txt1 = get_tf_vector("软件工程是一门实践性很强的学科")
        txt2 = get_tf_vector("软件工程是一门实践性很强的学科")
        sim = cosine_similarity(txt1, txt2)
        self.assertAlmostEqual(sim, 1.0, places=2)

    def test_totally_different(self):
        # 测试:完全不相关的文本,期望值为 0.0
        txt1 = get_tf_vector("今天天气不错")
        txt2 = get_tf_vector("我喜欢吃苹果")
        sim = cosine_similarity(txt1, txt2)
        self.assertEqual(sim, 0.0)

    def test_partial_overlap(self):
        # 测试:部分抄袭修改,期望值在 (0, 1) 之间
        txt1 = get_tf_vector("今天是星期天,天气晴,今天晚上我要去看电影。")
        txt2 = get_tf_vector("今天是周天,天气晴朗,我晚上要去看电影。")
        sim = cosine_similarity(txt1, txt2)
        self.assertTrue(0.5 < sim < 0.99)

image


五、计算模块部分异常处理说明

在一个规范的工程项目中,异常处理机制必不可少。本项目设计了 3 种主要的异常处理机制:

1. 命令行参数缺失 (IndexError 级拦截)

  • 设计目标:防止用户少传或不传文件路径参数,导致后续取参时数组越界。
  • 对应场景:用户直接双击 main.py 或仅输入 python main.py orig.txt
  • 应对方案:在程序入口检查 len(sys.argv) != 4,若不满足则打印用法提示并正常退出(sys.exit(1)),避免向用户抛出代码 Traceback。

2. 文件不存在异常 (FileNotFoundError)

  • 设计目标:防止程序试图读取不存在的文件或无权限访问的路径。
  • 对应场景:传入了错误的路径,或者原文件已被删除。
  • 测试用例与表现
    test_main.py 中构造测试:调用 read_file("not_exist.txt")
    实际运行时,终端会捕获并友好提示:错误:找不到文件 not_exist.txt,随后安全退出,不会抛出红色的报错代码。

3. 空文件除零异常 (ValueError 与防御性编码)

  • 设计目标:计算空文本的余弦相似度会导致分母为 0,程序会直接崩溃。
  • 对应场景:提供的比对文档或抄袭文档内容全为空格,或大小为 0 KB。
  • 应对方案
    read_file 阶段拦截:if not text.strip(): raise ValueError(...)
    同时在 cosine_similarity 核心计算函数末尾加入防御性代码:if mag1 * mag2 == 0: return 0.0,彻底杜绝了由于零向量导致除零崩溃的隐患。
posted on 2026-09-12 15:32  treec34  阅读(14)  评论(0)    收藏  举报