个人第一次编程作业
一、简述
Documents | Content |
---|---|
作业所属的课程 | 班级地址 |
作业要求 | 第一次编程作业要求 |
二、项目地址
三、项目需求
论文查重
描述:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
例子:
- 原文:今天是星期天,天气晴,今天晚上我要去看电影。
- 抄袭之后的文本:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
四、程序开发环境
- ①开发语言:Java
- ②开发环境:IntelliJ IDEA Community Edition 2020.2.1
- ③单元测试工具:Junit
- ④性能分析工具:Jprofiler
五、PSP表格(预计花费时间)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
· Planning | · 计划 | 90 | |
· Estimate | · 估计这个任务需要多少时间 | 30 | |
Development | · 开发 | 800 | |
· Analysis | · 需求分析 (包括学习新技术) | 200 | |
· Design Spec | · 生成设计文档 | 120 | |
· Design Review | · 设计复审 | 120 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | |
· Design | · 具体设计 | 90 | |
· Coding | · 具体编码 | 100 | |
· Code Review | · 代码复审 | 120 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | |
Reporting | 报告 | 60 | |
· Test Repor | · 测试报告 | 60 | |
· Size Measurement | · 计算工作量 | 30 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | |
· 合计 | 1940 |
六、计算模块接口的设计与实现过程
1.接口主要实现的功能:通过求出两个文本内容的海明距离来求出其相似度
2.simHash算法
simHash的主要思想是降维,将高维的特征向量映射成低维的特征向量,通过两个向量的Hamming Distance来确定文章是否重复或者高度近似。其中,Hamming Distance,又称汉明距离,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。也就是说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:1011101 与 1001001 之间的汉明距离是 2。至于我们常说的字符串编辑距离则是一般形式的汉明距离。如此,通过比较多个文档的simHash值的海明距离,可以获取它们的相似度。
simHash流程主要有五个步骤:分词、hash、加权、合并、降维。
分词:给定一段语句,进行分词,得到有效的特征向量,然后为每一个特征向量设置1-5等5个级别的权重(如果是给定一个文本,那么特征向量可以是文本中的词,其权重可以是这个词出现的次数)。
hash:通过hash函数计算各个特征向量的hash值,hash值为二进制数01组成的n-bit签名。
加权:在hash值的基础上,给所有特征向量进行加权,即W = Hash * weight,且遇到1则hash值和权值正相乘,遇到0则hash值和权值负相乘。
合并:将上述各个特征向量的加权结果累加,变成只有一个序列串。
降维:对于n-bit签名的累加结果,如果大于0则置1,否则置0,从而得到该语句的simhash值,最后我们便可以根据不同语句simhash的海明距离来判断它们的相似度。
3.海明距离计算
海明距离是序列相同位置上数据不同的个数,比如abc和acb,海明距离是第二位和第三位不同,海明距离是2。
具体实现:
七、性能分析
可以看出消耗较多的函数为用在浮点计算中的Float、hanlp包所提供的函数等
八、单元测试
1.主测试方法
package libgem.mainclass;
import libgem.utils.SimHash;
import libgem.utils.Txt_TO_String;
import libgem.utils.Utils_Hamming;
import org.junit.Test;
public class TestMainClass {
@Test
// 测试原文本和其他所有抄袭文本的查重
public void TestforAllTxt(){
String[] strings = new String[6];
strings[0] = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
strings[1] = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_add.txt");
strings[2] = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_del.txt");
strings[3] = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_dis_1.txt");
strings[4] = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_dis_10.txt");
strings[5] = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_dis_15.txt");
}
@Test
// 测试原文本和原文本比较的查重率
public void origEqualTest(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String compTxt = "E:/3119005458SoftW/test/equTxt.txt";
double equal = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
Txt_TO_String.txt_write_String(equal, compTxt);
}
@Test
// 测试orig和orig_0.8_add查重率
public void origAddTest(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_add.txt");
String compTxt = "E:/3119005458SoftW/test/addTxt.txt";
double compAdd = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
Txt_TO_String.txt_write_String(compAdd, compTxt);
}
@Test
// 测试orig和orig_0.8_del查重率
public void origDelTest(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_del.txt");
String compTxt = "E:/3119005458SoftW/test/delTxt.txt";
double compDel = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
Txt_TO_String.txt_write_String(compDel, compTxt);
}
@Test
// 测试orig和orig_0.8_dis_1查重率
public void origDis1Test(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_dis_1.txt");
String compTxt = "E:/3119005458SoftW/test/dis1Txt.txt";
double compDis1 = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
Txt_TO_String.txt_write_String(compDis1, compTxt);
}
@Test
// 测试orig和orig_0.8_dis_10查重率
public void origDis10Test(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_dis_10.txt");
String compTxt = "E:/3119005458SoftW/test/dis10Txt.txt";
double compDis10 = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
Txt_TO_String.txt_write_String(compDis10, compTxt);
}
@Test
// 测试orig和orig_0.8_dis_15查重率
public void origDis15Test(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/orig_0.8_dis_15.txt");
String compTxt = "E:/3119005458SoftW/test/dis15Txt.txt";
double compDis15 = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
Txt_TO_String.txt_write_String(compDis15, compTxt);
}
}
2.Hamming距离测试
public class TestForHamming {
@Test
public void testHammingDistance(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig_0.8_add.txt");
int distance = Utils_Hamming.hammingDistance(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
System.out.println("海明距离:" + distance);
System.out.println("相似度" + (100 - distance * 100 / 128) + "%");
}
@Test
public void hammingDistanceException(){
// 如果出现两个获取的字符串长度不一样的情况
String str0 = "10101010";
String str1 = "1010101";
System.out.println(Utils_Hamming.hammingDistance(str0, str1));
}
@Test
public void SimilarityTest(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig_0.8_add.txt");
int hammingDistance = Utils_Hamming.hammingDistance(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
double similarity = Utils_Hamming.similarity(SimHash.getSimHash(str0), SimHash.getSimHash(str1));
System.out.println("str0和str1之间的海明距离为:" + hammingDistance);
System.out.println("str0和str1的相似度为:" + similarity);
}
}
3.simHash流程测试
public class TestForSimHash {
@Test
public void TestForGetHash(){
String[] strings = {"广东", "工业" , "大学", "计算机学院", "信息", "安全"};
for(String string : strings){
String hash_String = SimHash.getHash_for_String(string);
System.out.println(hash_String.length());
System.out.println(hash_String);
}
}
@Test
public void TestForSimHash(){
String str0 = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig.txt");
String str1 = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig_0.8_add.txt");
System.out.println(SimHash.getSimHash(str0));
System.out.println(SimHash.getSimHash(str1));
}
}
4.对文本内容输入输出测试
public class TestForTxt_TO_String {
@Test
public void TestFor_getTxt(){
// 如果文件路径存在的话,则正常读取文件
String str = Txt_TO_String.getStringForTxt("E:/3119005458/test/orig.txt");
String[] strings = str.split(" "); // 切割文本
for (String string : strings){
System.out.println(string);
}
}
@Test
public void TestFor_getTxt_Fail(){
// 如果读取文件的路径不存在的话,则读取失败
String str = Txt_TO_String.getStringForTxt("E:/3119005458SoftW/test/fail.txt");
}
@Test
public void write_Txt_Test(){
// 当路径存在时,测试查重率的写入是否正常
double[] rate_rechecking = {0.10, 0.99, 0.24, 0.34, 0.67};
for (int i = 0; i < rate_rechecking.length; i++) {
Txt_TO_String.txt_write_String(rate_rechecking[i], "E:/3119005458SoftW/test/result.txt");
}
}
@Test
public void write_txt_test_fail(){
// 路径不存在时,对数据写入错误的测试
double[] rate_recheck = {0.10, 0.26, 0.45, 0.25, 0.57};
for (int i = 0; i < rate_recheck.length; i++) {
Txt_TO_String.txt_write_String(rate_recheck[i], "E:/3119005458SoftW/test/fail.txt");
}
}
}
5.测试结果文件
九、异常处理
由于导入的外部hanLP依赖包只支持长度为200以上的关键字,所以当文本比较短的时候会报出ShortStringException的异常。
异常处理模块:
public class TestForShortStringException {
@Test
public void test_for_shortString(){
// 测试hanlp包在str.length() < 200 的情况
System.out.println(SimHash.getSimHash("广东工业大学"));
}
}
模块单元测试
十、PSP表格(程序设计完成之后)
由于本人的Java代码开发技术很有限,花了比想象中多得多的时间,通过这一次的作业,更加的了解在开发软件时需要注意的问题
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
· Planning | · 计划 | 90 | 120 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | · 开发 | 800 | 1200 |
· Analysis | · 需求分析 (包括学习新技术) | 200 | 300 |
· Design Spec | · 生成设计文档 | 120 | 110 |
· Design Review | · 设计复审 | 120 | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 90 |
· Design | · 具体设计 | 90 | 120 |
· Coding | · 具体编码 | 100 | 150 |
· Code Review | · 代码复审 | 120 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 45 |
Reporting | 报告 | 60 | 85 |
· Test Repor | · 测试报告 | 60 | 90 |
· Size Measurement | · 计算工作量 | 30 | 60 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 50 |
· 合计 | 1940 | 2690 |