个人项目:论文查重
这个作业属于哪个课程 | 软件工程2024 |
---|---|
这个作业要求在哪里 | |
这个作业的目标 | 了解软件项目开发的整体流程,实现自己的个人项目,学习单元测试、性能优化和 git 操作,学会使用 PSP 表格 |
Github地址 : 点击此处,进入我的仓库
一、项目需求
题目:论文查重
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
-
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
-
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
-
从命令行参数给出:论文原文的文件的绝对路径。
-
从命令行参数给出:抄袭版论文的文件的绝对路径。
-
从命令行参数给出:输出的答案文件的绝对路径。
提供的样例:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。样例下载地址
答案文件中输出的答案为浮点型,精确到小数点后两位
二、设计和实现
1、整体流程
2、类和函数
类 | 方法 | 描述 |
---|---|---|
Main | main() | 主程序入口 |
TxtUtils | readFile(String filePath) writeFile(double fileElem, String filePath) |
从目标文件路径中读取文件内容 将结果写入指定文件 |
HashUtils | getHash(String str) getSimHash(String str) |
传入 String ,计算出它的 hash 值,并以字符串形式输出 传入 String ,计算出它的 simHash 值 |
HammingUtils | calculateHammingDistance(String simHash1, String simHash2) calculateSimilarity(String simHash1, String simHash2) |
计算两个 SimHash 值的海明距离 输入两个 simHash 值,输出相似度 |
ExceptionUtils | ExceptionUtils(String message) | 处理文本过短异常 |
3、关键算法
simHash算法和海明距离实现论文查重
三、性能改进
性能分析图:
Memory占用图:
byte类、String类和HashMap类占用较,byte类、String类主要来自于simHash值和海明距离的计算,HashMap类主要来自于使用分词器分词需要用到Map进行词频映射,可从此处改进。
四、单元测试
1. 读写 txt 文件的模块测试
思路:测试读取成功、读取失败、写入成功、写入失败的情况
代码:
public class TxtUtilsTest {
@Test
public void readTest() {
// 路径存在 读取成功
String str = TxtUtils.readFile("D:/test/orig.txt");
String[] strings = str.split(" ");
for (String string : strings) {
System.out.println(string);
}
}
@Test
public void readTestErr() {
// 路径不存在 读取失败
String str = TxtUtils.readFile("D:/test/orig111.txt");
String[] strings = str.split(" ");
for (String string : strings) {
System.out.println(string);
}
}
@Test
public void writeTest() {
// 路径存在 写入成功
double[] elem = {0.12, 0.23, 0.34, 0.45, 0.56,0.99,0.88};
for (double v : elem) {
TxtUtils.writeFile(v, "D:/test/answer.txt");
}
}
@Test
public void writeTestErr() {
//路径错误 写入失败
double[] elem = {0.12, 0.23, 0.34, 0.45, 0.56,0.99,0.88};
for (double v : elem) {
TxtUtils.writeFile(v, "QQ:/test//answer.txt");
}
}
}
测试结果:
代码覆盖率:
2. hash 模块测试
代码:
public class HashUtilsTest {
@Test
public void getHashTest() {
// 短文本
String[] strings = {"画画" , "多大" , "GG" , "火" , "阿凡达ya" , "版"};
for (String string : strings) {
String strHash = HashUtils.getHash(string);
if (strHash != null) {
System.out.println(strHash.length());
}
System.out.println(strHash);
}
}
@Test
public void getSimHashTest0() {
// 短文本
String str = "好";
System.out.println(HashUtils.getSimHash(str));
}
@Test
public void getSimHashTest() {
// 长文本
String str0 = TxtUtils.readFile("D:/test/orig.txt");
String str1 = TxtUtils.readFile("D:/test/orig_0.8_add.txt");
System.out.println(HashUtils.getSimHash(str0));
System.out.println(HashUtils.getSimHash(str1));
}
}
测试结果:
代码覆盖率:
3. 计算海明距离模块测试
代码:
public class HammingUtilsTest {
@Test
public void hammingDistanceTest0() {
String str0 = "10101100";
String str1 = "11001111";
int distance = HammingUtils.calculateHammingDistance(str0,str1);
double similarity = HammingUtils.calculateSimilarity(str0,str1);
System.out.println("海明距离为:" + distance);
System.out.println("相似度为:" + similarity);
}
@Test
public void hammingDistanceTest1() {
// 长度不等
String str0 = "10101100";
String str1 = "110011";
int distance = HammingUtils.calculateHammingDistance(str0,str1);
// double similarity = HammingUtils.calculateSimilarity(str0,str1);
System.out.println("海明距离为:" + distance);
// System.out.println("相似度为:" + similarity );
}
@Test
public void hammingDistanceTest2() {
String str0 = TxtUtils.readFile("D:/test/orig.txt");
String str1 = TxtUtils.readFile("D:/test/orig_0.8_add.txt");
int distance = HammingUtils.calculateHammingDistance(Objects.requireNonNull(HashUtils.getSimHash(str0)), Objects.requireNonNull(HashUtils.getSimHash(str1)));
double similarity = HammingUtils.calculateSimilarity(HashUtils.getSimHash(str0),HashUtils.getSimHash(str1));
System.out.println("海明距离为:" + distance);
System.out.println("相似度为:" + similarity);
}
@Test
public void hammingDistanceTest3() {
String str0 = TxtUtils.readFile("D:/test/orig.txt");
String str1 = TxtUtils.readFile("D:/test/orig_0.8_del.txt");
int distance = HammingUtils.calculateHammingDistance(Objects.requireNonNull(HashUtils.getSimHash(str0)), Objects.requireNonNull(HashUtils.getSimHash(str1)));
double similarity = HammingUtils.calculateSimilarity(HashUtils.getSimHash(str0),HashUtils.getSimHash(str1));
System.out.println("海明距离为:" + distance);
System.out.println("相似度为:" + similarity);
}
@Test
public void hammingDistanceTest4() {
String str0 = TxtUtils.readFile("D:/test/orig.txt");
String str1 = TxtUtils.readFile("D:/test/orig_0.8_dis_1.txt");
int distance = HammingUtils.calculateHammingDistance(Objects.requireNonNull(HashUtils.getSimHash(str0)), Objects.requireNonNull(HashUtils.getSimHash(str1)));
double similarity = HammingUtils.calculateSimilarity(HashUtils.getSimHash(str0),HashUtils.getSimHash(str1));
System.out.println("海明距离为:" + distance);
System.out.println("相似度为:" + similarity);
}
}
测试结果:
代码覆盖率:
4.主测试 MainTest
代码:
public class MainTest {
@Test
public void mainTestAll() {
String[] args = new String[6];
args[0] = "D:/test/orig.txt";
args[1] = "D:/test/orig_0.8_add.txt";
args[2] = "D:/test/orig_0.8_del.txt";
args[3] = "D:/test/orig_0.8_dis_1.txt";
args[4] = "D:/test/orig_0.8_dis_10.txt";
args[5] = "D:/test/orig_0.8_dis_15.txt";
String answerPath = "D:/test/answerAll.txt";
for (int i=1; i< args.length;i++){
double answer = HammingUtils.calculateSimilarity(HashUtils.getSimHash(TxtUtils.readFile(args[0])),HashUtils.getSimHash(TxtUtils.readFile(args[i])));
TxtUtils.writeFile(answer, answerPath);
}
}
@Test
public void mainTeatSame() {
String str = "D:/test/orig.txt";
String answerPath = "D:/test/answerSame.txt";
double answer = HammingUtils.calculateSimilarity(HashUtils.getSimHash(TxtUtils.readFile(str)),HashUtils.getSimHash(TxtUtils.readFile(str)));
TxtUtils.writeFile(answer,answerPath);
}
}
测试结果:
代码覆盖率:
结果文件:
五、异常处理
- 文本长度太短时,HanLp无法取得关键字,需要抛出异常。
// 文本长度太短 HanLp无法取关键字
try{
if(str.length() < 200) throw new ExceptionUtils("文本过短!");
}catch (ExceptionUtils e){
e.printStackTrace();
return null;
}
public class ExceptionUtils extends RuntimeException {
// 构造函数:仅带消息参数
public ExceptionUtils(String message) {
super(message);
}
}
测试:
public class ExceptionUtilsTest {
@Test
public void exceptionTest(){
//str.length()<200
System.out.println(HashUtils.getSimHash("世界这么大"));
}
@Test
public void exceptionTest0(){
//长文本
String str = TxtUtils.readFile("D:/test/orig.txt");
System.out.println(HashUtils.getSimHash(str));
}
}
- 判断命令行输入参数的数量
if (args.length != 3) {
System.out.println("请提供正确的参数:[原文文件] [抄袭版论文的文件] [答案文件]");
return;
}
六、附录
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | 1130 | 1350 |
· Analysis | · 需求分析 (包括学习新技术) | 300 | 400 |
· Design Spec | · 生成设计文档 | 30 | 40 |
· Design Review | · 设计复审 | 30 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 40 | 30 |
· Design | · 具体设计 | 90 | 100 |
· Coding | · 具体编码 | 360 | 390 |
· Code Review | · 代码复审 | 40 | 50 |
· Test | · 测试(自我测试,修改代码,提交修改) | 240 | 300 |
Reporting | 报告 | 130 | 110 |
· Test Repor | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 40 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
· 合计 | 1320 | 1520 |