软件工程第二次作业-个人项目
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13468 |
这个作业的目标 | 完成论文查重算法设计以及单元测试用例的设计 |
一、PSP表格(预计时间)
PSP2.1 | Personal Software Process Stages | 预估耗时 (分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 30 | |
Estimate | 估计这个任务需要多少时间 | 120 | |
Development | 开发 | 360 | |
Analysis | 需求分析 (包括学习新技术) | 60 | |
Design Spec | 生成设计文档 | 40 | |
Design Review | 设计复审 | 15 | |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 15 | |
Design | 具体设计 | 50 | |
Coding | 具体编码 | 260 | |
Code Review | 代码复审 | 40 | |
Test | 测试(自我测试,修改代码,提交修改) | 50 | |
Reporting | 报告 | 90 | |
Test Repor | 测试报告 | 50 | |
Size Measurement | 计算工作量 | 30 | |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 50 | |
合计 | 1260 |
二、计算模块接口的设计与实现过程
1.计算模块划分
以下表格展示了程序的模块划分、对应函数及功能说明:
模块 | 函数 | 功能说明 |
---|---|---|
文件读取 | read_file(path) |
读取指定路径的文件内容 |
文本预处理 | normalize_text(s) |
去除空白字符、标点,并将英文转小写,规范化文本 |
LCS 计算模块 | lcs_length(a, b) |
计算两段文本的最长公共子序列长度,使用滚动数组减少空间复杂度 |
分段重复率计算模块 | compute_duplicate_rate(orig_text, plag_text, as_percentage=True) |
对超长文本进行分段LCS,计算整体重复率 |
主程序 | main() |
命令行参数解析、文件存在性检查、调用重复率计算,并输出结果到文件 |
2.算法关键
(1) 文本规范化(normalize_text)
去除所有空白字符和标点,统一英文为小写。
(2) LCS 动态规划(LCS,lcs_length)
衡量连续字符的最长匹配长度。
(3) 分段处理(compute_duplicate_rate_segmented)
对长文本分段,降低内存和计算压力,可以灵活调整段长度控制精度和性能。
3.独到之处
(1) 滚动数组优化 LCS 算法,节省内存。
(2) 自定义文本规范化
采用正则同时处理中文、英文标点,保证不同语言文本匹配准确;去空白、去标点和大小写统一,避免无关字符影响重复率。
(3) 分段 LCS
既能检测大段连续抄袭,又能处理超长文本,可以根据实际文本长度和性能需求调整 seg_len。
三、计算模块接口性能改进
1.性能分析
改进前:
在初始实现中,对比文本计算重复率时,发现 lcs_length
函数 是程序的主要性能瓶颈。
图中显示
lcs_length
耗时87s,占据程序大部分执行时间,是主要性能消耗函数。
改进后:
耗时2.9s,优化后时间显著减少。
2.性能改进思路
(1) 滚动数组优化 LCS
原始 LCS 算法需要 O(nm) 空间存储二维 DP 矩阵。改进方法:只保留前一行和当前行数据,使用两个一维数组交替存储,空间复杂度从 O(nm) 降到 O(m)。
(2) 分段 LCS
对长文本直接计算 LCS 会导致时间复杂度过高,改进为按段分段计算LCS,每段长度 seg_len。
3. 所花费时间记录
优化步骤 | 所花时间(分钟) | 说明 |
---|---|---|
性能分析 | 10 | 使用 cProfile 等性能分析工具,找出程序瓶颈(主要在 lcs_length) |
空间优化 | 30 | 将二维 DP 数组改为两行滚动数组,减少内存占用 |
行列交换优化 | 10 | 对 lcs_length 内循环调整顺序,确保列长度小,提高缓存效率 |
分段计算优化 | 30 | 使用 compute_duplicate_rate_segmented,将文本分段计算 LCS,降低时间复杂度 |
测试与验证 | 10 | 对比优化前后结果,确保重复率计算正确 |
合计 | 90 | 性能优化总耗时 |
四、计算模块部分单元测试展示
1.部分单元测试代码
# ----------------- 文件读取测试 -----------------
def test_read_file():
with tempfile.NamedTemporaryFile("w+", delete=False, encoding="utf-8") as tmp:
tmp.write("hello world")
tmp_path = tmp.name
content = pc.read_file(tmp_path)
assert content == "hello world"
os.remove(tmp_path)
# ----------------- 文本规范化测试 -----------------
def test_normalize_text():
text = "今天 是 星期天,天气晴!Hello, World。"
normalized = pc.normalize_text(text)
# 按新代码逻辑,空格和标点去掉,英文小写化
assert normalized == "今天是星期天天气晴helloworld"
# ----------------- LCS 测试 -----------------
def test_lcs_length_identical():
assert pc.lcs_length("abc", "abc") == 3
def test_lcs_length_partial():
assert pc.lcs_length("abcdef", "ace") == 3
def test_lcs_length_empty():
assert pc.lcs_length("", "abc") == 0
assert pc.lcs_length("abc", "") == 0
2.测试函数说明与数据构造思路
测试函数 | 测试目标 | 构造测试数据思路 |
---|---|---|
test_read_file |
验证文件读取功能是否正确 | 使用 tempfile 创建临时文件,写入已知文本,然后读取验证内容是否一致 |
test_normalize_text |
验证文本规范化功能 | 构造包含空格、标点、英文大写的字符串,检查空格和标点是否去掉,并将英文转换为小写 |
test_lcs_length_identical |
验证 LCS 计算完全相同字符串 | 构造两个完全相同的字符串,预期 LCS 长度等于字符串长度 |
test_lcs_length_partial |
验证 LCS 计算部分相同字符串 | 构造部分相同的字符串,预期 LCS 长度为公共子序列长度 |
test_lcs_length_empty |
验证 LCS 计算空字符串情况 | 构造空字符串与非空字符串组合,预期 LCS 长度为 0 |
test_compute_duplicate_rate_segmented_identical |
验证分段重复率计算完全相同文本 | 构造完全相同的原文和抄袭文本,重复率应接近 100% |
test_compute_duplicate_rate_segmented_partial |
验证分段重复率计算部分重复文本 | 构造原文和部分相同的抄袭文本,计算 LCS 后重复率应接近公共字符比例(按分段算法计算) |
test_compute_duplicate_rate_segmented_empty_orig |
验证分段重复率计算原文为空 | 构造原文为空字符串,抄袭文本任意内容,重复率应为 0 |
test_compute_duplicate_rate_segmented_empty_copy |
验证分段重复率计算抄袭文本为空 | 构造原文非空,抄袭文本为空,重复率应为 0 |
test_compute_duplicate_rate_segmented_whitespace_only |
验证分段重复率计算仅空格情况 | 构造原文和抄袭文本仅包含空格,重复率应为 0 |
test_compute_duplicate_rate_segmented_punctuation |
验证分段重复率计算含标点文本 | 构造含中文/英文标点的文本,标点应被忽略,重复率接近文字内容比例 |
test_main_correct_execution |
验证 main 函数执行流程 | 使用临时目录创建原文和抄袭文本文件,运行 main 函数,检查输出为数字字符串且保留两位小数 |
test_main_missing_file |
验证文件不存在时程序异常退出 | 指定不存在的文件路径,检查程序是否抛出 SystemExit 异常 |
3.单元测试得到的测试覆盖率截图
五、计算模块部分异常处理说明
1.文件不存在异常
- 设计目标:保证程序在用户输入的原文或抄袭文件路径不存在时,能够及时提示并安全退出,而不会出现
FileNotFoundError
。 - 处理方式:
if not os.path.isfile(orig_path):
print("原文文件不存在:", orig_path)
sys.exit(2)
if not os.path.isfile(plag_path):
print("抄袭版文件不存在:", plag_path)
sys.exit(2)
- 单元测试示例:
def test_main_missing_file():
sys.argv = ["plagiarism_checker.py", "nofile.txt", "also_no.txt", "out.txt"]
with pytest.raises(SystemExit):
pc.main()
- 错误场景说明:
用户提供的原文文件或抄袭文件路径不存在,程序会捕获该情况并退出,避免崩溃。
2.原文为空异常
- 设计目标:避免计算重复率时出现除零错误,确保原文为空也能返回合理值。
- 处理方式:
def compute_duplicate_rate_segmented(orig_text, plag_text):
o = normalize_text(orig_text)
if len(o) == 0:
return 0.0
- 单元测试示例:
def test_compute_duplicate_rate_segmented_empty_orig():
rate = pc.compute_duplicate_rate_segmented("", "任意文本")
assert rate == pytest.approx(0.0)
- 错误场景说明:
原文文件内容为空,程序返回重复率 0,而不会报错。
3.抄袭文本为空异常
- 设计目标:确保重复率计算在抄袭文本为空时返回合理值,而不会报错。
- 处理方式:
def compute_duplicate_rate_segmented(orig_text, plag_text):
p = normalize_text(plag_text)
# 原文不为空时,重复率计算结果为 0
- 单元测试示例:
def test_compute_duplicate_rate_segmented_empty_copy():
rate = pc.compute_duplicate_rate_segmented("原文有内容", "")
assert rate == pytest.approx(0.0)
- 错误场景说明:
抄袭文本为空,程序返回重复率 0,而不会报错。
4.原文或抄袭文本全为空格异常
- 设计目标:确保重复率计算在文本只包含空格时返回合理值。
- 处理方式:
def compute_duplicate_rate_segmented(orig_text, plag_text):
o = normalize_text(orig_text)
p = normalize_text(plag_text)
if len(o) == 0:
return 0.0
- 单元测试示例:
def test_compute_duplicate_rate_segmented_whitespace_only():
rate = pc.compute_duplicate_rate_segmented(" ", " ")
assert rate == pytest.approx(0.0)
- 错误场景说明:
原文和抄袭文本只包含空格,程序返回重复率 0。
5.文本含标点异常
- 设计目标:保证重复率计算时忽略标点符号,避免影响结果。
- 处理方式:
def normalize_text(s):
import re
s = re.sub(r"\s+", "", s)
s = re.sub(r"[,。!?;:、,.!?;:\"'()()\[\]【】<>《》—\-…·/\\]", "", s)
s = s.lower()
return s
- 单元测试示例:
def test_compute_duplicate_rate_segmented_punctuation():
rate = pc.compute_duplicate_rate_segmented("今天,天气晴!", "今天天气晴")
assert rate >= 90.0
- 错误场景说明:
原文或抄袭文本含有中文或英文标点,程序忽略标点计算重复率,保证结果准确。
六、PSP表格(实际时间)
PSP2.1 | Personal Software Process Stages | 预估耗时 (分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 120 | 200 |
Development | 开发 | 360 | 380 |
Analysis | 需求分析 (包括学习新技术) | 60 | 80 |
Design Spec | 生成设计文档 | 40 | 60 |
Design Review | 设计复审 | 15 | 15 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 15 | 15 |
Design | 具体设计 | 50 | 60 |
Coding | 具体编码 | 260 | 320 |
Code Review | 代码复审 | 40 | 50 |
Test | 测试(自我测试,修改代码,提交修改) | 50 | 80 |
Reporting | 报告 | 90 | 120 |
Test Repor | 测试报告 | 50 | 50 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 50 | 30 |
合计 | 1260 | 1520 |