第一次个人编程作业

这个作业属于哪个课程 软件工程
这个作业要求在哪里 作业要求
这个作业的目标 完成个人项目:论文查重
作业GitHub网址 https://github.com/ww7c/www7c

一、psp表格

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

二、计算模块接口的设计与实现过程
本程序的核心计算模块主要负责文本预处理、3-Gram 特征提取以及文本相似度计算。为了提高程序的模块化程度,将不同功能分别封装在不同的类中,使文本处理和相似度计算相互独立,便于后续测试和维护。

代码共有三个核心类

类 对应模块 主要功能
TextPreprocessor 文本预处理模块 去空格、去标点、UTF-8 文本预处理
TextSimilarity 3-Gram + 相似度计算模块 生成 3-Gram、计算 Jaccard 相似度
FileManager 文件读写模块 读取文本文件、输出查重结果

文本预处理模块:
接口
class TextPreprocessor { public: static std::string process(const std::string& text); };
功能:文本预处理模块负责对读取到的文本进行规范化处理(去除空白字符,保留有效的中文,英文及数字字符。)返回处理后的文本。

3-Gram 哈希表模块
接口
class TextSimilarity { public: static std::set<std::string> generateNGram( const std::string& text, int n = 3 ); };
功能、
3-Gram 模块负责将预处理后的文本按照连续的三个UTF-8 字符划分为 3-Gram 特征。

相似度计算模块
接口
class TextSimilarity { public: static double calculateJaccard( const std::set<std::string>& setA, const std::set<std::string>& setB ); };
功能
相似度计算模块负责根据两篇文本生成的 3-Gram 特征集合计算文本相似度。

文件读写模块
接口
class FileManager { public: static std::string readFile(const std::string& filePath);
static bool writeResult(const std::string& filePath, double result); };
功能
文件读写模块负责程序与文本文件之间的数据交互。

流程图:

未命名文件 (6)

算法关键
本程序算法的核心是UTF-8 字符级 N-Gram + 集合 + Jaccard 相似度。
首先,将文本按照 UTF-8 字符进行切分,然后通过滑动窗口生成连续的 3-Gram 特征,每三个连续字符组成一个特征。随后将这些特征存入集合中,最后,通过计算两个文本 N-Gram 集合的交集和并集,使用 Jaccard 公式得到两篇文本的相似程度。

本设计的独到之处

  1. 采用模块化设计
    将文本预处理和相似度计算分离到不同类中,代码可读性更高。
  2. 采用 UTF-8 字符级 N-Gram
    不直接按照 std::string 的字节进行划分,而是先识别 UTF-8 字符,避免中文字符被错误拆分。
  3. 使用 std::set 保存 N-Gram
    去除重复特征,同时方便后续进行集合交集、并集计算。
  4. 采用 Jaccard 相似度进行计算
    算法原理简单,可以直观反映两篇文本的特征重合程度。

三、计算模块接口部分的性能改进
性能分析图由VS 2022的性能分析工具自动生成:

屏幕截图 2026-09-15 140323

cpu火焰图
image

排名 函数 / 模块名称 CPU 总计
1 TextSimilarity::generateNGram 25.00%
2 TextSimilarity::calculateJaccard 22.2%
3 FileManager::readFile 8.33%

优化方案:
根据 VS 2022 性能分析结果,程序运行过程中消耗 CPU 时间最多的函数为 TextSimilarity::generateNGram(),其 CPU 时间占比约为 33.33%。分析其代码后发现,主要开销来自 UTF-8 字符处理、3-Gram 字符串构造以及 std::set 插入。针对上述问题,对 vector 和 string 进行预分配,,可降低 3-Gram 生成过程中的开销。

四、计算模块部分单元测试展示

部分测试单元代码

屏幕截图 2026-09-15 155340

屏幕截图 2026-09-15 155425

单元测试得到的测试覆盖率截图
image

五、计算模块部分异常处理说明

对于非法 N-Gram 长度、空文本以及文本长度不足等情况,程序提前终止计算并返回空集合;对于 Jaccard 相似度计算中的空集合情况,程序提前判断,避免出现除零错误。

异常一:非法N-Gram 长度
如果传入:generateNGram(text, 0);generateNGram(text, -1);则 N-Gram 长度没有实际意义。

设计目标:该异常处理的目标是避免程序对不足以生成 N-Gram 的文本进行滑动窗口操作。
代码:
if (text.length() < static_cast<size_t>(n)) { return nGramSet; }
当文本长度不足时,函数直接返回空集合。

单元测试用例
std::string text = "abcdef";
std::set<std::string> result =TextSimilarity::generateNGram(text, 0);
预期结果:N-Gram数量:0

错误对应场景:
当用户或者其他模块传入 n=0 或负数时,该测试用于验证计算模块能否正确识别非法 N-Gram 长度,并安全返回空集合。

异常二:空文本异常
如果经过文本预处理之后,文本变成空字符串,例如原始文本只有空格、换行或者其他被过滤掉的内容:原文本: 预处理后:""
此时没有任何有效字符可以用于生成 N-Gram。

设计目标: 程序不应该因为空文本而崩溃,也不应该生成 N-Gram。因此可以在 generateNGram() 中直接判断:
if (text.empty()) { return nGramSet; }

单元测试样例:
测试输入:
std::string text = "";
std::set<std::string> result = TextSimilarity::generateNGram(text, 3);
预期结果:
N-Gram数量:0

错误对应场景:
当用户输入空文本,或者文本经过预处理后没有剩余有效内容时,该测试用于验证计算模块是否能够正常处理空输入。

异常三: 两篇文本均为空时的相似度计算
如果两篇文本都为空,那么:
A = {}B = {}
此时:
|A ∩ B| = 0
|A ∪ B| = 0
会出现:
0 / 0
这是一个没有定义的数学运算。

设计目标:为了避免出现除零错误,需要在计算 Jaccard 相似度之前判断并集是否为空。

单元测试样例
std::set<std::string> set1; std::set<std::string> set2;
double result = TextSimilarity::calculateJaccard(set1, set2);
预期结果相似度:0

错误对应场景:
当两篇文本经过预处理和 N-Gram 提取后都没有产生有效特征时,如果直接按照 Jaccard 公式计算,会产生 0/0。

posted @ 2026-09-15 19:05  www7c  阅读(14)  评论(0)    收藏  举报