个人项目

1.个人项目

github链接:https://github.com/pupaname/3124004431

这个作业属于哪一个课程 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15702
这个作业的目标 1. 在 GitHub 仓库中新建以学号命名的文件夹,并使用 Git 管理源代码;
2. 使用 C++ / Java / Python 实现论文查重程序,通过命令行参数读取原文与抄袭版论文,输出重复率到指定文件;
3. 完成代码质量分析、性能分析、单元测试(至少10个测试用例)与异常处理;
4. 撰写博客,记录 PSP 表格、设计实现、性能改进、测试展示与异常处理等内容。

2. PSP 时间预估表格

模块 预估耗时(min) 工作内容说明
UTF8文本切分模块 30 实现UTF-8多字节汉字识别,处理中文3字节字符,防止汉字截断
文本规范化模块 40 过滤中英文标点、全角空格,英文转为小写,清洗文本
N-Gram生成模块 30 滑动窗口生成5-gram字符串,存入unordered_set集合
N-Gram比对模块 25 求集合交集,计算Jaccard、overlap、coverage指标
文件读写模块 20 二进制读取文件,自动去除UTF-8 BOM头,异常处理
主函数与命令行参数、结果输出 20 参数校验,调用各模块,写入结果文件
代码调试、边界用例测试 60 空文件、纯标点、短文本、中英文混合测试
合计 225

3. 计算模块接口设计与实现

3.1 代码组织

  1. 函数列表
    • splitUTF8():将原始字符串切分为单个UTF-8字符(汉字/字母),返回字符vector
    • isChinesePunct():判断UTF8字符串是否是中文标点
    • isSpaceChar():判断是否为半角/全角空白字符
    • normalize():文本规范化,过滤标点、空白,英文小写,返回清洗后的字符vector
    • makeNGrams():滑动窗口生成N元字符片段集合,返回unordered_set
    • compareNGrams():接收两个N-Gram集合,计算各项相似度指标,返回CompareResult结构体
    • readFile():二进制读取文件,移除UTF8 BOM,返回读取成功与否
  2. 结构体
    • CompareResult:封装计算结果:overlap、jaccard、coverage、公共片段数量、两个文档N-gram总数
  3. 调用关系
    main() → readFile()读取两篇文档 → normalize()清洗文本 → makeNGrams()生成N-Gram集合 → compareNGrams()计算相似度 → 控制台+文件输出结果

3.2 算法关键

采用字符级5-Gram算法

  1. 将中文、英文全部切分为单个UTF8字符;
  2. 使用长度为5的滑动窗口,依次截取连续5个字符组成N-Gram片段;
  3. 两篇文档各自生成N-Gram集合(集合自动去重);
  4. 求两个集合交集,计算:
    • 原文覆盖率 overlap = \(\frac{|A∩B|}{|A|}\)
    • 待查文档覆盖率 coverage = \(\frac{|A∩B|}{|B|}\)
    • Jaccard相似度 Jaccard = \(\frac{|A∩B|}{|A∪B|}=\frac{|A∩B|}{|A|+|B|-|A∩B|}\)

3.3 独到之处

  1. 原生支持UTF-8中文:不是简单按char遍历,专门实现splitUTF8,避免中文汉字被拆成多个乱码字节;
  2. 同时过滤中文标点、全角空格,适配中文论文场景;
  3. 使用unordered_set存储N-Gram,查找为O(1),比对速度远优于LCS;
  4. 多维度相似度输出:同时输出Jaccard、原文被复制比例、待查文本来源比例,比单一重复率更适合论文查重;
  5. 文件读取自动去除UTF-8 BOM,解决记事本保存中文文件的乱码问题;
  6. 集合比对优化:遍历较小集合,减少哈希查找次数,降低时间开销。

4. 计算模块性能改进

4.1 性能优化花费时间:40分钟

4.2 改进思路

原始方案设想:采用LCS最长公共子序列,字符级DP。

  • 问题:文本长度增大时,时间复杂度 \((O(nm))\),长论文会出现计算缓慢、内存占用大。
  • 优化方案:替换为N-Gram+哈希集合
    1. 时间复杂度:生成N-Gram是\((O(n))\),集合求交集是\(O(min(|A|,|B|))\)
    2. 使用unordered_set哈希集合,利用哈希快速查找;
    3. 比对时遍历元素更少的集合,减少哈希查询次数;
    4. N-Gram使用集合自动去重,避免重复片段多次参与计算;
    5. UTF8处理只做一次,避免重复解码。

4.3 性能分析结论

程序耗时最多的函数:

  1. makeNGrams:生成滑动窗口字符串,字符串拼接占用CPU;
  2. normalize:遍历全部UTF8字符,做标点、空白判断;
  3. compareNGrams:哈希集合循环查找。

5. 单元测试展示

5.1 测试目标函数

splitUTF8normalizemakeNGramscompareNGramsreadFile

5.2 测试数据构造思路

  1. 正常中英文混合文本:测试中文汉字、英文大小写、标点过滤;
  2. 空文本:边界测试,输入空字符串,返回空集合,相似度为0;
  3. 纯标点/空格文本:清洗后为空,验证过滤逻辑;
  4. 完全相同文本:Jaccard=100%;
  5. 完全无关文本:Jaccard=0%;
  6. 部分重合文本:少量句子重合,验证指标计算正确性;
  7. 带UTF8 BOM的文件:验证readFile自动剔除BOM。

5.3 单元测试示例代码

#include "checker.h"
#include <cassert>
#include <cmath>
#include <iostream>
#include <fstream>

// ---------- 1. UTF-8 切分 ----------
void testSplitUTF8()
{
    // 纯 ASCII
    auto a = splitUTF8("hello");
    assert(a.size() == 5);
    assert(a[0] == "h");

    // 中英混合(1 个汉字 = 1 个元素)
    auto b = splitUTF8("你好a");
    assert(b.size() == 3);
    assert(b[0] == "你");
    assert(b[1] == "好");
    assert(b[2] == "a");

    // 空字符串
    auto c = splitUTF8("");
    assert(c.empty());

    std::cout << "[PASS] splitUTF8\n";
}

// ---------- 2. 中英文标点与空白判断 ----------
void testPunctAndSpace()
{
    assert(isChinesePunct("。") == true);
    assert(isChinesePunct(",") == true);
    assert(isChinesePunct("a")  == false);

    assert(isSpaceChar(" ")  == true);
    assert(isSpaceChar("\t") == true);
    assert(isSpaceChar(" ") == true);  // 全角空格
    assert(isSpaceChar("a")  == false);

    std::cout << "[PASS] isChinesePunct / isSpaceChar\n";
}

// ---------- 3. 规范化 ----------
void testNormalize()
{
    // 大小写 + 中英文标点 + 空格
    // "Hello, 世界。 Test!" → "hello世界test"
    // 共 5 + 2 + 4 = 11 个字符
    auto r = normalize("Hello, 世界。 Test!");
    assert(r.size() == 11);
    assert(r[0] == "h");
    assert(r[4] == "o");
    assert(r[5] == "世");
    assert(r[6] == "界");
    assert(r[7] == "t");

    // 空串
    assert(normalize("").empty());

    std::cout << "[PASS] normalize\n";
}

// ---------- 4. N-gram 生成 ----------
void testMakeNGrams()
{
    std::vector<std::string> chars = {"a", "b", "c", "d"};

    // N=3 → 2 个: abc, bcd
    auto g1 = makeNGrams(chars, 3);
    assert(g1.size() == 2);
    assert(g1.count("abc") == 1);
    assert(g1.count("bcd") == 1);

    // N 大于字符数 → 空
    auto g2 = makeNGrams(chars, 10);
    assert(g2.empty());

    // 恰好等于字符数 → 1 个
    auto g3 = makeNGrams(chars, 4);
    assert(g3.size() == 1);

    std::cout << "[PASS] makeNGrams\n";
}

// ---------- 5. 相似度计算 ----------
void testCompareNGrams()
{
    std::unordered_set<std::string> A = {"abc", "bcd", "cde"};
    std::unordered_set<std::string> B = {"abc", "cde", "def"};

    auto r = compareNGrams(A, B);
    // 交集 {abc, cde} = 2
    assert(r.commonGrams == 2);
    assert(std::fabs(r.overlap  - 2.0/3.0) < 1e-9);
    assert(std::fabs(r.coverage - 2.0/3.0) < 1e-9);
    assert(std::fabs(r.jaccard  - 0.5)     < 1e-9);

    // 空集合分支
    std::unordered_set<std::string> empty;
    auto r2 = compareNGrams(A, empty);
    assert(r2.commonGrams == 0);
    assert(r2.overlap == 0.0);
    assert(r2.coverage == 0.0);
    assert(r2.jaccard == 0.0);

    std::cout << "[PASS] compareNGrams\n";
}

// ---------- 6. 文件读取 + BOM 去除 ----------
void testReadFile()
{
    // 正常读取
    {
        std::ofstream f("tmp_test.txt");
        f << "hello world";
    }
    std::string s;
    assert(readFile("tmp_test.txt", s) == true);
    assert(s == "hello world");

    // 文件不存在
    assert(readFile("not_exist_12345.txt", s) == false);

    // 带 UTF-8 BOM 的文件
    {
        std::ofstream f("tmp_bom.txt", std::ios::binary);
        f << "\xEF\xBB\xBF" << "content";
    }
    assert(readFile("tmp_bom.txt", s) == true);
    assert(s == "content");

    std::cout << "[PASS] readFile\n";
}

int main()
{
    testSplitUTF8();
    testPunctAndSpace();
    testNormalize();
    testMakeNGrams();
    testCompareNGrams();
    testReadFile();

    std::cout << "\n==== All tests passed ====\n";
    return 0;
}

image

6.实际psp

阶段 实际时间 (min) 工作内容
需求分析与方案设计 18 放弃慢速 LCS,确定使用 UTF8+N-Gram 查重架构,确定三项相似度指标
UTF8 字符切分模块开发 35 实现完整 UTF8 汉字切分,解决中文多字节乱码问题
文本清洗规范化开发 38 完成中英文标点、全角空格过滤、英文小写归一化
N-Gram 生成算法开发 32 实现 5-gram 滑动窗口、集合去重逻辑
相似度计算模块开发 28 实现 Jaccard、Overlap、Coverage 三项公式计算
文件读写模块开发 15 实现二进制读取、自动去除 UTF-8 BOM、异常处理
命令行与结果输出 16 适配标准命令行参数、双文件输入、多指标文件输出
单元测试编写 45 编写全功能测试用例:空文本、纯标点、完全相同、无交集、边界测试
Gcov 覆盖率调试 42 反复清理 gcda、解决优化导致覆盖率异常、提升分支覆盖率
性能优化与耗时统计 30 移除 O (n²) LCS、改用哈希集合、添加 chrono 高精度计时
整体调试、Bug 修复 36 解决中文分句 bug、空集合崩溃、浮点精度问题
合计 335 完整项目开发、测试、优化、文档整理
posted @ 2026-09-14 17:23  pupaname  阅读(11)  评论(0)    收藏  举报