第一次个人编程作业
这个作业属于哪个课程 | 计科21级12班 |
---|---|
这个作业要求在哪里 | 个人项目 |
这个作业的目标 | 熟悉个人项目的流程与使用PSP表格 |
作业需求
-
题目:论文查重
-
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。 -
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。 -
要求输入输出采用文件输入输出,规范如下:
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 570 | 640 |
Development | 开发 | 450 | 500 |
· Analysis | · 需求分析 (包括学习新技术) | 100 | 120 |
· Design Spec | · 生成设计文档 | 30 | 20 |
· Design Review | · 设计复审 | 30 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 180 | 150 |
· Code Review | · 代码复审 | 30 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 60 |
Reporting | 报告 | 40 | 40 |
· Test Repor | · 测试报告 | 30 | 40 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 40 |
· 合计 | 600 | 670 |
Github仓库
接口的设计和实现
1、设计思路
- 此次采用SimHash+海明距离的方式计算相似度。
主要流程为:读取文件->分词->计算SimHash->计算相似度->输出到文件
2、依赖的jar包
//汉语言处理包
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.8.4</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
3、类
- Filehandling.java 文件处理
- HammingDistance.java 计算相似度
- MyException.java 异常处理
- Simhash.java 计算SimHash值
- Main.java main函数所在的类
4、接口实现
4.1、Filehandling类
- 其内包含两个方法
- readTxt:读取文件并进行预处理
- 关键代码
处理文本的特殊符号,并使用Jsoup包的接口清除Html格式
String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", " ", "&", "<", ">", """, "&qpos;"};
for (String string : strings) {
A= A.replaceAll(string, "");
}
A = Jsoup.clean(A, Whitelist.none());
return A;
- writeTxt:将文本写入文件
4.2、Simhash类
- 其内包含两个方法:
- MD5:调用计算hash值
- GetSimhash:计算SimHash值
GetSimhash关键代码如下:
此处调用汉语言处理包中的接口计算词频并据此得出关键词
//计算词频并得出关键词进行分词
TermFrequencyCounter counter = new TermFrequencyCounter();
counter.add(sentence);
Map<String, Integer> wordFreqMap = new HashMap<>();
for(TermFrequency keyword:counter){
wordFreqMap.put(keyword.getTerm(), keyword.getFrequency());
}
//加权计算
for (Map.Entry<String, Integer> entry : wordFreqMap.entrySet()){
//获得hash值
StringBuilder hash= new StringBuilder(Objects.requireNonNull(MD5(entry.getKey())));
if(hash.length()<128){
hash.append("0".repeat(Math.max(0, 128 - hash.length())));
}
//以词频作为权值
for(int j=0;j<128;j++){
if(hash.charAt(j)=='1'){
v[j]+= entry.getValue();
}
else{
v[j]-= entry.getValue();
}
}
}
//计算simhash值
StringBuilder simHash= new StringBuilder();
for(int i=0;i<128;i++){
if(v[i]>0){
simHash.append("1");
}
else {
simHash.append("0");
}
}
4.3、HammingDistance类
- GetSimilarity:对数据进行简单的计算,此处省略
4.4、MyException类
- 自定义异常类,用于抛出自定义的异常信息
4.5、Main类
- 包含main函数,接收命令行参数,并将其传给对应方法计算出相似度,将结果写入文件
单元测试
1、Filehandling类
- 主要测试:
- 路径错误读入
- 正常读入
- 路径错误写出
- 正常写出
- 对文本预处理
- 路径为非txt文件
- 测试代码如下:
@Test
void readTxtfailTest() {
//路径错误,读取失败
String str =Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\none.txt");
}
@Test
void readTxtfail2Test() {
//非txt文件
String str =Filehandling.readTxt("none");
}
@Test
void readTxtTest() {
//路径正确,正常读取
String str =Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\test.txt");
System.out.println(str);
}
@Test
void writeTxtTest() {
//路径正确,写入成功
String str="1.23";
Filehandling.writeTxt(str,"none.txt");
}
@Test
void writeTxtfailTest() {
//路径错误,写入失败
String str="1.23";
Filehandling.writeTxt(str,"d\\none.txt");
}
@Test
void writeTxtfail2Test() {
//非txt文件
String str="1.23";
Filehandling.writeTxt(str,"none");
}
@Test
void readTxtHtmlTest(){
//文本预处理,去除Html标签
String str =Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\test2.txt");
System.out.println(str);
}
-
测试结果
-
代码覆盖率
2、HammingDistance类
- 测试代码
void getSimilarityTest() {
String path1 = Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig.txt");
String path2= Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig_0.8_add.txt");
String simHash0 = Simhash.GetSimhash(path1);
String simHash1 = Simhash.GetSimhash(path2);
String similarity = HammingDistance.GetSimilarity(simHash0, simHash1);
System.out.println(similarity);
}
-
测试结果
-
代码覆盖率
3、Simhash类
- 主要测试:
- 正常运行
- 文本过短
- 测试代码
@Test
void MD5Test() {
String[] strings = {"一位", "真正", "的", "作家","永远","只为"};
for (String string : strings) {
String stringHash = Simhash.MD5(string);
System.out.println(stringHash.length());
System.out.println(stringHash);
}
}
@Test
void GetSimhashTest() {
String str1 = "一位真正的作家永远只为内心写作,只有内心才会真实地告诉他,他的自私、他的高尚是多么突出。内心让他真实地了解自己,一旦了解了自己也就了解了世界。很多年前我就明白了这个原则,可是要捍卫这个原则必须付出艰辛的劳动和长时期的痛苦,因为内心并非时时刻刻都是敞开的,它更多的时候倒是封闭起来,于是只有写作,不停地写作才能使内心敞开,才能使自己置身于发现之中,就像日出的光芒照亮了黑暗,灵感这时候才会突然来到。长期以来,我的作品都是源出于和现实的那一层紧张关系。我沉湎于想象之中,又被现实紧紧控制,我明确感受着自我的分裂,我无法使自己变得纯粹,我曾经希望自己成为一位童话作家,要不就是一位实实在在作品的拥有者,如果我能够成为这两者中的任何一个,我想我内心的痛苦将会轻微得多,可是与此同时我的力量也会削弱很多。事实上我只能成为现在这样的作家,我始终为内心的需要而写作,理智代替不了我的写作,正因为此,我在很长一段时间是一个愤怒和冷漠的作家。";
String str2= "于是村里人就知道那个会讲荤故事会唱酸曲的人又来了。其实所有的荤故事所有的酸曲都是从他们那里学来的,我知道他们全部的兴趣在什么地方,自然这也是我的兴趣。我曾经遇到一个哭泣的老人,他鼻青眼肿地坐在田埂上,满腹的悲哀使他变得十分激动,看到我走来他仰起脸哭声更为响亮。我问他是谁把他打成这样的?他手指挖着裤管上的泥巴,愤怒地告诉我是他那不孝的儿子,当我再问为何打他时,他支支吾吾说不清楚了,我就立刻知道他准是对儿媳干了偷鸡摸狗的勾当。还有一个晚上我打着手电赶夜路时,在一口池塘旁照到了两段赤裸的身体,一段压在另一段上面,我照着的时候两段身体纹丝不动,只是有一只手在大腿上轻轻搔痒,我赶紧熄灭手电离去。在农忙的一个中午,我走进一家敞开大门的房屋去找水喝,一个穿短裤的男人神色慌张地挡住了我,把我引到井旁,殷勤地替我打上来一桶水,随后又像耗子一样窜进了屋里。这样的事我屡见不鲜,差不多和我听到的歌谣一样多,当我望着到处都充满绿色的土地时,我就会进一步明白庄稼为何长得如此旺盛。那个夏天我还差一点谈情说爱,我遇到了一位赏心悦目的女孩,她黝黑的脸蛋至今还在我眼前闪闪发光。我见到她时,她卷起裤管坐在河边的青草上,摆弄着一根竹竿在照看一群肥硕的鸭子。这个十六七岁的女孩,羞怯地与我共同度过了一个炎热的下午,她每次露出笑容时都要深深地低下头去,我看着她偷偷放下卷起的裤管,又怎样将自己的光脚丫子藏到草丛里去。那个下午我信口开河,向她兜售如何带她外出游玩的计划,这个女孩又惊又喜。我当初情绪激昂,说这些也是真心实意。我只是感到和她在一起身心愉快,也不去考虑以后会是怎样。可是后来,当她三个强壮如牛的哥哥走过来时,我才吓一跳,我感到自己应该逃之夭夭了,否则我就会不得不娶她为妻。我遇到那位名叫福贵的老人时,是夏天刚刚来到的季节。";
System.out.println(Simhash.GetSimhash(str1));
System.out.println(Simhash.GetSimhash(str2));
}
@Test
void GetSimhashfailTest(){
//处理过短文本时,会出现无法得到关键词的情况,此时会异常
String str ="123";
Simhash.GetSimhash(str);
}
@Test
void MD5failTest() {
Simhash.MD5("");
}
-
测试结果
-
代码覆盖率
4、Main类
- 将测试文本全部进行测试
@Test
void mainTest() {
String path1 = Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig.txt");
String path2= Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig_0.8_add.txt");
String path3 = Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig_0.8_del.txt");
String path4= Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig_0.8_dis_1.txt");
String path5 = Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig_0.8_dis_10.txt");
String path6= Filehandling.readTxt("E:\\GitHub\\MZiShi\\3121004832\\PaperChecking\\src\\main\\resources\\测试文本\\orig_0.8_dis_15.txt");
String simHash1 = Simhash.GetSimhash(path1);
String simHash2 = Simhash.GetSimhash(path2);
String simHash3 = Simhash.GetSimhash(path3);
String simHash4 = Simhash.GetSimhash(path4);
String simHash5 = Simhash.GetSimhash(path5);
String simHash6 = Simhash.GetSimhash(path6);
String similarity1 = HammingDistance.GetSimilarity(simHash1, simHash2);
String similarity2 = HammingDistance.GetSimilarity(simHash1, simHash3);
String similarity3 = HammingDistance.GetSimilarity(simHash1, simHash4);
String similarity4 = HammingDistance.GetSimilarity(simHash1, simHash5);
String similarity5 = HammingDistance.GetSimilarity(simHash1, simHash6);
Filehandling.writeTxt(similarity1, "answer.txt");
Filehandling.writeTxt(similarity2, "answer.txt");
Filehandling.writeTxt(similarity3, "answer.txt");
Filehandling.writeTxt(similarity4, "answer.txt");
Filehandling.writeTxt(similarity5, "answer.txt");
}
- 测试结果
异常处理
1、短文本处理
- 文本过短时无法提出关键词,此时应该异常扔出
try {
if(sentence.length()<10){
throw new MyException("文本过短无意义");
}
} catch (MyException e) {
e.printStackTrace();
return null;
}
2、非txt处理
try{
if (!txtPath.endsWith(".txt")){
throw new MyException("非txt类型文件");
}
}catch (MyException e){
e.printStackTrace();
return ;
}
性能分析
如图因为此次一次读取全部测试文本进行测试,故用于存储文本占用较大内存;
除此之外占用大内存的为Hanlp语言处理的接口;
综上,难以提升性能,可考虑通过采用其他语言处理包