个人项目
这个作业属于哪个课程 | 计科22级12班 |
---|---|
这个作业要求在哪里 | 作业 |
这个作业的目标 | 设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。 |
Github链接:https://github.com/CloneYek/Kevin/blob/main/3122004904
项目开发要求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
1 PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 1200 | 1100 |
· Estimate | · 估计这个任务需要多少时间 | 900 | 720 |
Development | 开发 | 600 | 300 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 300 |
· Design Spec | · 生成设计文档 | 60 | 90 |
· Design Review | · 设计复审 | 60 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 60 |
· Design | · 具体设计 | 120 | 150 |
· Coding | · 具体编码 | 60 | 30 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 60 |
Reporting | 报告 | 300 | 300 |
· Test Repor | · 测试报告 | 120 | 120 |
· Size Measurement | · 计算工作量 | 120 | 60 |
Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 120 |
· 合计 | 1200 | 1100 |
2 项目模块设计与实现
主类:PlagiarismChecker:包含main方法,可以接收指定参数,使用JiebaSegmenter进行中文分词,再通过集合操作计算Jaccard相似度,结果以百分比形式输出到指定文件中。
测试类:JiebaPlagiarismTest:测试不同情况的字符串之间重复率。
2.1项目结构:
2.2程序流程:
- 接收命令行参数:程序启动时,检查是否接收到三个命令行参数:
- 原始文本文件路径、抄袭文本文件路径、输出结果文件路径
- 如果参数数量不正确,输出用法提示并终止程序。
- 读取文件内容:
- 使用 Files.readAllBytes 方法读取原始文本文件的内容,并将其存储为字符串。
- 读取抄袭文本文件的内容,并将其存储为字符串。
- 计算相似度:
- 创建 JiebaSegmenter 实例,用于中文分词。
- 对原始文本进行分词,生成一个包含所有单词的集合。
- 对抄袭文本进行分词,生成另一个包含所有单词的集合。
- 计算这两个集合的交集,得到两个文本中共同存在的单词数量。
- 计算相似度百分比:将交集的大小除以原始文本单词集合的大小,并乘以 100,以获得相似度的百分比值。
- 写入结果:
- 使用 BufferedWriter 将计算得到的相似度结果写入指定的输出文件。
- 结果格式化为两位小数。
- 异常处理:
在读取文件和写入结果的过程中,捕获可能发生的 IOException,并打印异常信息以便调试。
2.3关键方法分析
-
分词
-
工具的介绍:Jieba分词器,最早是提供了python包,后来由huaban开发出了java版本。
-
引入:引入相关依赖
<dependency> <groupId>com.huaban</groupId> <artifactId>jieba-analysis</artifactId> <version>1.0.2</version> <dependency>
-
原理:jieba项目resource目录下有个dict.txt文件,里面维护了非常多的拆词,jieba就是根据这个文件进行拆词的。自己也可以在这个文件中添加自定义拆词,或者新建一个文件。
-
查重
- 将两个文本分词后存入集合;
- 计算两个文本分词后的交集;
- 计算相似度百分比;
- tips:此处用到的是jaccard算法 具体参考:用jaccard算法计算两个字符串的相似度
3. 计算模块接口部分的性能改进
-
主类模块的性能如下
-
改进后
- 优化文件读取 采用BufferedReader;
- 优化集合操作 将较小的集合作为参数来使用retainAll;
- 使用并行处理;
4. 计算模块单元展示
测试代码展示:
点击查看代码
public class JiebaPlagiarismTest {
private double calculateJaccardSimilarity(String originalText, String plagiarizedText) {
JiebaSegmenter segmenter = new JiebaSegmenter();
Set<String> originalWords = new HashSet<>(segmenter.sentenceProcess(originalText));
Set<String> plagiarizedWords = new HashSet<>(segmenter.sentenceProcess(plagiarizedText));
// 计算交集和并集
Set<String> intersection = new HashSet<>(originalWords);
intersection.retainAll(plagiarizedWords);
Set<String> union = new HashSet<>(originalWords);
union.addAll(plagiarizedWords);
// Jaccard 相似度计算
return union.isEmpty() ? 0 : (double) intersection.size() / union.size() * 100;
}
@Test
public void testExactMatch() {
double similarityRate = calculateJaccardSimilarity("今天是星期天,天气晴,今天晚上我要去看电影。",
"今天是星期天,天气晴,今天晚上我要去看电影。");
System.out.printf("%.2f", similarityRate);
}
@Test
public void testReverseString() {
double similarityRate = calculateJaccardSimilarity("今天是星期天,天气晴,今天晚上我要去看电影。",
"电影看去我要晚上今天,晴天气,天星期是今天。");
System.out.printf("%.2f", similarityRate);
}
@Test
public void testWithSymbols() {
double similarityRate = calculateJaccardSimilarity("今天是星期天,天气晴,今天晚上我要去看电影。",
"今天是星期天,天气晴,今天晚上我要去看电影!");
System.out.printf("%.2f", similarityRate);
}
@Test
public void testPartialChange() {
double similarityRate = calculateJaccardSimilarity("今天是星期天,天气晴,今天晚上我要去看电影。",
"今天是星期天,天气晴,今晚上我要去看电影。");
System.out.printf("%.2f", similarityRate);
}
@Test
public void testPartialMissing() {
double similarityRate = calculateJaccardSimilarity("今天是星期天,天气晴,今天晚上我要去看电影。",
"今天是星期天,天气晴,今晚上要去看电影。");
System.out.printf("%.2f", similarityRate);
}
@Test
public void testEmptyString() {
double similarityRate = calculateJaccardSimilarity("", "");
System.out.printf("%.2f", similarityRate);
}
}
测试结果展示:
5.模块部分异常处理
5.1 文件路径检查
if (!Files.exists(Paths.get(originalFilePath))||!Files.exists(Paths.get(plagiarizedFilePath)))
{
System.out.println("One or both input files do not exist.");
return;
}
5.2 读取文件的异常处理
try {
String originalText = readFile(originalFilePath);
String plagiarizedText = readFile(plagiarizedFilePath);
// 其他代码...
} catch (IOException e) {
e.printStackTrace();
}
5.3 并发处理的异常处理
try {
double similarityRate = similarityFuture.get(); // 等待计算结果
// 写入文件的代码...
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
5.4 写入文件的异常处理
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath))) {
writer.write(String.format("Similarity Rate: %.2f%%", similarityRate));
} catch (IOException e) {
e.printStackTrace();
}
6. 项目功能测试