首页 > 编程语言 >个人项目——C++实现论文查重(简易版)

个人项目——C++实现论文查重(简易版)

时间:2023-09-19 09:14:07浏览次数:46  
标签:查重 txt string C++ put 简易版 test main 函数

本次项目GitHub地址:https://github.com/Focuspresent/Paper_Review

这个作业属于哪个课程 https://edu.cnblogs.com/campus/jmu/ComputerScience21
这个作业的要求 https://edu.cnblogs.com/campus/jmu/ComputerScience21/homework/13034
这个作业的目标 完成一次编程练习

前言

题目需求

题目:论文查重

描述如下:

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

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

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

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

开发环境

  • 编程语言: C++11
  • 编程环境: Vscode(Vscode怎么了你)
  • 第三方库: cppjieba
  • 其他软件: cmake,mingw32-make

算法及其流程

简单的采用余弦相似度来计算文本相似度

  1. 将文件内容作为整体读入字符串
  2. 调用分词库将字符串分词,并去掉标点符号,得到两个列表
  3. 将两个列表进行去重,并做标识记录,生成哈希表
  4. 分别遍历列表,用哈希表做两次词频统计,也就是映射完后的向量
  5. 由多维向量计算余弦相似度

编程实现

接口的设计与实现

整体架构

函数说明

  • main(int,char*) -> int: 主体部分,由命令行输入参数调用,在计算并写入之前,会检测参数个数,路径以及文件内容的合法性
  • put_error(string) -> void: 错误输出,并由此退出程序
  • check_path(string&) -> bool: 路径检测,失败则调用退出函数
  • read_file(string&) -> string: 输入路径,并打开文件,将文本内容读入到字符串并返回,文件打开失败调用退出函数
  • write_file(string&,string) -> void: 输入路径,并打开文件,将后一个字符串写到文件中并换行,文件打开失败调用退出函数
  • spilt(string&) -> vector<string>: 调用分词库,将字符串分词为列表,并去掉标点符号,返回
  • count(string&, string&) -> string: 调用两次spilt,将两个列表映射成多维向量,由向量计算余弦相似度,最后将答案格式化并转为字符串

算法关键

关键在于count函数的实现,本来其实要多写几个函数,倒不如再多写几个function指针
1.调用分词函数,并声明向量

    vector<string> l1=spilt(text1),l2=spilt(text2);
    vector<int> v1,v2;

2.将两个列表进行合并,以下是合并的函数,记录大小为零会退出

    auto Union=[&](vector<string>& l){
        for(auto& n: l){
            if(us.count(n)) continue;
            um[n]=index++;
            us.insert(n);
        }
    };

3.分别遍历,将列表映射成向量,以下是转化的函数

auto ltov=[&](vector<string>& l,vector<int>& v){
        if(!index) {
            put_error("File Size Empty");
        }
        v.resize(index);
        for(auto& n: l){
            if(!us.count(n)) continue;
            v[um[n]]++;
        }
    };
  1. 用两个向量计算余弦相似度,简单的遍历以下就行,以下是计算的函数,由于涉及除法,检测分母
auto getSim=[&](vector<int>& v1, vector<int>& v2)->double{
        double ans=0,m1=0,m2=0;
        for(int i=0;i<v1.size();i++){
            ans+=v1[i]*v2[i];
            m1+=v1[i]*v1[i];
            m2+=v2[i]*v2[i];
        }
        if(!m1||!m2){
            put_error("Zero Denominator");
        }
        return ans/(sqrt(m1)*sqrt(m2));
    };
  1. 函数调用,先合并,否则记录大小为零会直接退出,再将列表转为向量,最后计算余弦相似度,并将其格式化
    Union(l1);Union(l2);
    ltov(l1,v1);ltov(l2,v2);
    //格式化
    char ans[4];
    sprintf(ans,"%3.2f",getSim(v1,v2));

性能分析

虽然没找到啥好用的插件,但是编译器自带的gprof暂且还可以用,先在编译选项上加上pg选项,然后在运行可执行文件之后,会生成out文件,然后采用以下命令

gprof target.exe gmon.out //可以在命令行中看到相关信息
gprof target.exe gmon.out > report.txt //可以将相关信息保留在目标文本中

不过麻烦的是,由于有三方库的介入,生成的文本较多

可以看出性能瓶颈除去三方库的函数,就在于spilt函数,其实也挺正常的,毕竟spilt就是调用第三方库的函数

性能改进

我觉得没啥好改的,除去第三方库及其调用的,关键的count函数也只是多次一次遍历就可以完成

单元测试

路径检测测试

void check_path_test(){
    //相对路径
    string s1=".\\test",s2="saxaxa",s3="E:\\hah\\hha.txt";
    cout<<check_path(s1)<<endl;
    //错误路径
    cout<<check_path(s2)<<endl;
    //绝对路径
    cout<<check_path(s3)<<endl;
}

打印输出

0
0
1

分割函数测试

void spilt_test(){
    //打印函数
    auto print=[&](string& s){
        for(auto& w: spilt(s)){
            cout<<w<<" ";
        }
        cout<<endl;
    };
    //
    string s1="你说的对但是原神是由米哈游自主研发的一款全新开放世界冒险游戏游戏发生在一个被称作提瓦特的幻想世界在这里被神选中的人将被授予神之眼导引元素之力你将扮演一位名为旅行者的神秘角色在自由的旅行中邂逅性格各异能力独特的同伴们和他们一起击败强敌找回失散的亲人的同时逐步发掘原神的真相";
    string s2="我认为, 识之律者女士万岁,发生了会如何,不发生又会如何。 识之律者女士万岁,发生了会如何,不发生又会如何。 要想清楚,识之律者女士万岁,到底是一种怎么样的存在。 而这些并不是完全重要,更加重要的问题是, 了解清楚识之律者女士万岁到底是一种怎么样的存在,是解决一切问题的关键。 这种事实对本人来说意义重大,相信对这个世界也是有一定意义的。 既然如此, 就我个人来说,识之律者女士万岁对我的意义,不能不说非常重大。 带着这些问题,我们来审视一下识之律者女士万岁。 要想清楚,识之律者女士万岁,到底是一种怎么样的存在。 所谓识之律者女士万岁,关键是识之律者女士万岁需要如何写。";
    print(s1);print(s2);
}

打印输出

你 说 的 对 但是 原神 是 由 米哈 游 自主 研发 的 一款 全新 开放 世界 冒险游戏 游戏 发生 在 一个 被称作 提 瓦特 的 幻想 世界 在 这里 被 神 选中 的 人 将 被 授予 神之 
眼 导引 元素 之力 你 将 扮演 一位 名为 旅行者 的 神秘 角色 在 自由 的 旅行 中 邂逅 性格 各异 能力 独特 的 同伴 们 和 他们 一起 击败 强敌 找回 失散 的 亲人 的 同时 逐
步 发掘 原神 的 真相
我 认为   识之律者 女士 万岁 发生 了 会 如何 不 发生 又 会 如何   识之律者 女士 万岁 发生 了 会 如何 不 发生 又 会 如何   要 想 清楚 识之律者 女士 万岁 到底 是 一种 
怎么样 的 存在   而 这些 并 不是 完全 重要 更加 重要 的 问题 是   了解 清楚 识之律者 女士 万岁 到底 是 一种 怎么样 的 存在 是 解决 一切 问题 的 关键   这种 事实 对  
本人 来说 意义 重大 相信 对 这个 世界 也 是 有 一定 意义 的   既然如此   就 我 个人 来说 识之律者 女士 万岁 对 我 的 意义 不能不 说 非常 重大   带 着 这些 问题 我们 
来 审视 一下 识之律者 女士 万岁   要 想 清楚 识之律者 女士 万岁 到底 是 一种 怎么样 的 存在   所谓 识之律者 女士 万岁 关键 是识 之律者 女士 万岁 需要 如何 写   

计算函数测试

void count_test(){
    string s1="生活中,若你说的对出现了,我们就不得不考虑它出现了的事实。 既然如何, 一般来说, 那么, 这种事实对本人来说意义重大,相信对这个世界也是有一定意义的。 我们都知道,只要有意义,那么就必须慎重考虑。";
    string s2="一般来说, 现在,解决你说的对的问题,是非常非常重要的。 所以, 阿卜·日·法拉兹曾经说过,学问是异常珍贵的东西,从任何源泉吸收都不可耻。我希望诸位也能好好地体会这句话。 美华纳在不经意间这样说过,勿问成功的秘诀为何,且尽全力做你应该做的事吧。带着这句话,我们还要更加慎重的审视这个问题: ";
    cout<<count(s1,s2)<<endl;
}

打印输出

0.46

主函数测试

利用_execl(...)可以调用程序来测试

int main(){
    int ch;
    cin>>ch;
    switch(ch){
        case 0:{
            //错误路径
            _execl("..\\main.exe","main_test_0","wad","xaw","../../data/target.txt",NULL);
            break;
        }
        case 1:{
            //文本为空
            _execl("..\\main.exe","main_test_1","E:\\CODE\\data\\paper.txt","E:\\CODE\\data\\copy_paper2.txt","E:\\CODE\\data\\target.txt",NULL);
            break;
        }
        case 2:{
            //正确
            _execl("..\\main.exe","main_test_2","E:\\CODE\\data\\paper.txt","E:\\CODE\\data\\copy_paper1.txt","E:\\CODE\\data\\target.txt",NULL);
            break;
        }
    }
    return EXIT_SUCCESS;
}

打印输出

$ ./main_test
0
Parameter Quantity Error
$ ./main_test
1
File Size Empty
$ ./main_test
2

异常处理

输入参数错误

    if(argc!=4){
        put_error("Parameter Quantity Error");
    }

路径出错

    if(!check_path(orig_path)||!check_path(origadd_path)||!check_path(target_path)){
        put_error("File Path Error");
    }

文本内容为空

    if(!paper.size()||!copied_paper.size()){
        put_error("File Size Empty");
    }

读取或写文件出错

    if(!fin.is_open()){
        put_error("File Open Error");
    }
    if(!fout.is_open()){
        put_error("File Open Error");
    }

去完标点符号后,文本内容为空

    if(!index) {
        put_error("File Size Empty");
    }

计算余弦时分母为零

    if(!m1||!m2){
        put_error("Zero Denominator");
    }

PSP表格

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

写到最后

c++/java确实麻烦了点(可惜没有轮子哥),不如python的difflib库

标签:查重,txt,string,C++,put,简易版,test,main,函数
From: https://www.cnblogs.com/ChangSi/p/17713123.html

相关文章

  • C++系列十:日常学习-Lambda表达式
    目录前言必备理论知识:例子:前言有C#经验,使用起来,驾轻就熟。就是语法糖。但是也要熟悉用法,才好众享丝滑。内容参考:Chatjpt、文心一言必备理论知识:捕获列表:[]:默认不捕获任何变量;[=]:默认以值捕获所有变量;内部有一个相应的副本[&]:默认以引用捕获所有变量;[x]:仅以值捕获x,其它......
  • C++中的深拷贝和浅拷贝介绍
    对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如:classBase{public:Base():m_a(0),m_b(0){}Base(inta,intb):m_a(a),m_b(b){}private:intm_a;intm_b;};intmain(){in......
  • C/C++中结构体占用内存大小的计算方法
    两个值:对齐系数:一般为8个字节。#pragmapack(8)设置对齐系数为8。有效对齐值:假设结构体中最长的类型的长度为len,则有效对齐值=min(len,对齐系数)。计算规则:计算存放的位置:第一个成员放在位置0,后面的成员A存放的时候,会先计算size=min(A大小,有效对齐值),A只放在size的整数倍......
  • C++ explicit
    C++explicitexplicit关键字有两个用途:指定构造函数或者转换函数(C++11起)为显示,即它不用用于隐式转换和赋值初始化。可以与常量表达式一同使用。当该表达式为true才为显示转换(C++20起)。1.将构造函数标记为显式C++中的explicit关键字通常用来将构造函数标记为显式类型转换,......
  • C++基础入门
    C++基础入门1C++初识1.1第一个C++程序编写一个C++程序总共分为4个步骤创建项目创建文件编写代码运行程序1.1.1创建项目​ VisualStudio是我们用来编写C++程序的主要工具,我们先将它打开1.1.2创建文件右键源文件,选择添加->新建项给C++文件起个名称,然后点击添......
  • 论文查重(基于余弦相似和Sim-Hash)
    个人项目这个作业属于哪个课程计科21级2班这个作业要求在哪里个人项目这个作业的目标熟悉单人开发软件整体流程PSP表*PSP2.1**PersonalSoftwareProcessStages**预估耗时(分钟)**实际耗时(分钟)*Planning计划2017·Estimate·估计这个任务......
  • 软件工程2 论文查重
    软工作业2:实现论文查重课程地址[广东工业大学-软件工程-计科21级12班]https://edu.cnblogs.com/campus/gdgy/CSGrade21-12作业要求https://edu.cnblogs.com/campus/gdgy/CSGrade21-12/homework/13014作业目标通过C++编程来实现论文查重。GitHub地址:https://g......
  • 个人项目-java实现论文查重(更新中)
    本次作业GitHub项目链接:https://github.com/zzz-muxin/PlagiarismCheck作业详情这个作业属于哪个课程工程概论这个作业要求在哪里作业要求这个作业的目标学会利用GitHub进行项目开发,完成一个工程项目开发的详细过程需求题目:论文查重描述如下:设计一个论......
  • java中使用JNA调用C/C++动态链接库中的函数
    目录C++部分创建动态库项目JAVA部分pom依赖工程结构测试类jna提供C与JAVA数据类型映射参考C++部分创建动态库项目注意:动态库中的头文件,必须要将需要导出的函数包裹在extern"C"{}中,否者在使用侧java工程中,就无法加载并使用JAVA部分pom依赖<dependencies><dependenc......
  • C++ Primer 学习笔记——第十章
    第10章前言在前面我们学习容器的时候,是否发现标准库下的对容器的操作并不是太多(或许,初学时已经觉得好多了......