软工第一次编程作业
这个作业属于哪个课程 | 计科23级34班 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477 |
这个作业的目标 | 通过设计论文查重系统,体会工程开发流程,实践工程化开发相关知识 |
1. GitHub仓库地址:
https://github.com/xoaop/3123004536
2. PSP表格
PSP阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|
计划 | 20 | 25 |
需求分析 | 30 | 35 |
设计 | 40 | 45 |
代码实现 | 120 | 130 |
测试 | 30 | 40 |
报告撰写 | 30 | 35 |
事后总结 | 20 | 25 |
合计 | 290 | 335 |
3. 模块接口的设计和实现
代码组织
程序主要由一个Python文件main.py
组成,包含两个核心函数:
get_text_fingerprint(text, top_n=20)
: 将文本转换为SimHash指纹calculate_similarity(simhash1, simhash2)
: 计算两个指纹的相似度
主程序部分处理命令行参数,读取文件,调用上述函数并输出结果。
函数关系
- 主程序读取两个文件内容
- 调用
get_text_fingerprint
生成两个指纹 - 调用
calculate_similarity
计算相似度 - 输出结果到文件
算法关键
算法使用SimHash算法:
- 对文本进行中文分词(使用jieba库)
- 统计词频,取频率最高的top_n个词作为特征
- 生成SimHash指纹(64位)
- 计算两个指纹的海明距离,转换为相似度百分比
独到之处
- 使用中文分词确保准确处理中文文本
- 通过词频排序选择最具代表性的特征词,提高查重准确性
- 简单高效,适合大规模文本比较
关键函数流程图(简化):
文本输入 -> 分词 -> 词频统计 -> 选择top_n -> 生成SimHash -> 计算距离 -> 输出相似度
4. 性能改进
优化前
优化后
程序中消耗最大的函数
本项目中,消耗时间最多的函数为 get_text_fingerprint
,其核心耗时在于分词(jieba.lcut)和特征提取。分词算法复杂度高,且需遍历大量文本,成为性能瓶颈。即使优化了数据结构和流程,分词与SimHash指纹生成的复杂度决定了其在大文本下仍为主要耗时环节。
原因分析:
- 分词需遍历和匹配词典,耗时显著。
- 特征词频统计和SimHash计算涉及多次遍历和位运算。
- 算法复杂度近似 $O(n)$,文本越大耗时越明显。
6. 计算模块部分单元测试展示
覆盖率
主要测试代码
def test_get_text_fingerprint(self):
text = "这是一个测试文本"
fp = get_text_fingerprint(text)
self.assertIsNotNone(fp)
# SimHash 64位,检查hash值
self.assertIsInstance(fp.value, int)
def test_calculate_similarity_identical(self):
text = "相同的文本内容"
fp1 = get_text_fingerprint(text)
fp2 = get_text_fingerprint(text)
similarity = calculate_similarity(fp1, fp2)
self.assertAlmostEqual(similarity, 100.0, delta=1.0)
def test_calculate_similarity_different(self):
text1 = "第一篇测试文章"
text2 = "第二篇完全不同的文章"
fp1 = get_text_fingerprint(text1)
fp2 = get_text_fingerprint(text2)
similarity = calculate_similarity(fp1, fp2)
self.assertLess(similarity, 70.0) # 调整阈值
def test_calculate_similarity_similar(self):
text1 = "这是一个相似的文本"
text2 = "这是一个相似的文章"
fp1 = get_text_fingerprint(text1)
fp2 = get_text_fingerprint(text2)
similarity = calculate_similarity(fp1, fp2)
self.assertGreater(similarity, 80.0)
def test_empty_text(self):
text1 = ""
text2 = ""
fp1 = get_text_fingerprint(text1)
fp2 = get_text_fingerprint(text2)
similarity = calculate_similarity(fp1, fp2)
self.assertAlmostEqual(similarity, 100.0, delta=1.0)
def test_one_empty_text(self):
text1 = "非空文本, 包含一些内容"
text2 = ""
fp1 = get_text_fingerprint(text1)
fp2 = get_text_fingerprint(text2)
similarity = calculate_similarity(fp1, fp2)
self.assertLess(similarity, 1.0) # 调整阈值
7. 计算模块部分异常处理说明
- 文件读取异常
- 设计目标:文件不存在或编码错误时,能捕获异常并提示,防止程序崩溃。
- 测试代码:
with self.assertRaises(FileNotFoundError): read_utf8_file("nonexistent.txt")
- 文件写入异常
- 设计目标:写入路径无权限或磁盘满时,能捕获异常并提示。
- 测试代码:
with self.assertRaises(Exception): write_result("/root/forbidden.txt", 0.9)
- 空文本/参数异常
- 设计目标:输入参数不足或文本为空时,能优雅处理并给出提示。
- 测试代码:
self.assertEqual(calculate_similarity(0, 0), 100.0)
- 编码转换异常
- 设计目标:读取非UTF-8文件时,能捕获UnicodeDecodeError。
- 测试代码:
with self.assertRaises(UnicodeDecodeError): read_utf8_file("gbk_encoded.txt")