个人项目
GitHob仓库链接:https://github.com/town-flowWind/3124004176
一、PSP 表格(预估 vs 实际)
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 15 20
· Estimate ·估计这个任务需要多长时间 15 20
Development 开发 120 200
· Analysis ·需求分析(包括学习新技术) 30 45
·Design Spec ·生成设计文档 20 20
·Design Reciew ·设计复审 10 10
·Coding Standard ·代码规范 10 15
·Design ·具体设计 20 30
·Coding ·具体编码 50 90
·Code Reviw ·代码复审 10 10
·Test ·测试(自我测试,修改代码,提交修改) 30 60
Reporting 报告 30 50
·Test Report ·测试报告 15 20
·Size Measurement ·计算功能工作量 10 10
·Postmortem &Process lmprovement Plan ·事后总结,并提出过程改进计划 15 20
合计 254 400
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位。以上是课程作业说明,请你在3124004176个人项目文件夹下完成这个作业
19:53
Agent
思考过程
我先了解一下当前工作目录和相关记忆。
已读取 2 个文件,搜索 1 次文件,执行 1 条命令
3124004176个人项目 目前是一个空文件而不是文件夹,我需要删掉它再建文件夹。先确认本机可用的开发环境。
已执行 2 条命令
手动终止输出
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位。以上是课程作业说明,使用java完成作业
展示更多
19:59
Agent
任务耗时 4m 19s
思考过程
明白,使用 Java 实现。JDK 1.8 可用。我先删除那个同名的空文件并创建项目文件夹,然后编写代码。
已删除 1 个文件,执行 1 条命令
该空文件已不存在,直接创建项目文件夹。
已执行 1 条命令
文件夹已创建。现在编写查重程序 Main.java 。
已创建 1 个文件
编译并用题目示例测试。
已执行 3 条命令
三组测试全部通过。再验证一下 GBK 编码文件的兼容性(课堂样例可能是 GBK 编码)。
已执行 1 条命令
作业已完成,位于 3124004176个人项目 文件夹下。
实现说明 — Main.java :
- 用法 :
java Main <原文绝对路径> <抄袭版绝对路径> <答案文件绝对路径> - 算法 :预处理(去除标点、空白,仅保留文字数字)→ 提取字符二元组(bigram)词频向量 → 计算余弦相似度作为重复率,对"增、删、改"类抄袭均敏感
- 编码兼容 :优先按 UTF-8 解码,失败自动回退 GBK
- 输出 :
%.2f格式浮点数(如0.61),UTF-8 写入答案文件
自测结果 :
测试用例 输出 题目示例(今天是星期天… vs 今天是周天…) 0.61 完全相同文本 1.00 完全不同文本 0.00 GBK 原文 vs UTF-8 抄袭版(混合编码) 0.61
已编译生成Main.class (JDK 1.8)。拿到课堂下发的orig.txt /orig_add.txt 等样例后,直接运行java -cp . Main orig.txt orig_add.txt ans.txt 即可验证。
1 个文件已更改
- 113 - 0
Main.java
.\3124004176个人项目/Main.java
- 113 - 0
二、计算模块接口设计与实现过程
- 算法关键
本程序的核心算法使用 Python 内置的 difflib.SequenceMatcher。它基于最长公共子序列(LCS) 来计算两个字符串的相似度,计算公式为 2 * M / T(M 为匹配字符数,T 为总字符数)。相比于余弦相似度,这个算法对中文短文本的查重更加精准,并且完全不需要安装第三方依赖库。
- 代码组织与函数关系
整个程序共有三个核心函数,它们之间的关系是“流水线”式的:
read_text(path: Path) -> str:负责读取文件。内部包含 try...except 异常防护,若文件不存在或编码错误,返回空字符串 ""。
plagiarism_rate(original: str, copy: str) -> float:负责查重计算。接收两个字符串,内部使用 SequenceMatcher 计算相似度,返回 0.0 到 1.0 之间的浮点数。
main() -> None:主入口。使用 argparse 解析命令行参数(原文、抄袭文、结果文件),依次调用上述两个函数,最后将结果格式化(保留两位小数)写入指定的 ans.txt 中。
- 独到之处
在调用 SequenceMatcher 时,设置了 autojunk=False 参数。如果不设置,算法会自动忽略掉长文本中高频出现的字符(自动垃圾过滤),这会导致查重结果不准确。禁用该功能后,查重更加严谨。
模仿这段文本并阅读项目,写一段同样的计算模块接口设计与实现过程
展示更多
20:12
Agent
任务耗时 54s
思考过程
我先重新读一下当前的代码,确保描述与实现一致。
已读取 1 个文件
二、计算模块接口设计与实现过程
1. 算法关键
本程序的核心算法使用 字符二元组(bigram)词频向量 + 余弦相似度 。它将文本中相邻两个字符构成的 bigram 作为特征项统计词频,把两篇文本映射为两个高维向量,再用余弦公式计算相似度:
cos ( θ ) = ∣ A ∣ × ∣ B ∣ A ⋅ B
由于中文没有天然空格分词,bigram 无需引入 jieba、HanLP 等第三方分词组件即可有效捕捉文本的局部字序特征,对"增、删、改"类抄袭行为(如"星期天"→"周天"、"天气晴"→"天气晴朗")都有明显响应,且完全基于 JDK 标准库实现,零外部依赖。
2. 代码组织与函数关系
整个程序共有四个核心函数,它们之间的关系是"流水线"式的:
readFile(String path) :负责读取文件。内部优先按 UTF-8 严格解码,解码失败时自动回退为 GBK,兼容不同编码的课程样例文件。
normalize(String s) :负责文本预处理。遍历字符串剔除标点、空白等噪声字符,仅保留字母与数字(含中文字符),避免标点差异干扰查重结果。
bigramFrequency(String s) :负责特征提取。统计相邻两字符 bigram 的词频表,单字符文本自动退化为 unigram。
calculateSimilarity(String s1, String s2) :负责查重计算。依次调用上述两个函数得到两篇文本的词频向量,按余弦公式计算并返回 0.0 到 1.0 之间的浮点数。
main(String[] args) :主入口。解析命令行参数(原文路径、抄袭版路径、答案文件路径),依次调用上述函数,最后将结果格式化(%.2f ,保留两位小数)写入指定的答案文件中。
3. 独到之处
在编码处理上,使用CharsetDecoder 配合CodingErrorAction.REPORT 严格校验 UTF-8 合法性,仅当确实出现非法字节序列时才回退 GBK,避免了盲目猜测编码导致的乱码误判。
在结果输出上,使用String.format(Locale.ROOT, "%.2f", rate) 固定区域设置,确保在任何系统语言环境下小数点都输出为. ,不会因为本地化差异输出成逗号而导致格式错误。
在边界场景上,对空文本做了显式处理:两篇均为空返回 1.00,仅一篇为空返回 0.00,保证算法在极端输入下也不会抛出除零异常,程序行为完全可预期。

三、计算模块接口部分的性能改进
1. 改进思路
最初我考虑使用"HanLP 分词 + TF-IDF + 余弦相似度"的方案。但分析后发现,这需要引入第三方依赖库,评测环境不一定支持联网下载,且分词模型的加载与计算开销大。
我也考虑过基于最长公共子序列(LCS)或编辑距离的方案,但这类算法的时间复杂度为 O(n×m),面对长文本时性能无法接受。
最终我改为使用"字符 bigram 词频向量 + 余弦相似度"的方案:完全基于 JDK 标准库实现,零外部依赖;预处理、特征提取、相似度计算均为线性时间复杂度 O(n),对"增删改"类抄袭同样敏感,完全满足评测要求。
2. 性能分析
在终端使用 PowerShell 的Measure-Command 对程序进行计时测试(模拟真实评测的完整流程:读取文件 → 计算 → 写答案),结果如下:
小文本(题目示例,约 20 字) :全程耗时约 150ms,其中绝大部分为 JVM 启动开销,算法本身耗时在毫秒级。
超大文本(约 105 万字原文 vs 100 万字抄袭版,文件各 3.3MB) :连续运行 3 次,全程耗时分别为 258ms、233ms、230ms,输出结果 0.64。扣除约 150ms 的 JVM 启动开销后,算法处理 200 余万字符的纯计算耗时仅约 80ms。
其中bigramFrequency 的 bigram 切分与HashMap 词频统计是耗时最大的模块,但得益于线性复杂度,即使文本规模扩大百倍,耗时也仅线性增长,远低于评测要求的"5 秒内出结果"。
由于算法本身已足够快,无需进一步优化,只需保证在极端情况下(如超大文本)不超时即可——实测表明本程序在最坏情况下仍有 20 倍以上的时间余量。
四、计算模块部分单元测试展示
1. 测试思路
由于评测环境要求零第三方依赖,我没有引入 JUnit,而是使用 JDK 标准库自研了一个等效于 unittest 的轻量测试运行器( MainTest.java ),共设计 12 个测试用例,采用白盒测试思路,覆盖了以下边界场景:
- 完全相同(相似度应为 1.00)
- 完全不同(相似度应为 0.00)
- 部分抄袭(相似度应在 0.0 到 1.0 之间,题目示例实测 0.61)
- 原文为空、抄袭文为空、两者都为空
- 纯空格或纯标点符号(归一化后为空)
- 超长文本(5 万次重复约 200 万字,验证结果稳定性并限时 5 秒)
- 英文大小写区分(全大写与全小写应判为 0.00)
- 特殊字符和换行符(归一化后不影响结果)
- 文件不存在(异常处理测试)
- 读取文件发生异常(以目录作为输入路径,异常处理测试)
其中两个异常用例通过反射调用私有的readFile方法,断言其按预期抛出IOException。
2. 测试结果
经过本地运行,所有 12 个测试用例全部通过(OK),总耗时 137ms,其中超长文本用例单条耗时 85ms,验证了算法在大规模输入下的性能稳定性。

五、事后总结与过程改进
1. 实际耗时统计
结合 PSP 表格,本次作业实际耗时约 360 分钟,超出预估的 245 分钟。主要额外时间花在了两处:一是中文编码问题的调试(Windows 默认 GBK 与源文件 UTF-8 不一致,导致javac 编译报错、控制台输出乱码,最终通过统一指定-encoding UTF-8 解决);二是单元测试用例的返工(英文大小写用例初版设计不合理,"Hello World" 仅首字母大写,内部小写 bigram 仍有重合,导致断言失败,后改为全大写对全小写才正确验证了大小写敏感性)。
2. 过程改进计划
环境方面 :下次开发前应尽早确认 JDK 版本、编译编码与控制台编码是否一致,先跑通最小可运行示例(Hello World + 中文字符串),再进入正式开发,避免后期因环境干扰打乱节奏。
版本控制方面 :熟悉了 Git 的分支管理和远程推送流程。在遇到网络问题时,学会了使用git push -u origin master:main --force 强制覆盖,确保代码能按时推送到云端。
测试方面 :掌握了在无第三方依赖条件下自研轻量测试运行器的方法,能够覆盖完全相同、完全不同、空文本、纯标点、超长文本、异常 IO 等 12 类边界场景;同时认识到测试用例本身也需要验证——断言失败时先分析是用例错了还是代码错了,这次正是通过失败用例的排查,修正了测试设计,也加深了对 bigram 算法行为的理解,极大地提升了代码的健壮性
浙公网安备 33010602011771号