这个作业属于哪个课程 <计算21级 (集美大学)>
这个作业要求在哪里 <个人项目(第二次作业)>
这个作业的目标 <学习项目搭建,学习GitHub的运用,学习设计论文查重代码>

个人项目《用C++实现论文查重程序》

本次项目 github 项目链接:(https://github.com/lifeteatable/teatable/tree/master/202121331110)

需求分析

题目:论文查重

描述如下:

设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。

原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:

从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。

注意:答案文件中输出的答案为浮点型,精确到小数点后两位

开发环境

编译环境:Vscode

编译语言:C++

算法分析

本程序运用余弦相似度进行设计来实现论文代码的查重
1.先通过字符流把文件内容存入字符串流中
2.经过预处理,用正则表达式模式匹配,去除标点符号和空格,只保留字符
3.建立两个vector容器,分别将两个文件的内容存入其中,生成两个词频向量
4.运用迭代器逐一遍历两个向量,通过余弦相似度公式求出相似度
5.根据设定的标准,判断是否有抄袭

项目结构

整体框架

项目结构

函数说明

main() --> int 主体代码,用于处理文件打开异常,以及处理输入
createWordFrequencyVector(const std::string& text) --> std::unordered_map<std::string, int> 创建词频向量函数,根据输入的字符串,将其存入vector容器中,最后返回一个词频向量(键为字符串,值为字符串出现的次数)
calculateCosineSimilarity(const std::unordered_map<std::string, int>& vector1, const std::unordered_map<std::string, int>& vector2) --> double 计算余弦相似度函数,根据输入的两个词频向量,根据余弦相似度算法,算出相似度

算法关键

1.正则表达式匹配字符串
通过正则表达式R"([\w'-]+)"匹配字符串,用于去除空格与标点符号

    std::unordered_map<std::string, int> vector;
    std::regex pattern(R"([\w'-]+)");//用正则表达式匹配字符串,用于去除空格和标点符号
    std::sregex_iterator iter(text.begin(), text.end(), pattern);//用迭代器来迭代字符串,用于遍历字符串中所有符合的项
    std::sregex_iterator end;
    std::string word;
    while (iter != end) 
    {
        std::smatch match = *iter;
        std::string word = match.str();
        std::cout << word << std::endl;
        // 转换为小写以忽略大小写
        for (char& c : word) {
            c = tolower(c);
        }
        vector[word]++;
        ++iter;
    }

2.计算余弦相似度
余弦值=(两个向量的内积)/[(向量一的模长)*(向量二的模长)](即余弦公式),通过这个方式,来计算两个生成的向量的相似度

    double dotProduct = 0.0; //用于存放两个向量的点积
    double norm1 = 0.0; //向量一的模长的平方
    double norm2 = 0.0; //向量二的模长的平方

    for (const auto& entry : vector1)
    {
        const std::string& word = entry.first;//在向量一获得键值,即获得单词
        int freq1 = entry.second; //获得该单词的词频
        if (vector2.count(word))
        { //如果在向量二中也有该单词
            int freq2 = vector2.at(word); //获得该单词的词频
            dotProduct += freq1 * freq2; //增加点积值
        }
        norm1 += freq1 * freq1; //增加向量一的模长的平方值
    }

    for (const auto& entry : vector2)
    {
        int freq2 = entry.second; //获得向量二中每一个单词的词频
        norm2 += freq2 * freq2; //增强向量二的模长的平方值
    }

    if (norm1 == 0.0 || norm2 == 0.0)
    {
        return 0.0; // 避免除以零导致计算错误
    }

    return dotProduct / (std::sqrt(norm1) * std::sqrt(norm2));  //开平方根使之为绝对值,即为模长

性能分析

运用vscode自带的gprof工具分析代码性能,gprof是一款 GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。
性能分析

异常处理

1.处理两文件为空异常,即运算余弦相似度时,当两个模长都为0时,分母为0,这时会运算错误,所以需要处理
空异常
2.处理打开文件失败异常
打开文件失败异常

PSP表格记录

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