论文查重

论文查重

作业GitHub链接:[https://github.com/LAYG13/LAYG13/tree/main/3124004050]

一、PSP 表格(预估)

在开始实现程序之前,我对各个开发阶段的时间做了如下估计:

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
· Planning 计划 30
· Estimate 估计这个任务需要多少时间 30
· Development 开发 60
· Analysis 需求分析(包括学习新技术) 60
· Design Spec 生成设计文档 45
· Design Review 设计复审 25
· Coding Standard 代码规范(为目前的开发制定合适的规范) 25
· Design 具体设计 60
· Coding 具体编码 100
· Code Review 代码复审 30
· Test 测试(自我测试,修改代码,提交修改) 45
· Reporting 报告 45
· Test Repor 测试报告 30
· Size Measurement 计算工作量 30
· Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 25
合计 620

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

1.总体设计:模块如何组织

代码组织:本项目按 "入口层与算法层分离" 的原则组织,共分为两个包和三个层次:

(1)类设计:共4个类,全部位于checker/exceptions.py,采用统一异常层次结构。基类PaperCheckerError继承自Exception,作为所有自定义异常的父类。其下派生3个子类:UsageError(命令行参数个数不为 3 时抛出)、FileReadError(输入文件不存在、无权限或编码无法识别时抛出)、FileWriteError(答案文件写入失败时抛出)。设计目的是让入口函数只需捕获一次基类即可统一处理所有错误,同时保留错误类型的区分度,便于定位问题。

(2)函数设计:共 9 个函数,按模块划分

  • main.py(4 个):parse_args(argv)校验命令行参数;read_text(path)读取文本,按UTF-8到GBK顺序回退解码;write_result(path, rate)将结果保留两位小数写入答案文件;main(argv)为程序主流程,串联全部步骤并负责异常捕获。
  • checker/preprocess.py(2个):clean_text(text)完成全角空格归一化与首尾空白去除;tokenize(text)调用jieba分词,过滤标点与空token、英文转小写,并通过LRU缓存提升重复分词性能。
  • checker/similarity.py(1个):cosine_similarity(words_a, words_b) 统计两侧词频构成向量,按余弦公式计算相似度,任一向量为空时返回0以避免除零。
  • tests/(其余为测试函数,31个用例分属4个测试文件)。

(3)函数间关系

屏幕截图 2026-09-13 174143

2.关键函数

  • clean_text(text)
    负责最基础的文本清洗,把全角空格 \u3000 替换成普通空格,再去掉首尾空白,让后续分词不会受这些无关字符干扰。

屏幕截图 2026-09-13 180734

  • tokenize(text)
    先调用 clean_text 清洗文本,再用 jieba.cut 分词,过滤掉空字符串和纯标点 token,英文统一转小写,并用 @lru_cache 缓存结果,避免同一段文本被反复分词。

屏幕截图 2026-09-13 180804

  • cosine_similarity(words_a, words_b)
    它接收两个参数:words_a 和 words_b,分别是原文和抄袭文经过 tokenize 处理后的词列表。通过公式cos(θ) = A·B / (|A|×|B|)计算余弦相似度,返回一个[0, 1]之间的浮点数,表示两段文本的相似程度:越接近1越相似,越接近0越不相关。如果任一词表为空就直接返回0.0,避免除零。

屏幕截图 2026-09-13 180708

3.算法关键:

(1)分词:选择jieba而非自建词典。 中文没有天然空格分隔,必须先分词jieba基于前缀词典和动态规划(DAG),对星期天、天气晴这类常见词切分准确,且是纯Python库,可随requirements.txt一键安装。预处理时统一过滤纯标点token(正则 ^[\W_]+$,Unicode模式下中文属于\w,不会误删),并把英文统一转小写,避免The/the这类大小写差异干扰结果。

(2)相似度:选择余弦相似度。 把论文表示为“词->词频”向量,重复率即两向量夹角的余弦值。相比编辑距离(O(n²))更适合长文本,相比 SimHash 更简单直观、便于解释。且天然带词频加权:高频重复词对相似度贡献更大,符合"查重"直觉。

(3)独到之处:

  • 多编码自动兼容:read_text先按二进制读取,再依次尝试utf-8-sig -> tf-8 -> gbk解码。Windows下作业文件可能是任一种编码,全部覆盖,避免"乱码导致重复率失真"。
  • 空文本语义明确:任一文本分词后为空,重复率定义为0.00(无内容可比对),避免除零,也符合评测预期。
  • 防御性异常体系:三类自定义异常统一被入口捕获,程序绝不产生未处理traceback崩溃,不会发生异常退出。
  • 分词LRU缓存:同一文本重复分词直接命中缓存。

4.代码质量分析

使用 pylint 对项目代码进行静态质量分析。初始评分为9.95/10,唯一警告为文件末尾缺少换行符。在文件末尾补上换行后重新分析,评分达到10.00/10,无任何警告。

屏幕截图 2026-09-14 230530


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

1.性能分析工具

作业要求使用VS2017 Profiling Tools/JProfiler,二者分别面向C++与Java。本项目选用Python3,故采用Python官方标准库cProfile+pstats进行等价分析,对程序的行为建模与热点定位能力一致。

分析样本:约一万字的长文本。

2.改进前的数据

屏幕截图 2026-09-14 212851

结论:有两个瓶颈:
(1)jieba.__cut_DAG累计 0.493s,是分词核心(不可避免的算法成本,线性复杂度)。
(2)marshal.load单次 0.392s,是每次启动时加载词典的固定开销,且启动时会向 stdout 打印三行日志。

3.改进思路

(1)日志抑制:模块加载时jieba.setLogLevel(logging.WARNING),去掉 3 行词典加载日志,减少I/O,stdout 干净。
(2)LRU 缓存:tokenize加@lru_cache(maxsize=128),同一文本重复分词直接命中缓存,从O(n)降为O(1)。

4.改进后数据对比:

屏幕截图 2026-09-14 213803

屏幕截图 2026-09-14 213139

改进说明:
改进后程序总耗时为0.537s,较改进前下降5.8%。由于分词本身是O(n)核心成本、无法消除,而词典加载是一次性固定成本,评测中单次运行无可避免。两处改进消除了可去除的开销,大样本整体运行 0.54s 左右。


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

1.测试框架与覆盖工具

  • 单元测试:pytest(31 个用例,全部通过)
  • 覆盖率:coverage.py,python -m coverage html生成可视化报告

2.测试用例设计思路

按照语句覆盖+分支覆盖+路径覆盖设计,对每个模块的关键分支逐一构造输入:

模块 用例数 覆盖的分支/路径
test_similarity.py 8 相同/不同/部分重合、词频加权、三个空列表分支
test_preprocess.py 6 全角空格、标点过滤、英文小写、空文本分支
test_main.py 12 正常流程 + 参数错误/文件缺失/编码错误/写失败 4 条异常路径
test_exceptions.py 5 异常继承层次与错误信息

关键测试代码(构造数据的思路:用可手工验证的数值断言精确值)

def test_partial_overlap_known_value():
    """部分重合:共同词占 2/3,验证数值精确性。"""
    assert cosine_similarity(["我", "爱", "编程"], ["我", "爱", "学习"]) == \
        pytest.approx(2.0 / 3.0, abs=1e-6)

def test_frequency_weight_affects_result():
    """词频权重:手工计算值 2/sqrt(10),验证加权正确性。"""
    value = cosine_similarity(["a", "a", "b"], ["a", "c"])
    assert value == pytest.approx(2.0 / math.sqrt(10.0), abs=1e-6)

def test_gbk_encoded_input_readable():
    """GBK 编码输入应被自动识别(分支:多编码尝试路径)。"""
    _write(orig, "天气很好,适合出游。", encoding="gbk")
    _write(plag, "天气很好,适合出游。", encoding="gbk")
    assert main_module.main([str(orig), str(plag), str(ans)]) == 0
    assert ans.read_text(encoding="utf-8") == "1.00"

白盒覆盖思路说明:不仅测"正确结果",还针对cosine_similarity的空列表守卫、read_text的编码回退循环、parse_args的参数个数判断、write_result的OSError捕获等每个分支设计用例,因此覆盖率能达到99%。

3.测试覆盖率截图

屏幕截图 2026-09-14 220645

说明:

  • checker三个算法模块100%覆盖;
  • main.py缺失的1行为if name == "main"入口保护行,属于解释器执行路径,单元测试无法(也无需)覆盖;

4.测试文本的测试结果

屏幕截图 2026-09-14 201639


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

程序定义了统一异常基类PaperCheckerError,派生三类异常,入口main()统一捕获:

异常 设计目标 触发场景 处理
UsageError 最早阶段拦截调用错误,避免对不完整参数做无意义的文件操作 命令行参数个数 ≠ 3 打印参数用法,退出码 1
FileReadError 区分"输入问题"与"算法问题",把 IO 失败统一成可预期的错误 文件不存在、无读取权限、三种编码均无法解码 自动尝试 utf-8-sig/utf-8/gbk 后仍失败则报错,退出码 1
FileWriteError 防止答案文件静默写失败(用户以为成功实际没写) 输出目录不存在、无写入权限 报错退出码 1

每个异常对应的单元测试样例:

# 场景1:UsageError —— 参数不足
def test_main_usage_error_exit_code(capsys):
    code = main_module.main(["a", "b"])          # 只传 2 个参数
    assert code == 1
    assert "参数" in capsys.readouterr().err     # stderr 有可读错误信息

# 场景2:FileReadError —— 文件不存在
def test_main_missing_input_file(tmp_path, capsys):
    missing = str(tmp_path / "not_exist.txt")
    code = main_module.main([missing, missing, str(tmp_path / "ans.txt")])
    assert code == 1
    assert "无法读取文件" in capsys.readouterr().err

# 场景3:FileReadError —— 编码无法识别
def test_read_text_invalid_encoding_raises(tmp_path):
    bad = tmp_path / "bad.txt"
    bad.write_bytes(b"\xff\xfe\x00\x81\x82\x83")   # 三种编码均非法
    with pytest.raises(FileReadError):
        main_module.read_text(str(bad))

# 场景4:FileWriteError —— 输出目录不存在
def test_main_write_error_exit_code(tmp_path, capsys):
    ans = str(tmp_path / "no_such_dir" / "ans.txt")
    code = main_module.main([str(orig), str(plag), ans])
    assert code == 1
    assert "无法写入" in capsys.readouterr().err

设计要点:所有异常都被main()捕获并写入stderr,程序以非零码退出,不会产生未处理的traceback崩溃。


六、PSP表格

完成项目后,各阶段实际耗时记录如下:

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
· Planning 计划 30 25
· Estimate 估计这个任务需要多少时间 30 20
· Development 开发 60 70
· Analysis 需求分析(包括学习新技术) 60 60
· Design Spec 生成设计文档 45 60
· Design Review 设计复审 25 20
· Coding Standard 代码规范(为目前的开发制定合适的规范) 25 20
· Design 具体设计 60 80
· Coding 具体编码 100 150
· Code Review 代码复审 30 20
· Test 测试(自我测试,修改代码,提交修改) 45 40
· Reporting 报告 45 60
· Test Repor 测试报告 30 30
· Size Measurement 计算工作量 30 20
· Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 25 30
合计 620 705

posted @ 2026-09-14 23:24  LAYG  阅读(11)  评论(0)    收藏  举报