软件工程第二次作业

软件工程第二次个人作业

github仓库:dyLR-036030/xy2

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/class12Grade23ComputerScience/homework/13469
这个作业要求在哪里 个人项目 - 作业 - 计科23级12班 - 班级博客 - 博客园
这个作业的目标 学习个人项目制作,掌握项目性能分析

1. PSP表格

在开始实现程序之前,我们估计各个模块的开发时间如下:

PSP阶段 任务内容 预估耗时(分钟) 实际耗时(分钟)
计划 确定需求和技术方案 30 25
需求分析 分析论文查重功能需求 60 60
设计文档 编写系统设计文档 25 20
设计复审 审查设计方案 15 10
代码规范 制定编码规范 20 20
具体设计 模块接口设计 40 50
具体编码 编写核心代码 120 150
代码复审 代码审查和优化 20 15
测试 单元测试和集成测试 30 40
测试报告 编写测试报告 10 10
总结报告 编写项目总结 10 5
总计 所有开发活动 400 450

2. 计算模块接口的设计与实现过程

2.1 代码组织架构

本项目采用模块化设计,将不同功能分离到独立的模块中:

text

论文查重系统/
├── main.py              # 主程序入口
├── file_io.py          # 文件读写模块
├── processors.py       # 文本处理模块
├── lcs.py             # 核心算法模块
├── config.py          # 配置参数模块
└── test_main.py       # 单元测试模块

2.2 模块功能关系

模块名称 主要功能 核心函数 依赖关系
main.py 程序入口,流程控制 main() file_io, processors, lcs
file_io.py 文件读写和参数验证 read_files(), write_result() config
processors.py 文本预处理和分块 preprocess(), split_into_chunks() config
lcs.py 智能分层查重算法 calculate_similarity_optimized()
lcs_length_optimized()
approximate_lcs()
quick_similarity_check()
processors
functools
collections
config.py 配置参数管理 常量定义

2.3主要模块流程图

主函数流程图

13bd754103e145ed77493f334e7c2b47

LCS流程图

1558e45570658761e014bd76b6dfff73

2.4关键算法设计

2.4.1LCS算法优化(lcs_length_optimized)

python

# 使用一维DP数组优化空间复杂度
# 空间复杂度从O(mn)优化到O(min(m,n))
def lcs_length_optimized(a: str, b: str) -> int:

算法关键点:

  • 动态规划实现最长公共子序列计算
  • 滚动数组技术大幅降低内存占用
  • 确保较短的字符串作为行向量

2.4.2 混合相似度计算(calculate_similarity)

python

# 结合整体相似度和句子级相似度
def calculate_similarity(original: str, copied: str) -> float:

权重分配:

  • 整体LCS相似度:30%
  • 句子级相似度:70%

2.5 独到之处

  1. 双重相似度评估:结合字符级和句子级相似度,提高准确性
  2. 内存优化算法:LCS算法空间复杂度优化至O(min(m,n))
  3. 分块处理机制:支持超长文本处理,避免内存溢出
  4. 智能预处理:保留中文字符和数字,消除符号干扰

3. 计算模块接口部分的性能改进

3.1 性能瓶颈分析

d6a9623bf06b2c6dfc49cad63a2db5b7

  • 具体问题分析

    1. LCS算法是主要瓶颈
      • lcs_length_optimized 函数消耗了25.584秒,占总时间的95%以上
      • 被调用了60次,平均每次0.426秒
    2. 句子级相似度计算效率低
      • 虽然自身时间很短,但累计时间长(因为调用了LCS)
      • 说明句子匹配策略可能不够高效

3.2 性能优化策略

1. 算法复杂度优化

问题:原始LCS算法O(m*n)复杂度无法避免
解决方案:减少不必要的计算,而不是改变算法本身

python

# 优化策略:
# 1. 快速路径检查
if a == b: return len(a)          # 完全相同直接返回
if a in b: return len(a)          # 子串关系快速返回

# 2. 长度差异过滤
length_ratio = min(len(a), len(b)) / max(len(a), len(b))
if length_ratio < 0.6: continue   # 长度差异过大跳过

2. 计算量削减策略

问题:每个句子都与所有其他句子进行LCS计算
解决方案:多层次过滤机制

python

# 三级过滤机制:
# 第一层:哈希完全匹配(O(1))
if hash(orig_sent) == hash(copy_sent): similarity = 1.0

# 第二层:快速相似度估算(O(n))
if quick_similarity_check(orig_sent, copy_sent) < 0.3: continue

# 第三层:精确LCS计算(O(m*n))← 只有前两层通过的才进入

3. 缓存机制应用

问题:相同句子可能被重复计算
解决方案:LRU缓存最近的计算结果

python

from functools import lru_cache

@lru_cache(maxsize=500)  # 缓存500个最近的计算结果
def lcs_length_optimized(a: str, b: str) -> int:
    # 缓存命中时直接返回,避免重复计算

4. 近似算法替代

问题:超长文本的精确计算代价过高
解决方案:牺牲少量精度换取巨大性能提升

python

def approximate_lcs(a: str, b: str, k: int = 4) -> int:
    """使用k-gram近似算法,复杂度O(n)"""
    a_grams = {a[i:i+k] for i in range(len(a)-k+1)}  # O(n)
    b_grams = {b[i:i+k] for i in range(len(b)-k+1)}  # O(n)
    return len(a_grams & b_grams) * k  # O(1)

5. 智能分块策略

问题:分块比较时每个块都与所有其他块比较
解决方案:限制比较数量,动态调整块大小

python

# 限制每个原文块最多比较5个抄袭块
max_comparisons = min(5, len(copied_chunks))

# 动态块大小调整
if avg_length > 50000: chunk_size = 5000  # 超长文本用大块
elif avg_length > 20000: chunk_size = 3000
else: chunk_size = 2000

6. 提前终止机制

问题:即使已经找到高相似度匹配,仍继续计算
解决方案:达到阈值后提前终止

python

# 句子匹配中
if max_similarity > 0.8: break  # 达到80%相似度就停止搜索

# 分块匹配中  
if max_similarity > 0.9: break  # 达到90%相似度提前终止

**3.2 性能改进后结果

f04eaddcc32d8d277b196b80e48435b8

性能指标 原始版本 优化版本 改进幅度 详细分析
总执行时间 25.593秒 10.195秒 ↓60.2% 性能提升显著,比之前估算更好
LCS计算时间 25.584秒 10.166秒 ↓60.3% LCS优化效果明显
LCS调用次数 60次 25次 ↓58.3% 过滤机制有效
平均LCS时间 0.426秒 0.407秒 ↓4.5% 单次计算略有优化
句子处理时间 12.812秒(累计) 0.023秒 ↓99.8% 句子级优化效果惊人

4. 计算模块部分单元测试展示

4.1 单元测试结构

python

class TestLCSAlgorithms(unittest.TestCase):
    """测试LCS算法模块"""
    
    def test_lcs_length_optimized_basic(self):
        """测试基础LCS计算"""
        a = "ABCD"
        b = "ACBD"
        result = lcs_length_optimized(a, b)
        self.assertEqual(result, 3)
    
    def test_lcs_length_optimized_empty(self):
        """测试空字符串的LCS"""
        self.assertEqual(lcs_length_optimized("", "abc"), 0)
        self.assertEqual(lcs_length_optimized("abc", ""), 0)
        self.assertEqual(lcs_length_optimized("", ""), 0)

4.2 测试数据构造思路

4.2.1 正常情况测试

  • 相同文本:验证相似度为1.0
  • 不同文本:验证相似度接近0
  • 部分相似:验证中间值合理性
  • 预处理效果:测试大小写、特殊字符处理

4.2.2 边界情况测试

  • 空文本处理:测试空字符串和空白字符处理
  • 超长文本:测试分块机制和近似算法
  • 特殊字符:测试预处理后为空的情况
  • 短文本:测试单字符、短句处理

4.2.3 异常情况测试

  • 文件不存在:测试错误处理
  • 编码错误:测试编码异常处理
  • 内存不足:测试大文件分块处理
  • 参数错误:测试命令行参数验证

4.3 关键测试用例

python

def test_calculate_similarity_identical(self):
    """测试相同文本相似度"""
    text = "这是一个完整的测试文本。"
    similarity = calculate_similarity(text, text)
    self.assertGreaterEqual(similarity, 0.8)  # 相同文本应该高相似度
    self.assertLessEqual(similarity, 1.0)

def test_calculate_similarity_empty(self):
    """测试空文本相似度"""
    self.assertEqual(calculate_similarity("", "abc"), 0.0)
    self.assertEqual(calculate_similarity("abc", ""), 0.0)
    self.assertEqual(calculate_similarity("", ""), 0.0)

def test_calculate_similarity_special_chars(self):
    """测试特殊字符文本的相似度"""
    text = "!@#$%^&*()"
    similarity = calculate_similarity(text, text)
    self.assertEqual(similarity, 0.0)  # 预处理后为空,相似度应为0

def test_very_long_text(self):
    """测试超长文本"""
    long_text = "软件工程" * 1000
    similarity = calculate_similarity(long_text, long_text)
    self.assertGreaterEqual(similarity, 0.8)  # 长文本相同应该高相似度

def test_split_into_chunks_edge_cases(self):
    """测试分块边界情况"""
    test_cases = [
        ("", 100, 10),  # 空文本
        ("a", 100, 10),  # 文本小于块大小
        ("a" * 100, 100, 10),  # 文本等于块大小
    ]
    
    for text, chunk_size, overlap in test_cases:
        with self.subTest(text=text, chunk_size=chunk_size, overlap=overlap):
            chunks = split_into_chunks(text, chunk_size, overlap)
            self.assertTrue(len(chunks) >= 1)

4.4 测试覆盖率

736db018ab864e7a4d7fdd3c7211ca70


5. 计算模块部分异常处理说明

5.1 异常处理设计目标

异常类型 设计目标 处理策略 修复后的改进
ValueError 参数验证 提前检查,明确提示 增强命令行参数验证
FileNotFoundError 文件不存在 提供详细路径信息 改进文件路径错误处理
IOError 读写错误 分级处理,友好提示 优化文件编码处理
MemoryError 内存不足 优雅降级,分块处理 改进大文件分块策略
空文本处理 特殊输入 返回0.0相似度 新增:预处理空文本检查

5.2 异常处理单元测试

5.2.1 参数验证异常测试

python

def test_validate_arguments_incorrect(self):
    """测试参数数量错误"""
    test_args = ['main.py']  # 缺少参数
    with patch('sys.argv', test_args):
        with self.assertRaises(ValueError):
            validate_arguments()

def test_validate_arguments_correct(self):
    """测试正确的参数验证"""
    test_args = ['main.py', 'orig.txt', 'copy.txt', 'output.txt']
    with patch('sys.argv', test_args):
        orig, copy, output = validate_arguments()
        self.assertEqual(orig, 'orig.txt')
        self.assertEqual(copy, 'copy.txt')
        self.assertEqual(output, 'output.txt')

错误场景:命令行参数数量不正确

5.2.2 文件操作异常测试

python

def test_main_file_not_found(self):
    """测试文件不存在的情况"""
    test_args = ['main.py', 'nonexistent.txt', 'copy.txt', 'output.txt']
    with patch('sys.argv', test_args):
        with self.assertRaises(SystemExit):
            main()

def test_read_files_length_exceed(self):
    """测试文本长度超限"""
    long_text = 'a' * (MAX_TEXT_LENGTH + 100)
    test_file2 = os.path.join(self.test_dir, 'test2.txt')
    
    with open(self.test_file, 'w', encoding=FILE_ENCODING) as f:
        f.write(long_text)
    with open(test_file2, 'w', encoding=FILE_ENCODING) as f:
        f.write('短文本')
    
    text1, text2 = read_files(self.test_file, test_file2)
    self.assertEqual(len(text1), MAX_TEXT_LENGTH)  # 自动截断
    self.assertEqual(text2, '短文本')

错误场景:输入文件路径不存在或文件过大

5.2.3 空文本和特殊字符处理测试

python

def test_calculate_similarity_empty(self):
    """测试空文本相似度"""
    self.assertEqual(calculate_similarity("", "abc"), 0.0)
    self.assertEqual(calculate_similarity("abc", ""), 0.0)
    self.assertEqual(calculate_similarity("", ""), 0.0)

def test_calculate_similarity_special_chars(self):
    """测试特殊字符文本的相似度"""
    text = "!@#$%^&*()"
    similarity = calculate_similarity(text, text)
    self.assertEqual(similarity, 0.0)  # 预处理后为空,相似度应为0

def test_calculate_similarity_whitespace(self):
    """测试空白字符文本的相似度"""
    text = "   \t\n"
    similarity = calculate_similarity(text, text)
    self.assertEqual(similarity, 0.0)  # 预处理后为空,相似度应为0

新增场景:空文本、纯特殊字符、纯空白字符处理

5.2.4 分块处理异常测试

python

def test_split_into_chunks_empty_text(self):
    """测试空文本分块"""
    chunks = split_into_chunks("", 100, 10)
    self.assertEqual(len(chunks), 1)
    self.assertEqual(chunks[0], "")  # 修复:空文本返回包含空字符串的列表

def test_merge_chunk_results_empty(self):
    """测试空结果合并"""
    self.assertEqual(merge_chunk_results([]), 0.0)
    self.assertEqual(merge_chunk_results([], [1, 2, 3]), 0.0)

修复场景:空文本分块返回空列表的问题

5.3 异常处理策略

5.3.1 预防性检查(新增)

python

def calculate_similarity_optimized(original: str, copied: str) -> float:
    """优化版相似度计算主函数"""
    # 处理空文本情况 - 新增检查
    if not original or not original.strip():
        return 0.0
    if not copied or not copied.strip():
        return 0.0
    
    # 预处理文本
    processed_original = preprocess(original)
    processed_copied = preprocess(copied)
    
    # 检查预处理后是否为空 - 新增检查
    if not processed_original or not processed_copied:
        return 0.0

5.3.2 分级处理策略

  1. 轻度异常:文本过短 → 警告提示,继续处理
  2. 中度异常:文件过大 → 自动截断,继续处理
  3. 重度异常:文件不存在 → 明确报错,终止程序
  4. 特殊异常:内存不足 → 启用分块,优雅降级

5.3.3 用户友好提示

python

except ValueError as e:
    print(f"参数错误: {e}")
    sys.exit(1)
except FileNotFoundError as e:
    print(f"文件错误: {e}")
    sys.exit(1)
except IOError as e:
    print(f"IO错误: {e}")
    sys.exit(1)
except MemoryError as e:
    print(f"内存不足: {e}")
    print("尝试使用更小的分块大小...")  # 提供解决方案
    sys.exit(1)

5.3.4 优雅降级机制

python

# 内存不足时自动调整分块策略
if avg_length > 20000:
    chunk_size = 3000  # 自动调整块大小
elif avg_length > 50000:
    chunk_size = 5000

# 限制比较数量优化性能
max_comparisons = min(5, len(copied_chunks))  # 最多比较5个块

5.4 修复的主要异常处理问题

  1. 空文本相似度:从返回1.0修正为返回0.0
  2. 空文本分块:从返回空列表修正为返回包含空字符串的列表
  3. 特殊字符文本:预处理后为空文本的正确处理
  4. 短文本处理:降低阈值,提高短文本兼容性
  5. 边界情况覆盖:增加各种边界条件的测试用例

这些改进确保了系统在各种异常情况下的稳定性和正确性。

6. 命令行输入说明

bash

python main.py orig.txt orig_add.txt result.txt
posted @ 2025-09-23 13:00  晓轩ya  阅读(11)  评论(0)    收藏  举报