第一次个人编程作业
第一次个人编程作业
这个作业属于哪个课程 | <班级链接> |
---|---|
这个作业要求在哪里 | <作业要求的链接> |
这个作业的目标 | 论文查重算法+PSP表格+使用JProfiler性能分析+Git管理+单元测试 |
1 代码链接
- Github链接:https://github.com/lz2y/3119005464
- 可运行jar包已经发布至仓库的releases
2 计算模块接口的设计
本次作业需要实现一个 论文查重 软件
传统的 hash 算法只负责将原始内容尽量均匀随机地映射为一个签名值,原理上相当于伪随机数产生算法。产生的两个签名,如果相等,说明原始内容在一定概 率 下是相等的;如果不相等,除了说明原始内容不相等外,不再提供任何信息,因为即使原始内容只相差一个字节,所产生的签名也很可能差别极大。
我们可以使用 SimHash 来实现我们想要的功能,它除了能够提供原始内容是否相等的信息外,还能额外提供不相等的,原始内容的差异程度的信息,我们可以配合海明距离判断两篇论文的重复率
Simhash 主要步骤为:分词、hash、加权、合并、降维
得到 Simhash 之后,我们可以使用海明距离来判断相似度,一般来说,海明距离小于等于3就是相似
海明距离:在信息编码中,两个合法代码对应位上编码不同的位数称为码距,又称海明距离。举例如下:10101和00110从第一位开始依次有第一位、第二、第五位不同,则海明距离为3。
计算 Jaccard 相似度,可以把它理解为我们最后的相似度
Jaccard index [1] , 又称为Jaccard相似系数(Jaccard similarity coefficient)用于比较有限样本集之间的相似性与差异性。Jaccard系数值越大,样本相似度越高。
所以我们大概需要三个类
- 一个类用来存放 SimHash 的一些属性,并提供 SimHash 的计算方法
- 一个类用来实现海明距离的计算
- 一个类调用其他方法,实现文件读取、相似度的计算以及结果的写入操作
3 计算模块接口的实现
最终实现的项目使用 maven 创建项目,由于类不多,就没有按照属性分成不同的包,具体结构如下:
主要类如下:
-
SimHash 类:用来存放 SimHash 的一些属性,并提供 SimHash 的计算方法
- getFrequency 方法调用分词器进行分词,遍历所有的单词,并计算它的词频放到一个 Map 中
- hash 方法是一个普通的hash算法,但是其扩充他的 hash 到 64 位
- simHash 方法用来计算 simHash 值,首先调用了 getFrequency 方法得到词频,调用 hash 方法得到长度为 64 的字符,对这 64 为字符进行加权计算,最后进行降维,把权重值大于零的位置设置为 1,小于 0 的位置设置为 0,得到文本的局部哈希值
-
HammingDistanceUtils 类:这是一个工具类,用来计算海明距离
- hammingDistance 方法用来计算海明距离,传入的值为 SimHash
- getDistance 方法用来计算海明距离,和1使用不同的方法,传入的值为 String
- getSimilarity 方法用来计算相似度,也就是论文最后的相似度
-
Main 类:用来实现程序的逻辑,试上面两个类的方法关联起来,计算论文的相似度
- main 程序的主要入口,根据传入的参数进行相似度的检查,接受的值为要求的 论文原文的文件的绝对路径、抄袭版论文的文件的绝对路径和输出的答案文件的绝对路径,调用 check 进行相似度的计算,是打包 jar 包时的主函数
- check 程序,实现相似度的主要逻辑,调用其他函数进行计算,把结果打印在屏幕上、并保存在指定的文件中
-
FilenameUtils 类:主要用于测试单元获取位于resources的文件路径
- getResourcesPath 方法,主要用于测试单元获取位于resources的文件路径
4 计算模块接口部分的性能改进
类的占用情况
方法测试
最多的还是文件读取
5 计算模块部分单元测试展示
主要是通过测试不同的文件原文件改进,空文件、添加、删除、错别字,打乱等测试,看是否能够正确读取、进行计算来判断
@Test // 正常情况
public void testMain() {
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_add.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // 测试待测文件为空情况
public void testNullText() {
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("empty.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // 测试待测文本不存在情况
public void testNullPath(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("fake.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // 测试待测文件为相同文件或相同内容
public void testSameText(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // 测试不同的文件格式(不报错,但因为读取的不是字,意义不大)
public void testOtherExt(){
String firstFilename = FilenameUtils.getResourcesPath("avatar.png");
String secondFilename = FilenameUtils.getResourcesPath("avatar.png");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // 测试添加了一些内容的文档
public void testAdd(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_add.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // 测试删除了一些内容的文档
public void testDel(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_del.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // dis10
public void testDis(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_dis_10.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // dis15
public void testDis15(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_dis_15.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // dis1
public void testDis1(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_dis_1.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
@Test // mix
public void testMix(){
String firstFilename = FilenameUtils.getResourcesPath("orig.txt");
String secondFilename = FilenameUtils.getResourcesPath("orig_0.8_mix.txt");
String outputFilename = FilenameUtils.getResourcesPath("out.txt");
Main.check(firstFilename, secondFilename, outputFilename);
}
代码覆盖率
测试结果
6 计算模块部分异常处理说明
-
输入的文件为空文件,则提示 ”文件为空,请重新输入“
-
输入的文件为不存在,则提示 ”文件不存在,请检查路径“
-
输入参数个数不符合要求,直接提示正确用法
7 PSP表格
PSP2.1 | ersonal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 30 |
Estimate | 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 400 | 360 |
Analysis | 需求分析 (包括学习新技术) | 60 | 120 |
Design Spec | 生成设计文档 | 60 | 60 |
Design Review | 设计复审 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 50 | 20 |
Design | 具体设计 | 30 | 30 |
Coding | 具体编码 | 160 | 150 |
Code Review | 代码复审 | 40 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 90 |
Reporting | 报告 | 150 | 160 |
Test Repor | 测试报告 | 60 | 60 |
Size Measurement | 计算工作量 | 40 | 40 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 50 |
合计 | 1190 | 1280 |
8 总结
在本次作业中,学到了很多,除了 Simhash 和 海明距离外,还学会了 github 的使用
在作业过程中,也遇到了一些比较坑的地方,如我在打包 jar 包时,发现生成的jar包运行结果和 idea 中直接运行结果不同,结果测试,是因为文件读取时编码问题,设置编码即可