Thanks to visit codestin.com
Credit goes to www.cnblogs.com

第一次个人编程作业

论文查重项目

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477
这个作业的目标 实现论文查重需求,接触项目开发流程

GitHub链接 :https://github.com/Nanako51/3123002551

1.PSP表格

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

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

2.1 代码组织结构

image

2.2 函数关系图

image

2.3 关键函数流程图

主流程 - calculate_similarity()
image

文本预处理流程 - preprocess_text()
image

余弦相似度计算流程 - calculate_cosine_similarity()
image

2.4 算法关键点

  1. 文本预处理关键点
    文本清洗:使用正则表达式移除特殊字符,保留中文、英文、数字和基本标点
    分词策略:优先使用jieba分词库,如果不可用则使用简单分词方法
    n-gram特征:将文本转换为n-gram特征向量,默认使用2-gram
  2. 相似度计算关键点
    余弦相似度公式:similarity = (A · B) / (||A|| × ||B||)
    向量化:将文本转换为词频向量,使用Counter统计词频
    数值稳定性:处理零向量和空向量的边界情况
  3. 性能优化关键点
    缓存机制:在优化版本中实现文本预处理和相似度计算的缓存
    内存管理:合理使用内存,避免大文件处理时的内存溢出
    算法复杂度:O(n)的线性时间复杂度,其中n是词汇数量

2.5 独到之处

1. 自适应分词策略

# 智能选择分词方法
if JIEBA_AVAILABLE:
    words = jieba.lcut(text)
else:
    words = self._simple_segment(text)
  • 优先使用jieba分词库获得更好的中文分词效果
  • 当jieba不可用时自动降级到简单分词方法
  • 保证程序在任何环境下都能正常运行

2. 多算法支持

def calculate_similarity(self, vector1, vector2, method="cosine"):
    if method == "cosine":
        return self.calculate_cosine_similarity(vector1, vector2)
    elif method == "jaccard":
        return self.calculate_jaccard_similarity(vector1, vector2)
    # ... 其他算法
  • 支持多种相似度计算方法
  • 统一的接口设计,便于扩展
  • 可根据具体需求选择最适合的算法

3. 完善的错误处理

def safe_read_file(self, file_path, encoding='utf-8', max_size=10*1024*1024):
    try:
        # 验证文件路径
        is_valid, error_msg = self.validate_file_path(file_path, check_exists=True)
        if not is_valid:
            return None
        
        # 检查文件大小
        file_size = self.get_file_size(file_path)
        if file_size > max_size:
            return None
        
        # 读取文件
        return self.read_file(file_path, encoding)
    except Exception as e:
        return None
  • 多层次的错误处理机制
  • 文件大小限制防止内存溢出
  • 路径验证确保安全性

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

3.1 性能改进时间记录

改进项目 预估时间(分钟) 实际时间(分钟) 完成情况
缓存机制实现 30 25 已完成
内存使用优化 20 15 已完成
算法复杂度优化 15 20 已完成
性能监控实现 10 15 已完成
合计 75 75 已完成

3.2 改进思路

1. 缓存机制优化

class OptimizedPlagiarismDetector:
    def __init__(self, enable_cache=True):
        self._text_cache = {}  # 文本预处理缓存
        self._similarity_cache = {}  # 相似度计算缓存
    
    def _preprocess_text_cached(self, text):
        text_hash = str(hash(text))
        if text_hash in self._text_cache:
            return self._text_cache[text_hash]
        # 处理并缓存结果
        _, vector = self.text_processor.preprocess_text(text)
        self._text_cache[text_hash] = vector
        return vector

2. 内存使用优化

  • 使用生成器减少内存占用
  • 及时释放不需要的变量
  • 限制文件大小防止内存溢出(默认10MB限制)

3. 算法优化

  • 优化n-gram生成算法,减少不必要的计算
  • 使用更高效的数据结构(Counter)
  • 优化文本清洗的正则表达式

3.3 性能分析图

性能分析结果(基于1000字符文本)

论文查重算法性能分析报告
============================================================
测试文本长度: 1000 字符
测试文本2长度: 1000 字符

1. 整体性能测试:
------------------------------
平均处理时间: 1.7911ms
相似度结果: 0.8402

2. 各模块性能分析:
------------------------------
文本预处理平均耗时: 3.1471ms
相似度计算平均耗时: 0.0386ms

3. 详细函数性能分析:
------------------------------
文本清洗平均耗时: 0.0292ms
分词处理平均耗时: 0.6659ms
n-gram生成平均耗时: 0.3303ms
向量化平均耗时: 2.1216ms
余弦相似度平均耗时: 0.0386ms
Jaccard相似度平均耗时: 0.0065ms
曼哈顿距离平均耗时: 0.0028ms
欧几里得距离平均耗时: 0.0167ms

cProfile函数耗时分析结果

前20个最耗时的函数:
----------------------------------------
         116272 function calls (116237 primitive calls) in 0.067 seconds        

   Ordered by: cumulative time
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       10    0.000    0.000    0.067    0.007 main.py:31(calculate_similarity)
       20    0.000    0.000    0.065    0.003 text_processor.py:171(preprocess_text)
       20    0.003    0.000    0.041    0.002 text_processor.py:59(segment_text)
       20    0.012    0.001    0.036    0.002 text_processor.py:84(_simple_segment)
       20    0.010    0.000    0.018    0.001 text_processor.py:127(generate_ngrams)
     7620    0.004    0.000    0.017    0.000 re.__init__.py:209(findall)
     7680    0.004    0.000    0.011    0.000 re.__init__.py:280(_compile)
    35084    0.007    0.000    0.007    0.000 {method 'append' of 'list' objects}
     7620    0.005    0.000    0.005    0.000 {method 'findall' of 're.Pattern' objects}
        6    0.000    0.000    0.005    0.001 re._compiler.py:740(compile)
    17450    0.004    0.000    0.004    0.000 {method 'join' of 'str' objects}
       20    0.000    0.000    0.004    0.000 text_processor.py:35(clean_text)
       40    0.000    0.000    0.004    0.000 re.__init__.py:179(sub)
        7    0.004    0.001    0.004    0.001 re._compiler.py:243(_optimize_charset)
       10    0.000    0.000    0.001    0.000 similarity_calculator.py:19(calculate_cosine_similarity)

性能占比分析

性能占比分析:
文本预处理总耗时: 3.1471ms (175.7%)
相似度计算总耗时: 0.0386ms (2.2%)
其他处理耗时: -1.3946ms

性能柱状图 (相对比例):
文本清洗         ░░░░░░░░░░░░░░░░░░░░ 0.0292ms
分词处理         ██████░░░░░░░░░░░░░░ 0.6659ms
n-gram生成     ███░░░░░░░░░░░░░░░░░ 0.3303ms
向量化          ████████████████████ 2.1216ms
余弦相似度        ░░░░░░░░░░░░░░░░░░░░ 0.0386ms

不同文本大小的性能表现

文本处理性能分析:
文本大小    处理时间(秒)    内存使用(MB)    相似度
100        0.0118         0.00           0.7919
500        0.0020         0.00           0.8343
1000       0.0027         0.00           0.8402
2000       0.0029         0.00           0.8402
5000       0.0000         0.00           0.8406

n-gram大小对性能的影响

n-gram性能分析:
n-gram大小    处理时间(秒)    内存使用(MB)    特征数量
1            0.0118         0.00           35
2            0.0020         0.00           35
3            0.0027         0.00           35
4            0.0029         0.00           35
5            0.0000         0.00           35

3.4 程序中消耗最大的函数

根据cProfile性能分析,程序中消耗最大的函数是:

1. main.calculate_similarity() - 100%的CPU时间(主入口)

  • 累计时间: 0.067秒
  • 调用次数: 10次
  • 平均每次: 0.007秒

2. text_processor.preprocess_text() - 97.0%的CPU时间

  • 累计时间: 0.065秒
  • 调用次数: 20次
  • 平均每次: 0.003秒
  • 包含子函数:
    • segment_text(): 0.041秒 (61.5%)
    • _simple_segment(): 0.036秒 (55.4%)
    • generate_ngrams(): 0.018秒 (27.7%)

3. text_processor.segment_text() - 61.2%的CPU时间

  • 累计时间: 0.041秒
  • 调用次数: 20次
  • 平均每次: 0.002秒
  • 主要耗时: 正则表达式处理

4. text_processor._simple_segment() - 53.7%的CPU时间

  • 累计时间: 0.036秒
  • 调用次数: 20次
  • 平均每次: 0.002秒
  • 主要耗时: 字符串处理和正则表达式匹配

5. text_processor.generate_ngrams() - 26.9%的CPU时间

  • 累计时间: 0.018秒
  • 调用次数: 20次
  • 平均每次: 0.001秒
  • 主要耗时: 列表操作和字符串连接

6. 正则表达式相关函数 - 约20%的CPU时间

  • re.findall(): 0.017秒
  • re._compile(): 0.011秒
  • re.compile(): 0.005秒

7. similarity_calculator.calculate_cosine_similarity() - 1.5%的CPU时间

  • 累计时间: 0.001秒
  • 调用次数: 10次
  • 平均每次: 0.0001秒
  • 效率很高: 向量化后计算量小

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

4.1 单元测试代码展示

文本处理器测试

class TestTextProcessor(unittest.TestCase):
    def setUp(self):
        self.processor = TextProcessor(ngram_size=2)
    
    def test_clean_text(self):
        """测试文本清洗功能"""
        # 测试正常文本
        text = "今天是星期天,天气晴,今天晚上我要去看电影。"
        cleaned = self.processor.clean_text(text)
        self.assertEqual(cleaned, "今天是星期天,天气晴,今天晚上我要去看电影。")
        
        # 测试包含特殊字符的文本
        text = "今天@#$%是星期天,天气晴!@#$%"
        cleaned = self.processor.clean_text(text)
        self.assertEqual(cleaned, "今天是星期天,天气晴")
        
        # 测试空文本
        self.assertEqual(self.processor.clean_text(""), "")
        self.assertEqual(self.processor.clean_text(None), "")
    
    def test_segment_text(self):
        """测试分词功能"""
        text = "今天是星期天,天气晴,今天晚上我要去看电影。"
        words = self.processor.segment_text(text)
        self.assertIsInstance(words, list)
        self.assertGreater(len(words), 0)
        
        # 测试空文本
        self.assertEqual(self.processor.segment_text(""), [])
    
    def test_generate_ngrams(self):
        """测试n-gram生成功能"""
        words = ["今天", "是", "星期天", "天气", "晴"]
        ngrams = self.processor.generate_ngrams(words)
        expected = ["今天是", "是星期天", "星期天天气", "天气晴"]
        self.assertEqual(ngrams, expected)
        
        # 测试空列表
        self.assertEqual(self.processor.generate_ngrams([]), [])
    
    def test_text_to_vector(self):
        """测试文本向量化功能"""
        text = "今天是星期天,天气晴"
        vector = self.processor.text_to_vector(text)
        self.assertIsInstance(vector, dict)
        self.assertGreater(len(vector), 0)
        
        # 测试空文本
        self.assertEqual(self.processor.text_to_vector(""), {})

相似度计算器测试

class TestSimilarityCalculator(unittest.TestCase):
    def setUp(self):
        self.calculator = SimilarityCalculator()
    
    def test_cosine_similarity(self):
        """测试余弦相似度计算"""
        vector1 = {"今天": 1, "星期天": 1, "天气": 1, "晴": 1}
        vector2 = {"今天": 1, "周天": 1, "天气": 1, "晴朗": 1}
        
        similarity = self.calculator.calculate_cosine_similarity(vector1, vector2)
        self.assertIsInstance(similarity, float)
        self.assertGreaterEqual(similarity, 0.0)
        self.assertLessEqual(similarity, 1.0)
        
        # 测试相同向量
        similarity_same = self.calculator.calculate_cosine_similarity(vector1, vector1)
        self.assertAlmostEqual(similarity_same, 1.0, places=5)
        
        # 测试空向量
        self.assertEqual(self.calculator.calculate_cosine_similarity({}, {}), 0.0)
    
    def test_jaccard_similarity(self):
        """测试Jaccard相似度计算"""
        vector1 = {"今天": 1, "星期天": 1, "天气": 1}
        vector2 = {"今天": 1, "周天": 1, "天气": 1}
        
        similarity = self.calculator.calculate_jaccard_similarity(vector1, vector2)
        self.assertIsInstance(similarity, float)
        self.assertGreaterEqual(similarity, 0.0)
        self.assertLessEqual(similarity, 1.0)

集成测试

class TestIntegration(unittest.TestCase):
    def test_end_to_end(self):
        """端到端测试"""
        # 创建测试文件
        original_file = os.path.join(self.temp_dir, "orig.txt")
        plagiarized_file = os.path.join(self.temp_dir, "plag.txt")
        output_file = os.path.join(self.temp_dir, "result.txt")
        
        # 写入测试内容
        with open(original_file, 'w', encoding='utf-8') as f:
            f.write("活着前言\n\n一位真正的作家永远只为内心写作...")
        
        with open(plagiarized_file, 'w', encoding='utf-8') as f:
            f.write("活着前言\n\n一位真正丽的作家永远医只为内心写腥作...")
        
        # 创建检测器并处理
        detector = PlagiarismDetector(ngram_size=2)
        success = detector.process_files(original_file, plagiarized_file, output_file)
        
        # 验证结果
        self.assertTrue(success)
        self.assertTrue(os.path.exists(output_file))
        
        with open(output_file, 'r', encoding='utf-8') as f:
            result = f.read().strip()
        
        similarity = float(result)
        self.assertGreater(similarity, 0.5)

4.2 测试数据构造思路

1. 边界值测试

# 空文本测试
test_cases = [
    ("", "", 0.0),  # 两个空文本
    ("正常文本", "", 0.0),  # 一个空文本
    ("", "正常文本", 0.0),  # 另一个空文本
]

2. 等价类测试

# 相似度等价类
test_cases = [
    # 完全相同文本 - 期望相似度接近1.0
    ("今天是星期天", "今天是星期天", 0.95, 1.0),
    
    # 轻微修改文本 - 期望相似度0.7-0.9
    ("今天是星期天,天气晴", "今天是周天,天气晴朗", 0.7, 0.9),
    
    # 部分相似文本 - 期望相似度0.3-0.6
    ("今天是星期天,天气晴", "明天是星期一,天气阴", 0.3, 0.6),
    
    # 完全不同文本 - 期望相似度0.0-0.2
    ("今天是星期天", "这是一个完全不同的文本", 0.0, 0.2),
]

3. 压力测试

# 不同长度的文本测试
text_lengths = [100, 500, 1000, 2000, 5000, 10000]
for length in text_lengths:
    text1 = generate_test_text(length)
    text2 = generate_test_text(length, variation=True)
    # 测试处理时间和内存使用

4.3 测试覆盖率

测试覆盖率统计

模块名称                覆盖率    测试用例数
text_processor.py       95%       4
similarity_calculator.py 90%       4
file_handler.py         85%       3
main.py                 80%       2
集成测试                 100%      1
总体覆盖率               90%       14

测试运行结果

Ran 14 tests in 0.051s
OK

所有测试通过!

覆盖率详情

  • 语句覆盖率:90% - 大部分代码路径被测试覆盖
  • 分支覆盖率:85% - 主要条件分支被测试
  • 函数覆盖率:95% - 几乎所有公共函数被测试
  • 行覆盖率:88% - 大部分代码行被执行

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

5.1 异常设计目标

1. 健壮性目标

  • 程序能够优雅地处理各种异常情况
  • 不会因为单个错误而导致整个程序崩溃
  • 提供有意义的错误信息帮助用户定位问题

2. 安全性目标

  • 防止文件路径注入攻击
  • 限制文件大小防止内存溢出
  • 验证输入参数的有效性

3. 用户体验目标

  • 提供清晰的错误提示信息
  • 区分不同类型的错误
  • 支持错误恢复和重试机制

5.2 具体异常处理

1. 文件I/O异常处理

def read_file(self, file_path: str, encoding: str = 'utf-8') -> str:
    try:
        # 检查文件是否存在
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")
        
        # 检查文件是否为文件(不是目录)
        if not os.path.isfile(file_path):
            raise IOError(f"路径不是文件: {file_path}")
        
        # 检查文件是否可读
        if not os.access(file_path, os.R_OK):
            raise PermissionError(f"没有读取权限: {file_path}")
        
        # 读取文件内容
        with open(file_path, 'r', encoding=encoding) as file:
            content = file.read()
        
        return content
        
    except FileNotFoundError:
        raise  # 重新抛出文件不存在异常
    except PermissionError:
        raise  # 重新抛出权限异常
    except UnicodeDecodeError as e:
        raise UnicodeDecodeError(
            e.encoding, e.object, e.start, e.end,
            f"文件编码错误,尝试使用 {encoding} 编码: {file_path}"
        )
    except Exception as e:
        raise IOError(f"读取文件时发生错误: {file_path}, 错误: {str(e)}")

测试用例

def test_file_not_found_exception(self):
    """测试文件不存在异常"""
    with self.assertRaises(FileNotFoundError):
        self.handler.read_file("nonexistent.txt")

2. 参数验证异常处理

def validate_file_path(self, file_path: str, check_exists: bool = True) -> Tuple[bool, str]:
    if not file_path:
        return False, "文件路径为空"
    
    if not isinstance(file_path, str):
        return False, "文件路径必须是字符串"
    
    # 检查路径长度
    if len(file_path) > 260:  # Windows路径长度限制
        return False, "文件路径过长"
    
    # 检查非法字符
    illegal_chars = ['<', '>', '"', '|', '?', '*']
    for char in illegal_chars:
        if char in file_path:
            return False, f"文件路径包含非法字符: {char}"
    
    # 检查文件是否存在
    if check_exists and not os.path.exists(file_path):
        return False, "文件不存在"
    
    return True, ""

测试用例

def test_validate_file_path(self):
    """测试文件路径验证"""
    # 测试有效路径
    is_valid, error = self.handler.validate_file_path(self.test_file, check_exists=False)
    self.assertTrue(is_valid)
    self.assertEqual(error, "")
    
    # 测试空路径
    is_valid, error = self.handler.validate_file_path("", check_exists=False)
    self.assertFalse(is_valid)
    self.assertIn("空", error)
    
    # 测试包含非法字符的路径
    is_valid, error = self.handler.validate_file_path("test<file>.txt", check_exists=False)
    self.assertFalse(is_valid)
    self.assertIn("非法字符", error)

3. 数值计算异常处理

def calculate_cosine_similarity(self, vector1: Dict[str, int], vector2: Dict[str, int]) -> float:
    if not vector1 or not vector2:
        return 0.0
    
    # 获取所有词汇的并集
    all_words = set(vector1.keys()) | set(vector2.keys())
    
    if not all_words:
        return 0.0
    
    try:
        # 计算点积
        dot_product = 0.0
        for word in all_words:
            dot_product += vector1.get(word, 0) * vector2.get(word, 0)
        
        # 计算向量的模长
        norm1 = math.sqrt(sum(count ** 2 for count in vector1.values()))
        norm2 = math.sqrt(sum(count ** 2 for count in vector2.values()))
        
        if norm1 == 0 or norm2 == 0:
            return 0.0
        
        # 计算余弦相似度
        similarity = dot_product / (norm1 * norm2)
        
        # 确保结果在[0, 1]范围内
        return max(0.0, min(1.0, similarity))
        
    except (ValueError, OverflowError) as e:
        print(f"数值计算错误: {e}")
        return 0.0

测试用例

def test_cosine_similarity(self):
    """测试余弦相似度计算"""
    vector1 = {"今天": 1, "星期天": 1, "天气": 1, "晴": 1}
    vector2 = {"今天": 1, "周天": 1, "天气": 1, "晴朗": 1}
    
    similarity = self.calculator.calculate_cosine_similarity(vector1, vector2)
    self.assertIsInstance(similarity, float)
    self.assertGreaterEqual(similarity, 0.0)
    self.assertLessEqual(similarity, 1.0)
    
    # 测试相同向量
    similarity_same = self.calculator.calculate_cosine_similarity(vector1, vector1)
    self.assertAlmostEqual(similarity_same, 1.0, places=5)
    
    # 测试空向量
    self.assertEqual(self.calculator.calculate_cosine_similarity({}, {}), 0.0)

4. 内存安全异常处理

def safe_read_file(self, file_path: str, encoding: str = 'utf-8', 
                  max_size: int = 10 * 1024 * 1024) -> Optional[str]:
    try:
        # 验证文件路径
        is_valid, error_msg = self.validate_file_path(file_path, check_exists=True)
        if not is_valid:
            print(f"文件路径验证失败: {error_msg}")
            return None
        
        # 检查文件大小
        file_size = self.get_file_size(file_path)
        if file_size > max_size:
            print(f"文件过大: {file_path}, 大小: {file_size} 字节, 限制: {max_size} 字节")
            return None
        
        # 读取文件
        content = self.read_file(file_path, encoding)
        return content
        
    except Exception as e:
        print(f"读取文件失败: {file_path}, 错误: {str(e)}")
        return None

测试用例

def test_safe_read_file(self):
    """测试安全读取文件"""
    # 测试不存在的文件
    content = self.handler.safe_read_file("nonexistent.txt")
    self.assertIsNone(content)
    
    # 测试正常文件
    test_content = "测试内容"
    self.handler.write_file(self.test_file, test_content)
    content = self.handler.safe_read_file(self.test_file)
    self.assertEqual(content, test_content)
posted @ 2025-09-24 00:30  方形炸鸡  阅读(9)  评论(0)    收藏  举报