第二周

第一次个人编程作业——论文查重

GitHub 链接:https://github.com/t2506290-bot/paper-check-homework

一、PSP 表格

预估耗时

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

实际耗时

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

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

1. 代码组织

程序使用 Python3 实现,入口文件为 main.py,其中包含以下函数:

函数名 功能
read_file(path) 读取文件内容,忽略无法解码的字符
clean_text(text) 只保留中文、英文、数字,去掉标点、空格、换行
get_ngrams(text, n=2) 把文本切成连续的 n 个字符片段
calc_similarity(text1, text2) 计算两个文本的余弦相似度
main() 解析命令行参数,读取文件,计算相似度,写入答案文件

2. 函数之间关系

  • main() 负责整个流程控制。
  • main() 调用 read_file() 读取原文和抄袭版论文。
  • main() 调用 clean_text() 清洗文本。
  • main() 调用 calc_similarity() 计算相似度。
  • calc_similarity() 内部调用 get_ngrams() 把文本转成 2-gram。
  • 最后 main() 把结果写入答案文件,保留两位小数。

3. 算法关键

算法核心是字符 2-gram + 余弦相似度

步骤如下:

  1. 读取原文和抄袭版论文。
  2. 清洗文本,只保留中文、英文、数字。
  3. 把清洗后的文本切成连续的 2 个字符片段,例如:
    • 今天是星期天今天天是是星星期期天
  4. 统计两个文本的 2-gram 词频。
  5. 把词频看成向量,计算余弦相似度。
  6. 输出结果,保留两位小数。

4. 独到之处

  • 不依赖任何第三方库,只用 Python 标准库,运行环境简单。
  • 使用字符 2-gram,对中文短文本比较友好。
  • 对空文本、短文本做了边界处理,避免除零错误。
  • 使用 Counter 统计词频,代码简洁,性能较好。

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

1. 性能瓶颈

主要耗时在:

  • 文本清洗:正则替换。
  • 2-gram 生成:遍历整个文本。
  • 词频统计:遍历所有 2-gram。
  • 余弦相似度计算:遍历词频向量。

2. 改进思路

  • 使用 collections.Counter 代替手动字典计数,减少代码量,提高速度。
  • 只清洗一次文本,避免重复清洗。
  • 使用局部变量保存中间结果,减少重复计算。
  • 输出时只写答案文件,不打印多余内容。

3. 性能分析图

使用 Python 自带的 cProfile 进行性能分析:

python -m cProfile -s time main.py orig.txt orig_add.txt answer.txt

7a8016abd9eaf26c396657cabcc0129e
403c247e8012c86894ceff775dd1aa54
dc6e4dcb88bf146b1878244bd8b5572d
a284957ee0ed3b4d826a20f44faf8692

从分析结果看,程序总耗时约 0.004 秒,耗时最大的函数是 main 和正则编译相关函数,整体性能良好。

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

1. 单元测试代码

import unittest
from main import clean_text, get_ngrams, calc_similarity


class TestPaperCheck(unittest.TestCase):

    def test_clean_text(self):
        self.assertEqual(clean_text("你好,世界!"), "你好世界")

    def test_get_ngrams_empty(self):
        self.assertEqual(get_ngrams(""), [])

    def test_get_ngrams_short(self):
        self.assertEqual(get_ngrams("你"), ["你"])

    def test_get_ngrams_normal(self):
        self.assertEqual(get_ngrams("你好"), ["你好"])

    def test_similarity_same(self):
        score = calc_similarity("今天是星期天", "今天是星期天")
        self.assertAlmostEqual(score, 1.0, places=2)

    def test_similarity_different(self):
        score = calc_similarity("今天是星期天", "苹果香蕉梨")
        self.assertLess(score, 0.5)

    def test_similarity_empty(self):
        self.assertEqual(calc_similarity("", "你好"), 0.0)

    def test_similarity_partial(self):
        score = calc_similarity("今天是星期天,天气晴", "今天是周天,天气晴朗")
        self.assertGreater(score, 0.0)
        self.assertLessEqual(score, 1.0)

    def test_similarity_symmetric(self):
        a = "今天天气很好"
        b = "今天天气不错"
        self.assertAlmostEqual(
            calc_similarity(a, b),
            calc_similarity(b, a),
            places=6
        )

    def test_output_two_decimal(self):
        score = calc_similarity("abcdef", "abcdef")
        self.assertEqual(f"{score:.2f}", "1.00")


if __name__ == "__main__":
    unittest.main()

2. 测试函数说明

测试函数 测试内容
test_clean_text 清洗文本是否正确
test_get_ngrams_empty 空文本是否返回空列表
test_get_ngrams_short 短文本是否返回自身
test_get_ngrams_normal 正常文本是否切分成 2-gram
test_similarity_same 相同文本相似度是否为 1
test_similarity_different 不同文本相似度是否较低
test_similarity_empty 空文本相似度是否为 0
test_similarity_partial 部分相似文本相似度是否在 0 到 1 之间
test_similarity_symmetric 相似度是否满足对称性
test_output_two_decimal 输出是否保留两位小数

3. 测试覆盖率

使用 coverage 查看覆盖率:

coverage run -m unittest test_paper.py
coverage report

b4e59b8d94b7c6db166be4daee982f39

覆盖率结果:

Name           Stmts   Miss  Cover
----------------------------------
main.py           53     24    55%
test_paper.py     32      1    97%
----------------------------------
TOTAL             85     25    71%

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

异常 场景 设计目标
FileNotFoundError 输入文件路径不存在 提示文件不存在,避免程序崩溃
PermissionError 文件无权限读取或写入 提示权限错误
UnicodeDecodeError 文件编码异常 使用 errors="ignore" 容错
IndexError / sys.argv 长度不对 命令行参数不足 提示正确用法
ZeroDivisionError 空文本导致模长为 0 在余弦相似度中判断并返回 0.0
Exception 其他未知异常 统一捕获,避免异常退出

异常测试样例

def test_file_not_found(self):
    with self.assertRaises(FileNotFoundError):
        open("not_exist_file_12345.txt", "r", encoding="utf-8")

对应场景:当输入文件不存在时,程序应提示错误,而不是直接崩溃。

六、实际 PSP 时间

见本文第一节“实际耗时”表格。

七、总结

本次作业完成了论文查重程序的基本功能:

  • 从命令行读取原文、抄袭版论文、答案文件路径。
  • 计算两篇论文的相似度。
  • 输出保留两位小数的浮点数。
  • 编写了 10 个单元测试,全部通过。
  • 使用 GitHub 管理源代码,至少提交两次。
  • 对异常情况做了处理,避免程序异常退出。

通过本次作业,我熟悉了 Python 文件读写、命令行参数、余弦相似度算法、单元测试和 GitHub 使用流程。

posted @ 2026-09-14 22:33  kkk616  阅读(9)  评论(0)    收藏  举报