第一次个人编程作业

第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477
这个作业的目标 实现一个3000字以上论文查重程序
github连接: https://github.com/LancyPluto/3123004789

一、psp表格如下

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

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

2.1 程序整体结构

2.1.1 核心模块

模块名称 主要函数 功能职责
文件读写模块 read_text_file(path) 负责读取输入文件,返回字符串
文本预处理模块 clean_text(text) 清洗文本:去掉多余符号,统一空格
分词模块 tokenize(text) 中文用 jieba 分词,英文或含空格文本直接用空格分割。
如果 jieba 不可用,退化为单字切分
算法核心模块 lcs_length(a, b) cosine_similarity(a_tokens, b_tokens) compute_duplicate_rate(orig, copy) LCS 算法:用动态规划求最长公共子序列长度,衡量顺序相似度(慢)。
-余弦相似度算法:基于词频向量计算整体相似度,效率高,适合长文本。<br/- compute_duplicate_rate:主接口,负责调用清理、分词、相似度计算,并输出重复率结果。
主流程模块 main() 命令行参数处理和程序入口

2.1.2 函数关系框架图

image

2.2 算法核心

2.2.1 核心思想

论文改写一般不是每句话都完全一致,而是部分修改。如果直接使用字符串匹配,则有些稍微更改的地方就会查重失败。因此本程序使用最长公共子序列(LCS)算法来计算相似度。

2.2 算法流程图

image

三、计算模块性能优化

3.1 改进前存在的问题

​ 在最初版本的算法中,最长公共子序列(LCS)计算模块采用了标准二维动态规划实现,时间复杂度为 O(n×m),空间复杂度也为 O(n×m)。当文本长度较长(如数千词以上)时,DP 表规模非常大,运行时间过长且内存消耗明显,无法适应大规模文本比对需求。

3.2 改进思路

  • 空间优化:

    • 原二位DP表改为滚动数组,只保留上一行和当前行。
    • 空间复杂度从O(nm) 降为 O(min⁡(n,m)),空间开销减少明显。
  • 时间优化:

    • 预处理阶段 合并清洗与分词操作,减少正则替换和多次字符串扫描的开销。

    • 引入可替代算法:

      • 小规模文本可继续使用 LCS(保证顺序敏感度)。
      • 大规模文本切换为 余弦相似度Jaccard 相似度,复杂度约 O(n+m),比 O(n×m) 大幅提升。
    • 通过这种“按规模选择算法”的方式,在不同文本规模下取得 精度与性能的平衡

image

性能分析图:

![image](https://img2024.cnblogs.com/blog/3699839/202509/3699839-20250921123557932-795119469.png)

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

测试代码:

image

测试覆盖率:

image

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

1.FileNotFoundError

当用户提供的文件路径不存在时,应该抛出 FileNotFoundError,而不是让程序在打开文件时直接崩溃。这样用户能明确知道是 文件路径错误

单元测试样例

import pytest
from plagiarism_checker import read_text_file

def test_file_not_found():
    with pytest.raises(FileNotFoundError):
        read_text_file("C:/Lancy/.txt")

错误场景

  • 用户输入了一个不存在的路径,例如 C:/Lancy/.txt
  • 或者文件被删除、移动,导致读取失败

2.RuntimeError — 分词器加载失败

设计目标

jieba 中文分词库在某些情况下可能初始化失败(例如缺少词典文件)。
为了避免程序静默失败,我们在 tokenize() 中增加检查,如果 jieba 初始化失败则抛出 RuntimeError

单元测试样例

import pytest
from plagiarism_checker import tokenize

def test_tokenizer_fail(monkeypatch):
    # 模拟 jieba 分词器不可用
    monkeypatch.setattr("plagiarism_checker.USE_JIEBA", False)
    text = "今天天气好"
    tokens = tokenize(text)
    # 即使不用 jieba,tokenize 也应返回非空结果
    assert len(tokens) > 0

(这个例子用 monkeypatch 模拟了失败场景,在真实环境中可能需要更复杂的异常触发)

错误场景

  • jieba 词典缺失或损坏
  • 分词器未正确初始化

3.极短文本(过滤后<10词)
设计目标
避免短文本因少量词重合虚高,返回0.05-0.1相似度。
错误场景
上传单句文本、摘要片段。

import unittest
from similarity_calculator import calculate_plagiarism_similarity
import jieba
from word_filter import filter_stop_words

class TestShortText(unittest.TestCase):
    def test_short_input(self):
        short1 = filter_stop_words(jieba.lcut("人工智能助力教育发展"))
        short2 = filter_stop_words(jieba.lcut("人工智能推动教育进步"))
        
        sim = calculate_plagiarism_similarity(short1, short2)
        self.assertTrue(0.05 <= sim <= 0.1)

if __name__ == "__main__":
    unittest.main()
posted @ 2025-09-21 12:36  林夜  阅读(52)  评论(0)    收藏  举报