个人项目-论文查重
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023 |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023/homework/13324 |
| 这个作业的目标 | 体验个人项目创建的流程,了解论文查重的原理所在,理解Git与GitHub的链接关系 |
一、GitHub仓库地址
二、PSP表
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 25 | 40 |
| Estimate | 估计这个任务需要多少时间 | 20 | 30 |
| Development | 开发 | 200 | 350 |
| Analysis | 需求分析 (包括学习新技术) | 300 | 350 |
| Design Spec | 生成设计文档 | 20 | 35 |
| Design Review | 设计复审 | 25 | 30 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 35 |
| Design | 具体设计 | 20 | 40 |
| Coding | 具体编码 | 140 | 180 |
| Code Review | 代码复审 | 20 | 30 |
| Test | 测试(自我测试,修改代码,提交修改) | 250 | 290 |
| Reporting | 报告 | 90 | 120 |
| Test Repor | 测试报告 | 40 | 50 |
| Size Measurement | 计算工作量 | 40 | 60 |
| Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 40 | 45 |
| 合计 | 1260 | 1685 |
三、题目需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
四、代码实现
1.核心算法
❗TF-IDF 算法:TF-IDF 通过结合词频(TF)和逆文档频率(IDF)衡量词的重要性,TF 表示词在文档中的频率,IDF 衡量词在整个文档集合中的稀有性。TF-IDF 值越高,词越重要。
❗余弦相似度:余弦相似度 用于衡量两个向量的相似性,通过计算它们的夹角余弦值,范围在 [−1,1] 之间,1 表示完全相同,0 表示无相似性。
2.实现流程图

3.具体代码
🧩头文件PlagiarismChecker.h
#ifndef PLAGIARISMCHECKER_H
#define PLAGIARISMCHECKER_H
#include <string>
#include <vector>
#include <map>
class PlagiarismChecker {
public:
// 构造函数:初始化原文和抄袭版论文的路径
PlagiarismChecker(const std::string& origFile, const std::string& plagFile);
// 计算相似度并输出结果
void calculateAndSaveSimilarity(const std::string& outputFile);
private:
// 读取文件内容(支持UTF-8编码)
std::string readFile(const std::string& filePath);
// 将文本按字符分词
std::vector<std::string> tokenize(const std::string& text);
// 计算词频(TF)
std::map<std::string, int> computeTF(const std::vector<std::string>& words);
// 计算逆文档频率(IDF)
std::map<std::string, double> computeIDF(const std::vector<std::vector<std::string>>& documents);
// 计算TF-IDF向量
std::vector<double> computeTFIDF(const std::vector<std::string>& words,
const std::map<std::string, int>& tf,
const std::map<std::string, double>& idf);
// 计算余弦相似度
double cosineSimilarity(const std::vector<double>& vec1, const std::vector<double>& vec2);
// 原文和抄袭版论文的内容
std::string origText;
std::string plagText;
};
#endif // PLAGIARISMCHECKER_H
🧩源文件main.cpp
#include "PlagiarismChecker.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <codecvt>
#include <locale>
using namespace std;
// 构造函数:读取原文和抄袭版论文的内容
PlagiarismChecker::PlagiarismChecker(const string& origFile, const string& plagFile) {
origText = readFile(origFile); // 读取原文
plagText = readFile(plagFile); // 读取抄袭版论文
}
// 读取UTF-8编码的文件
string PlagiarismChecker::readFile(const string& filePath) {
wifstream wif(filePath);
wif.imbue(locale(locale(), new codecvt_utf8<wchar_t>)); // 设置UTF-8编码
wstringstream wss;
wss << wif.rdbuf();
wstring wstr = wss.str();
// 将wstring转换为string(UTF-8)
wstring_convert<codecvt_utf8<wchar_t>> converter;
return converter.to_bytes(wstr);
}
// 按字符分词
vector<string> PlagiarismChecker::tokenize(const string& text) {
vector<string> tokens;
for (char ch : text) {
if (ch != ' ' && ch != '\n' && ch != '\r') { // 忽略空格和换行符
tokens.push_back(string(1, ch));
}
}
return tokens;
}
// 计算词频(TF)
map<string, int> PlagiarismChecker::computeTF(const vector<string>& words) {
map<string, int> tf;
for (const string& word : words) {
tf[word]++;
}
return tf;
}
// 计算逆文档频率(IDF)
map<string, double> PlagiarismChecker::computeIDF(const vector<vector<string>>& documents) {
map<string, double> idf;
int totalDocs = documents.size();
for (const auto& doc : documents) {
for (const string& word : doc) {
idf[word] = 0;
}
}
for (const auto& doc : documents) {
for (const string& word : doc) {
idf[word]++;
}
}
for (auto& pair : idf) {
pair.second = log10(totalDocs / pair.second);
}
return idf;
}
// 计算TF-IDF向量
vector<double> PlagiarismChecker::computeTFIDF(const vector<string>& words,
const map<string, int>& tf,
const map<string, double>& idf) {
vector<double> tfidf;
for (const string& word : words) {
tfidf.push_back(tf.at(word) * idf.at(word));
}
return tfidf;
}
// 计算余弦相似度
double PlagiarismChecker::cosineSimilarity(const vector<double>& vec1, const vector<double>& vec2) {
double dotProduct = 0.0, magnitude1 = 0.0, magnitude2 = 0.0;
for (size_t i = 0; i < vec1.size(); ++i) {
dotProduct += vec1[i] * vec2[i];
magnitude1 += vec1[i] * vec1[i];
magnitude2 += vec2[i] * vec2[i];
}
magnitude1 = sqrt(magnitude1);
magnitude2 = sqrt(magnitude2);
if (magnitude1 == 0 || magnitude2 == 0) {
return 0.0;
}
return dotProduct / (magnitude1 * magnitude2);
}
// 计算相似度并输出结果
void PlagiarismChecker::calculateAndSaveSimilarity(const string& outputFile) {
// 分词
vector<string> origTokens = tokenize(origText);
vector<string> plagTokens = tokenize(plagText);
// 计算TF
map<string, int> origTF = computeTF(origTokens);
map<string, int> plagTF = computeTF(plagTokens);
// 计算IDF
vector<vector<string>> documents = {origTokens, plagTokens};
map<string, double> idf = computeIDF(documents);
// 计算TF-IDF向量
vector<double> origTFIDF = computeTFIDF(origTokens, origTF, idf);
vector<double> plagTFIDF = computeTFIDF(plagTokens, plagTF, idf);
// 计算余弦相似度
double similarity = cosineSimilarity(origTFIDF, plagTFIDF);
// 输出结果到文件
ofstream outFile(outputFile);
outFile << fixed << setprecision(2) << similarity << endl;
}
// 主函数:处理命令行参数并调用查重功能
int main(int argc, char* argv[]) {
// 检查命令行参数数量
if (argc != 4) {
cerr << "Usage: " << argv[0] << " <original_file> <plagiarized_file> <output_file>" << endl;
return 1;
}
try {
// 创建查重对象
PlagiarismChecker checker(argv[1], argv[2]);
// 计算相似度并保存结果
checker.calculateAndSaveSimilarity(argv[3]);
cout << "Similarity calculation completed successfully." << endl;
} catch (const exception& e) {
cerr << "Error: " << e.what() << endl;
return 1;
}
return 0;
}
4.运行结果
🧩原文

🧩copy01

🧩相似度01

🧩copy02

🧩相似度02

🧩copy03

🧩相似度03

🧩copy04

🧩相似度04

🧩copy05

🧩相似度05

五、性能分析
1.性能分析图

2.性能瓶颈
- CPU使用率:程序的CPU使用率在分析期间保持在较低水平,但特定DLL的函数占用了大部分的CPU时间。
- 函数调用:Qt5CoreKso.dll 中的函数是执行单个工作最多的函数,独占样本数百分比最高。
3.优化措施
- 减少对Qt5CoreKso.dll的调用:
- 通过缓存结果或合并调用,减少对这些函数的调用次数。
- 检查并优化程序中的算法,降低复杂度。
- 使用缓冲技术和异步I/O操作,减少磁盘和网络访问次数。
六、异常处理
1.文件读写错误
std::string readFile(const std::string& filePath) {
std::ifstream file(filePath);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filePath);
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void writeUTF8File(const std::string& filePath, const std::string& content) {
std::ofstream file(filePath);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filePath);
}
file << content;
}
* 文件不存在:尝试打开一个不存在的文件时,可能会抛出异常。
* 权限不足:没有足够的权限读取或写入文件。
* 磁盘空间不足:磁盘空间不足以完成文件读写操作。
2.数据格式错误
std::vector<std::string> tokenize(const std::string& text) {
std::vector<std::string> tokens;
for (char ch : text) {
if (ch != ' ' && ch != '\n' && ch != '\r') {
tokens.push_back(std::string(1, ch));
}
}
return tokens;
}
* 输入数据格式不正确:例如,非数字字符在数字字段中,或者文本数据中包含非法字符。
3.算法错误
double cosineSimilarity(const std::vector<double>& vec1, const std::vector<double>& vec2) {
double dotProduct = 0.0, magnitude1 = 0.0, magnitude2 = 0.0;
for (size_t i = 0; i < vec1.size(); ++i) {
dotProduct += vec1[i] * vec2[i];
magnitude1 += vec1[i] * vec1[i];
magnitude2 += vec2[i] * vec2[i];
}
magnitude1 = sqrt(magnitude1);
magnitude2 = sqrt(magnitude2);
if (magnitude1 == 0 || magnitude2 == 0) {
return 0.0;
}
return dotProduct / (magnitude1 * magnitude2);
}
* 算法实现中的逻辑错误:例如,除以零、数组越界等。
4.资源管理错误
std::ifstream file(filePath);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filePath);
}
// 使用文件进行操作
file.close(); // 关闭文件
* 内存泄漏:动态分配的内存没有被正确释放。
* 资源未释放:打开的文件、网络连接等资源没有被正确关闭。
七、单元测试
测试用例
#include "stdafx.h" // 包含预编译头文件,加快编译速度
#include "CppUnitTest.h" // 包含CppUnitTest框架的头文件
#include "PlagiarismChecker.h" // 包含被测试的PlagiarismChecker类的头文件
using namespace Microsoft::VisualStudio::CppUnitTestFramework; // 使用CppUnitTest框架的命名空间
// 定义测试类
namespace PlagiarismCheckerTests
{
TEST_CLASS(PlagiarismCheckerTests)
{
public:
// 测试计算词频(TF)的测试用例
TEST_METHOD(CalculateTFTest)
{
// Arrange: 准备测试数据
std::vector<std::string> words = {"hello", "world", "hello"};
std::map<std::string, int> expectedTF = {{"hello", 2}, {"world", 1}};
// Act: 调用被测试的函数
std::map<std::string, int> actualTF = PlagiarismChecker::computeTF(words);
// Assert: 验证结果是否符合预期
Assert::IsTrue(actualTF.size() == expectedTF.size());
for (const auto& pair : expectedTF)
{
Assert::IsTrue(actualTF.count(pair.first) > 0);
Assert::AreEqual(pair.second, actualTF.at(pair.first));
}
}
// 测试计算逆文档频率(IDF)的测试用例
TEST_METHOD(CalculateIDFTest)
{
// Arrange: 准备测试数据
std::vector<std::vector<std::string>> documents = {
{"hello", "world"},
{"hello", "hello"}
};
std::map<std::string, double> expectedIDF = {{"hello", std::log10(2.0)}, {"world", std::log10(1.0)}};
// Act: 调用被测试的函数
std::map<std::string, double> actualIDF = PlagiarismChecker::computeIDF(documents);
// Assert: 验证结果是否符合预期
Assert::IsTrue(actualIDF.size() == expectedIDF.size());
for (const auto& pair : expectedIDF)
{
Assert::IsTrue(actualIDF.count(pair.first) > 0);
Assert::IsTrue(std::abs(actualIDF.at(pair.first) - pair.second) < 1e-6); // 允许一些浮点误差
}
}
// 测试计算TF-IDF向量的测试用例
TEST_METHOD(ComputeTFIDFTest)
{
// Arrange: 准备测试数据
std::vector<std::string> words = {"hello", "world", "hello"};
std::map<std::string, int> tf = {{"hello", 2}, {"world", 1}};
std::map<std::string, double> idf = {{"hello", std::log10(2.0)}, {"world", std::log10(1.0)}};
// Act: 调用被测试的函数
std::vector<double> actualTFIDF = PlagiarismChecker::computeTFIDF(words, tf, idf);
// Assert: 验证结果是否符合预期
std::vector<double> expectedTFIDF = {2.0 * std::log10(2.0), 1.0 * std::log10(1.0)};
Assert::IsTrue(actualTFIDF.size() == expectedTFIDF.size());
for (size_t i = 0; i < actualTFIDF.size(); ++i)
{
Assert::IsTrue(std::abs(actualTFIDF[i] - expectedTFIDF[i]) < 1e-6); // 允许一些浮点误差
}
}
// 测试计算余弦相似度的测试用例
TEST_METHOD(CosineSimilarityTest)
{
// Arrange: 准备测试数据
std::vector<double> vec1 = {0.0, 1.0, 2.0};
std::vector<double> vec2 = {1.0, 0.0, 1.0};
// Act: 调用被测试的函数
double similarity = PlagiarismChecker::cosineSimilarity(vec1, vec2);
// Assert: 验证结果是否符合预期
Assert::AreEqual(0.0, similarity, L"Tolerance", 0.0001);
}
};
}
八、Git日志截图

浙公网安备 33010602011771号