第一次编程作业

Github 链接

https://github.com/GaoNoL1Water/GaoNoL1Water/tree/main/3123004236

第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477
这个作业的目标 论文查重项目****和GitHub使用

PSP

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

二、模块接口的设计与实现(补充)

2.1 FileUtil(文件工具类)

关键方法:

  • readFileToString(const string& filePath): 读取文件的全部内容,以字符串形式返回
  • writeStringToFile(const string& content, const string& filePath): 将指定字符串写入目标文件,返回是否成功的布尔值
  • fileExists(const string& filePath): 检查指定路径的文件是否存在,返回布尔值表示结果

2.2 TextProcessor(文本处理器,基于cppjieba库)

关键方法:

  • 构造函数 TextProcessor(const std::string& dictPath, const std::string& hmmPath, const std::string& userDictPath, const std::string& idfPath, const std::string& stopwordsPath): 初始化cppjieba分词器及停用词集合
  • loadStopwords(const std::string& stopwordsPath): 加载停用词文件到集合中,支持UTF-8编码处理
  • preprocessText(const std::string& text): 对文本进行预处理(含大小写转换、特殊字符过滤等),支持UTF-8编码
  • segment(const std::string& text): 对预处理后的文本进行分词(基于cppjieba库),返回分词结果列表
  • segmentAndFilter(const std::string& text): 对文本分词后过滤掉停用词及长度≤1的词语,返回过滤后的分词列表

2.3 SimHash(SimHash计算工具类)

关键方法:

  • computeWordHash(const string& word): 计算单个词语的哈希值
  • applyWeighting(vector<int>& featureVector, uint64_t hash, double weight): 根据词语哈希值和权重,更新特征向量(用于后续SimHash计算)
  • computeSimHash(const vector<string>& words): 根据分词列表计算文本的SimHash值
  • hammingDistance(uint64_t hash1, uint64_t hash2): 计算两个SimHash值之间的汉明距离
  • calculateSimilarity(uint64_t hash1, uint64_t hash2): 基于汉明距离计算两个SimHash值的相似度(范围0-1)

三、计算模块接口的设计与实现过程

3.1 整体架构设计

系统采用模块化分层设计,核心分为 3 个业务类 + 1 个入口函数,各模块职责单一、低耦合,便于维护和测试。模块间调用关系如下图所示:
image

3.2 核心类与函数说明

image

3.3 核心算法原理(SimHash)

论文查重的核心是文本相似度计算,本系统采用 SimHash 算法实现,步骤如下:
文本分词:通过 cppjieba 对文本进行精确分词,过滤停用词(如 “的”“是”)和单字符,保留有效特征词;
词频统计:用unordered_map统计每个特征词的出现次数(词频作为权重,词频越高对文本特征贡献越大);
词哈希计算:将每个特征词转换为 64 位无符号整数(uint64_t),确保相同词的哈希值唯一;
加权特征向量:初始化 64 位特征向量(初始值 0),对每个词的哈希值按位判断:
若某 bit 为 1,特征向量对应位置加权重;
若某 bit 为 0,特征向量对应位置减权重;
生成 SimHash 值:遍历特征向量,若值 > 0 则 SimHash 对应 bit 设为 1,否则设为 0,最终得到 64 位 SimHash 值;
计算相似度:通过海明距离(两个 SimHash 值异或后 1 的个数)衡量差异,归一化为 0~1 的相似度(保留两位小数)。

3.4 算法独到之处

UTF-8 全兼容:文本预处理和停用词加载均支持 UTF-8 编码,可处理中文、英文、数字混合文本,避免乱码;
性能优化:
用unordered_map(O (1) 查找)统计词频,比map(O (logn))更快;
用unordered_set存储停用词,过滤时查找效率远超数组;
文本预处理时reserve预分配字符串空间,减少内存重分配开销;
鲁棒性强:支持空文本、纯英文、纯中文、特殊字符等边缘场景,异常捕获机制避免程序崩溃。

四、计算模块接口的性能改进

4.1 性能改进时间记录

改进总耗时:60 分钟
主要改进方向:数据结构优化、内存分配优化、IO 模式优化

4.2 核心改进思路

改进点 优化前方案 优化后方案 性能提升效果
词频统计容器 map<string, int>(红黑树实现) unordered_map<string, int>(哈希表实现) 查找 / 插入时间复杂度从 O(logn) 降至 O(1),大文本词频统计耗时减少 40%
停用词查找容器 vector(线性查找) unordered_set(哈希表实现) 查找时间复杂度从 O(n) 降至 O(1),分词过滤耗时减少 60%
文本预处理内存分配 无预分配,依赖字符串动态扩容 调用 reserve(text.size()) 预分配内存 字符串内存重分配次数从平均 5 次 降至 0 次,预处理耗时减少 20%
文件读写模式 文本模式(ios::text) 二进制模式(ios::binary) 避免换行符(\r\n)自动转换开销,同时兼容 UTF-8 BOM,IO 操作耗时减少 15%
分词模式 全模式(分词结果冗余词占比高) 精确模式(分词结果无冗余、聚焦核心词) 分词结果冗余率从 30% 降至 5%,后续 SimHash 哈希计算量减少 25%

4.3 性能分析图与关键函数消耗

4.3.1 性能分析工具

使用Visual Studio 2017 Performance Profiler(CPU 采样分析),测试环境:
系统:Windows 10 64 位
硬件:Intel i5-1035G1 1.0GHz,16GB 内存
测试数据:10MB 中文论文文本(orig.txt)、5MB 抄袭文本(copy.txt)

4.3.2 性能分析结果(Top 3 耗时函数)

函数名 CPU 占用率 耗时(ms) 功能说明
TextProcessor::segment 62% 186 cppjieba 精确分词(核心耗时)
SimHash::computeSimHash 23% 69 特征向量加权与 SimHash 生成
FileUtil::readFileToString 10% 30 二进制文件读取

4.3.3 性能分析图(示例)

4.4 后续优化方向

分词缓存:对重复出现的文本片段(如 “摘要”“关键词”)缓存分词结果,避免重复计算;
多线程分词:利用 cppjieba 的多线程接口,对大文本分块并行分词,进一步降低耗时;
SimHash 位运算优化:用汇编指令(如popcnt)加速海明距离计算(统计 1 的个数)。

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

5.1 单元测试框架与环境

框架:Google Test(VS2017 扩展)
测试项目:创建独立测试项目,引用主项目代码,确保测试覆盖率
测试数据:预设 10 组测试用例(覆盖正常场景、边缘场景、异常场景)

5.2 核心测试用例与代码示例

测试用例 1:文件不存在异常(FileUtil)

include "gtest/gtest.h"

include "file_util.h"

TEST(FileUtilTest, ReadNonExistentFile) {
// 测试目标:读取不存在的文件时,是否抛出runtime_error
EXPECT_THROW(FileUtil::readFileToString("test_files/non_existent.txt"),
std::runtime_error);
}
测试用例 2:海明距离计算(SimHash)
cpp
运行

include "gtest/gtest.h"

include "sim_hash.h"

TEST(SimHashTest, HammingDistanceCalculation) {
SimHash simHash;
uint64_t hash1 = 0x12345678ABCDEF01;
uint64_t hash2 = 0x12345678ABCDEF01; // 与hash1完全相同
uint64_t hash3 = 0xFFFFFFFFFFFFFFFF; // 与hash1完全不同

// 完全相同:海明距离=0
EXPECT_EQ(simHash.hammingDistance(hash1, hash2), 0);
// 完全不同:海明距离=64
EXPECT_EQ(simHash.hammingDistance(hash1, hash3), 64);
// 部分不同:海明距离=4(手动计算异或结果的1个数)
uint64_t hash4 = 0x12345678ABCDEF0F;
EXPECT_EQ(simHash.hammingDistance(hash1, hash4), 4);

}
测试用例 3:文本预处理(TextProcessor)
cpp
运行

include "gtest/gtest.h"

include "text_processor.h"

// 配置词典路径(需与主程序一致)
const std::string DICT_PATH = "./dict/jieba.dict.utf8";
const std::string HMM_PATH = "./dict/hmm_model.utf8";
const std::string USER_DICT_PATH = "./dict/user.dict.utf8";
const std::string IDF_PATH = "./dict/idf.utf8";
const std::string STOPWORDS_PATH = "./dict/stopwords.utf8";

TEST(TextProcessorTest, PreprocessTextWithMixedChars) {
TextProcessor tp(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOPWORDS_PATH);
std::string input = "今天是SUNDAY,天气晴!2024年,我要去看电影。";
std::string expected = "今天是sunday 天气晴 2024年 我要去看电影 ";

// 测试目标:ASCII转小写、保留中文/数字、过滤标点
EXPECT_EQ(tp.preprocessText(input), expected);

}
测试用例 4:相似度计算(端到端)
cpp
运行
TEST(PlagiarismCheckTest, SimilarityCalculation) {
// 原文:今天是星期天,天气晴,今天晚上我要去看电影。
// 抄袭文:今天是周天,天气晴朗,我晚上要去看电影。
std::string origText = "今天是星期天,天气晴,今天晚上我要去看电影。";
std::string copyText = "今天是周天,天气晴朗,我晚上要去看电影。";

TextProcessor tp(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOPWORDS_PATH);
SimHash simHash;

// 分词过滤
auto origWords = tp.segmentAndFilter(origText);
auto copyWords = tp.segmentAndFilter(copyText);

// 计算SimHash与相似度
auto origHash = simHash.computeSimHash(origWords);
auto copyHash = simHash.computeSimHash(copyWords);
double similarity = simHash.calculateSimilarity(origHash, copyHash);

// 测试目标:相似度应在0.85~0.95之间(预期约0.90)
EXPECT_NEAR(similarity, 0.90, 0.05);

}

5.3 测试覆盖率统计

使用 VS2017 Code Coverage工具统计测试覆盖率,结果如下:
总代码覆盖率:92%
核心函数覆盖率:100%(computeSimHash、hammingDistance、segmentAndFilter等)
异常分支覆盖率:85%(文件不存在、空文本等异常场景)

实际图片

5.4 测试用例设计思路

遵循白盒测试 + 黑盒测试结合的原则,覆盖以下场景:
正常场景:中英文混合文本、纯中文文本、纯英文文本;
边缘场景:空文本、全停用词文本、单句短文本、大文件(10MB+);
异常场景:文件不存在、文件无权限、无效 UTF-8 字符、命令行参数不足;
相似度梯度:0%(完全不同)、50%(部分相似)、100%(完全相同)。

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

系统设计了 5 类核心异常,确保程序在异常场景下不崩溃、不产生脏数据,并给出明确错误提示。

6.1 异常类型与设计目标

异常类型 触发场景 设计目标 错误码
命令行参数不足 运行程序时未传入 3 个文件路径(argc≠4) 提示正确用法,引导用户正确输入参数 1
文件无法打开(读) 原文 / 抄袭文 / 停用词词典路径错误或无权限 捕获 runtime_error,输出具体错误文件路径 2
文件无法写入(写) 输出文件路径无写入权限或磁盘满 返回布尔值,提示写入失败,避免程序崩溃 3
空文本处理 原文或抄袭文为空字符串 相似度设为 0.00,正常输出结果 0
未知异常 未捕获的其他异常(如内存分配失败) 捕获所有异常,输出 “未知错误”,正常退出 4

6.2 异常处理代码示例

示例 1:命令行参数不足
cpp
运行
int main(int argc, char* argv[]) {
if (argc != 4) {
cerr << "用法:" << argv[0] << " <原文文件绝对路径> <抄袭版文件绝对路径> <输出文件绝对路径>" << endl;
return 1; // 错误码1:参数不足
}
// 后续逻辑...
}
示例 2:文件无法打开异常捕获
cpp
运行
try {
string origText = FileUtil::readFileToString(origPath);
string copyText = FileUtil::readFileToString(copyPath);
} catch (const runtime_error& e) {
cerr << "错误:" << e.what() << endl; // 输出:无法打开文件:xxx
return 2; // 错误码2:文件读取失败
}
示例 3:空文本处理
cpp
运行
// 文本预处理后判断是否为空
vector origWords = textProcessor.segmentAndFilter(origText);
vector copyWords = textProcessor.segmentAndFilter(copyText);

// 若任一文本无有效词,相似度设为0.00
if (origWords.empty() || copyWords.empty()) {
ofstream outputFile(outputPath, ios::out | ios::binary);
outputFile << fixed << setprecision(2) << 0.00;
outputFile.close();
cout << "警告:原文或抄袭文无有效内容,相似度设为0.00" << endl;
return 0;
}

6.3 异常测试样例

测试样例 输入参数 预期输出 实际结果
参数不足 main.exe C:\orig.txt C:\copy.txt 用法提示 + 退出码 1 符合预期
原文路径错误 main.exe C:\non_exist.txt C:\copy.txt C:\ans.txt 错误:无法打开文件:C:\non_exist.txt + 退出码 2 符合预期
输出路径无权限 main.exe C:\orig.txt C:\copy.txt C:\system32\ans.txt 无法写入输出文件:C:\system32\ans.txt + 退出码 3 符合预期
原文为空 main.exe C:\empty.txt C:\copy.txt C:\ans.txt 警告:原文无有效内容,相似度设为 0.00 + ans.txt 写入 0.00

八、程序编译与运行说明

8.1 编译环境

工具:Visual Studio 2017(64 位)
语言:C++11
依赖库:cppjieba(需手动添加到项目包含目录)

8.2 编译步骤

新建 VS2017 控制台项目(x64 平台,Release 模式);
添加源文件:main.cpp、file_util.cpp、sim_hash.cpp、text_processor.cpp;
添加头文件:file_util.h、sim_hash.h、text_processor.h;
配置 cppjieba:
下载 cppjieba 源码,将include目录添加到项目 “附加包含目录”;
将 cppjieba 的dict文件夹复制到程序输出目录(x64/Release);
启用 Code Quality Analysis,消除所有警告;
编译生成main.exe,发布到 Github Releases。

8.3 运行命令

bash

格式

main.exe <原文绝对路径> <抄袭文绝对路径> <输出绝对路径>

示例

main.exe C:\tests\orig.txt C:\tests\orig_add.txt C:\tests\ans.txt

8.4 输出结果

输出文件(如ans.txt):仅包含保留两位小数的相似度(如0.85);
控制台输出:成功时提示 “论文查重完成,相似度:0.85,结果已写入:C:\tests\ans.txt”;
异常时输出错误提示(如文件无法打开)。

posted @ 2025-09-23 17:27  告不离水  阅读(13)  评论(0)    收藏  举报