第一次个人编程作业
| 这个作业属于哪个课程 | 19级网三网四软件工程 |
|---|---|
| 这个作业要求在哪里 | 作业要求 |
| 这个作业的目标 | <实现论文查重算法,学会使用PSP表格估计,学会单元测试> |
一、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| Estimate | ·估计这个任务需要多少时间 | 5 | 5 |
| Development | 开发 | ||
| Analysis | ·需求分析 (包括学习新技术) | 120 | 180 |
| Design Spec | ·生成设计文档 | 10 | 10 |
| Design Review | ·设计复审 | 30 | 40 |
| Coding Standard | ·代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| Design | ·具体设计 | 20 | 20 |
| Coding | ·具体编码 | 240 | 300 |
| Code Review | ·代码复审 | 30 | 30 |
| Test | ·测试(自我测试,修改代码,提交修改) | 60 | 70 |
| Reporting | 报告 | ||
| Test Repor | ·测试报告 | 30 | 25 |
| Size Measurement | ·计算工作量 | 20 | 15 |
| Postmortem & Process Improvement Plan | ·事后总结, 并提出过程改进计划 | 20 | 15 |
| ·合计 | 595 | 720 |
二、模块接口的设计与实现过程
1.主要实现类

其中Main类是作为程序的入口
Calculate类是将转换为字符串后的两个文本从词频的角度进行相似度计算
FileUtil类是用来进行文本与字符串之间的转换
Calculate类中的主要方法
采用HanLP提供的方法将文本分词后返回

对两个词组进行合并,获取共有的词

获取该词组在总词组中的词频

计算两个句子的相似度

其中计算相似度用到了 TF-IDF与余弦相似性的应用(二):找出相似文章
假设有两个句子A,B
句子A:我喜欢吃苹果,不喜欢吃榴莲。
句子B:我不喜欢吃苹果,也不喜欢吃榴莲。
第一步,分词。
句子A:我/喜欢/吃/苹果,不/喜欢/吃/榴莲。
句子B:我/不/喜欢/吃/苹果,也/不/喜欢/吃/榴莲。
实现:通过IKAnalyzer中文分词器实现,即通过查询内置的词典进行分词
第二步,列出所有的词。
我,喜欢,吃,苹果,榴莲,不,也。
第三步,计算词频。
句子A:我 1,喜欢 2,吃 2,苹果 1,榴莲 1,不 1,也 0。
句子B:我 1,喜欢 2,吃 2,苹果 1,榴莲 1,不 2,也 1。
第四步,写出词频向量。
句子A:[1, 2, 2, 1, 1, 1, 0]
句子B:[1, 2, 2, 1, 1, 2, 1]
实现:词与词频是键值对的形式,因此可以用哈希表来存储
到这里,问题就变成了如何计算这两个向量的相似程度。
我们可以把它们想象成空间中的两条线段,都是从原点([0, 0, ...])出发,指向不同的方向。两条线段之间形成一个夹角,如果夹角为0度,意味着方向相同、线段重合;如果夹角为90度,意味着形成直角,方向完全不相似;如果夹角为180度,意味着方向正好相反。因此,我们可以通过夹角的大小,来判断向量的相似程度。夹角越小,就代表越相似。
则计算A, B向量间的夹角的余弦值
假定a向量是[x1, y1],b向量是[x2, y2],那么可以将余弦定理改写成下面的形式:
余弦值越接近1,说明两向量间夹角越接近0,即两个句子越相似,因此余弦值即为两句子的相似度
2.程序流程图
将两个文本分别转换为字符串

将两个字符串输入Calculate提供的计算方法中,计算出结果并传给FileUtil,生成答案文件,
Calculate调用getSplitWords将文本分词,再合并出一个总词组,并计算出两个子词组的词频.计算出余弦相似度

三、计算模块部分单元测试展示
public class CaculateTest {
/**
* 测试句子
*/
@Test
public void sentence(){
String source = "我们去打球";
String fake = "我们去学习";
double res = Calculate.caculate(source,fake);
assert res < 1;
}
/**
* 测试与自身查重
*/
@Test
public void orig(){
String source = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
double res = Calculate.caculate(source,fake);
assert res == 1;
}
/**
* 测试与增加版查重
*/
@Test
public void orig_add(){
String source = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/orig_0.8_add.txt");
double res = Calculate.caculate(source,fake);
assert res <= 1;
}
/**
* 测试与删减版查重
*/
@Test
public void orig_del(){
String source = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/orig_0.8_del.txt");
double res = Calculate.caculate(source,fake);
assert res <= 1;
}
/**
* 测试orig_0.8_dis_1.txt
*/
@Test
public void orig_dis(){
String source = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/orig_0.8_dis_1.txt");
double res = Calculate.caculate(source,fake);
assert res <= 1;
}
/**
* 测试orig_0.8_dis_10.txt
*/
@Test
public void orig_dis2(){
String source = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/orig_0.8_dis_10.txt");
double res = Calculate.caculate(source,fake);
assert res <= 1;
}
/**
* 测试orig_0.8_dis_15.txt
*/
@Test
public void orig_dis3(){
String source = FileUtil.fileToString("src/main/resources/testFile/orig.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/orig_0.8_dis_15.txt");
double res = Calculate.caculate(source,fake);
assert res <= 1;
}
/**
* 测试自己编写的文本
*/
@Test
public void testOwnFile(){
String source = FileUtil.fileToString("src/main/resources/testFile/testFileUtil.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile/testFileUtil.txt");
double res = Calculate.caculate(source,fake);
assert res == 1;
}
/**
* 测试自己编写的文本
*/
@Test
public void testOwnFileWithCopy(){
String source = FileUtil.fileToString("src/main/resources/testFile/testFileUtil.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile//testFileUtil_copy.txt");
double res = Calculate.caculate(source,fake);
assert res == 1;
}
/**
* 测试自己编写的文本
*/
@Test
public void testOwnFileWithAdd(){
String source = FileUtil.fileToString("src/main/resources/testFile/testFileUtil.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile//testFileUtil_add.txt");
double res = Calculate.caculate(source,fake);
assert res < 1;
}
/**
* 测试自己编写的文本
*/
@Test
public void testOwnFileWithDel(){
String source = FileUtil.fileToString("src/main/resources/testFile/testFileUtil.txt");
String fake = FileUtil.fileToString("src/main/resources/testFile//testFileUtil_del.txt");
double res = Calculate.caculate(source,fake);
assert res < 1;
}
}
测试覆盖率为
四、模块接口部分的性能改进


分析:程序在对文本的分词上花费了很长时间。
五、计算模块部分异常处理说明
1.IOException
采用空文本与原文对比测试
FileNotFoundException
采用路径错误的文本与原文对比测试
六、测试结果 (部分)
原文本与orig_0.8_dis_1进行对比测试

原文本与orig_0.8_add进行对比测试




浙公网安备 33010602011771号