第一次个人编程作业----论文查重
1. PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 60 |
Development | 开发 | 430 | 560 |
· Analysis | · 需求分析 (包括学习新技术) | 90 | 120 |
· Design Spec | · 生成设计文档 | 20 | 20 |
· Design Review | · 设计复审 | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 180 | 240 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 90 |
Reporting | 报告 | 180 | 130 |
· Test Report | · 测试报告 | 75 | 70 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 75 | 30 |
合计 | 640 | 750 |
2. 计算模块接口的设计与实现过程
本项目采用函数式 + 模块化组织,围绕“字符 n-gram 表示 → 集/袋建模 → 多相似度量 → 可配置回退”的主线展开。入口为 main.py
代码组织与关系
src/normalizer.py
def normalize(text: str) -> str
- 作用:统一大小写、去除多余空白/标点(具体规则以实现为准),为 n-gram 稳定化输入。
src/tokenizer.py
def char_ngrams(text: str, n: int = 3) -> List[str]
- 作用:基于字符的 n-gram(默认 3-gram)
src/shingler.py
def as_set(it: Iterable[str]) -> Set[str]
def as_bag(it: Iterable[str]) -> Counter
- 作用:将 n-gram 序列转换为集合(用于 Jaccard)或词袋 Counter(用于余弦)。
src/similarity.py
def jaccard(a: set, b: set) -> float
def cosine_tf(a: Counter, b: Counter) -> float
def levenshtein_ratio(s1: str, s2: str) -> float
- 作用:三种相似度量
src/pipeline.py
调用关系(自顶向下)
main.py
→ compute_similarity
→ normalize
→ char_ngrams
→ as_set
→ jaccard
→ 得出结果[0,1]
流程图
3. 计算模块接口的性能改进
- 使用 cProfile 对 CLI 主流程进行剖析,并基于 cumtime 绘制前 20 个热点函数的耗时图
瓶颈分析
- 文本预处理开销较大,正则替换操作消耗明显,所以优先对normalize进行优化。
原始:
t = unicodedata.normalize("NFKC", text).casefold()
t = t.translate(_PUNCT_TABLE)
t = re.sub(r"\s+", "", t) # 使用正则删除空白
改进:
t = unicodedata.normalize("NFKC", text).casefold()
t = t.translate(_PUNCT_TABLE)
# 使用纯内建方法删除空白,避免正则开销
t = t.replace(" ", "").replace("\t", "").replace("\n", "").replace("\r", "")
- 改进后结果
4. 计算模块部分单元测试展示
为了保证核心计算模块的正确性,本项目在 tests/
目录下编写了单元测试,确保文本归一化、分词、相似度计算以及完整流程的正确性。
测试思路
- normalizer:测试大小写归一化、标点清理、同义词替换。
- tokenizer:验证 n-gram 切分结果是否符合预期。
- similarity:针对 Jaccard、Cosine、Levenshtein 相似度构造边界用例(完全相同/完全不同/部分重叠)。
- pipeline:使用短文本模拟完整流程,检查最终重复率是否合理。
测试函数
def test_normalize_basic():
def test_char_ngrams_short_and_empty():
def test_similarity_jaccard_overlap():
def test_cosine_tf_bag():
def test_levenshtein_ratio_known_case():
def test_pipeline_chinese_example():
def test_pipeline_empty_files():
def test_pipeline_english_texts():
def test_synonyms_extension_improves_similarity():
def test_edit_fallback_extension():
def test_cli_behavior(tmp_path: Path):
def test_pipeline_cosine_branch():
def test_io_read_text_missing_file(tmp_path):
def test_io_read_text_gb18030_fallback(tmp_path):
- 覆盖率
5. 计算模块部分异常处理说明
5.1 文件与输入异常
- 文件不存在:在
io_utils.py
中对文件读写进行try/except
封装,遇到路径错误时会抛出FileNotFoundError
,并给出提示信息。 - 空文件输入:如果输入文本为空,归一化与分词阶段会返回空集合,最终相似度计算统一返回
0.0
,避免出现除零错误。
5.2 文本归一化异常
- 非字符串输入:在
normalizer.py
中增加类型检查,若传入None
或非字符串对象,直接返回空字符串,保证后续流程不会中断。 - 同义词替换缺失:若
synonyms.txt
中不存在对应词汇,则保持原词不变,不影响流程。
5.3 分词与 n-gram 生成异常
- 文本过短:当文本长度小于设定的 n 值(如 n=3)时,
tokenizer.py
会返回一个空列表,后续的相似度计算模块能够自然处理空输入。
5.4 相似度计算异常
- 集合运算异常:
jaccard
与cosine_tf
在集合或字典为空时,均会返回0.0
,而不是报错。
5.5 流程级异常
- 在
pipeline.py
的主流程中使用try/except
包装:- 遇到不可恢复的异常时,打印错误信息并安全退出;
- 遇到可恢复的边界情况(如空输入文件),则返回重复率
0.0
并继续执行。
实际花费时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 60 |
Development | 开发 | 430 | 560 |
· Analysis | · 需求分析 (包括学习新技术) | 90 | 120 |
· Design Spec | · 生成设计文档 | 20 | 20 |
· Design Review | · 设计复审 | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 180 | 240 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 90 |
Reporting | 报告 | 180 | 130 |
· Test Report | · 测试报告 | 75 | 70 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 75 | 30 |
合计 | 640 | 750 |