这个作业属于那个课程 | 软件工程-计科21级12班 |
---|---|
这个作业要求在哪里 | 个人项目 |
这个作业的目标 | 使用Java设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率 |
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
Estimate | 估计这个任务需要多少时间 | 80 | 80 |
Development | 开发 | 15 | 30 |
Analysis | 需求分析(包括学习新技术) | 50 | 40 |
Design Spec | 生成设计文档 | 5 | 15 |
Design Review | 设计复审 | 20 | 20 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 15 | 15 |
Design | 具体设计 | 20 | 30 |
Coding | 具体编码 | 60 | 75 |
Coding Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 45 | 50 |
Reporting | 报告 | 60 | 70 |
Test Repor | 测试报告 | 30 | 25 |
Size Measurement | 计算工作量 | 20 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 15 | 10 |
合计 | 485 | 540 |
二、模块接口的设计与实现过程
总体设计
依赖
JUnit测试以及Logback日志依赖
工具类
IOUtils
- 读取文件内容方法:readFileContent
- 使用字符缓冲流读取数据,文件路径有误则使用日志在控制台输出问题
- 写入文件内容方法:writeFileContent
- 使用字符缓冲流写入数据,文件路径有误则使用日志在控制台输出问题
CheckUtils
-
计算字符串相似度方法:calculateSimilarity
-
算法介绍
-
使用 Levenshtein 距离算法,也称为编辑距离算法,是一种用于衡量两个字符串之间差异程度的度量方法。它通过计算将一个字符串转换为另一个字符串所需的最少编辑操作次数来量化字符串之间的差异
-
动态规划思想:Levenshtein距离算法使用动态规划的思想,通过构建一个二维矩阵来处理字符串比较问题。
-
状态转移方程:算法通过迭代更新矩阵中的值,以获得最小编辑距离。状态转移方程如下:
dp[i][j] = min(dp[i-1][j-1] + cost, dp[i-1][j] + 1, dp[i][j-1] + 1);
其中dp[i] [j]表示字符串1的前i个字符与字符串2的前j个字符之间的编辑距离,cost表示第i和第j个字符是否匹配,若不匹配则cost为1,否则为0。min表示取三个参数的最小值。
-
初始化操作:将矩阵的第一行和第一列初始化为对应的索引值,因为将一个字符串转换为空字符串或反之所需的编辑操作次数等于索引值。
-
计算最小编辑距离:迭代填充矩阵,并获取矩阵的右下角元素的值,即为字符串的最小编辑距离。
-
-
算法独到之处
- 灵活性:算法可以用于不同类型的字符串,包括文本、代码和其他形式的数据。它不依赖于特定的领域知识,因此适用于各种应用场景。
- 不敏感性:与其他相似度度量方法相比,Levenshtein距离算法对字符串的长度变化和字符顺序的变化不敏感。它关注的是字符的匹配和替换,而不考虑它们的位置。
- 适应性:Levenshtein距离算法可以根据需求进行扩展和定制。根据编辑操作的成本和约束条件,可以修改状态转移方程,以便更精确地度量字符串之间的差异。
-
三、模块接口部分的性能改进
使用Intellij Profiler分析性能
四、模块部分单元测试展示
readFileContentTest1
-
代码:
@Test public void readFileContentTest1() { // 读取文本内容测试 IOUtils.readFileContent("D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig.txt"); }
- 测试方法:IOUtils.readFileContent
- 测试覆盖率:
readFileContentTest2
-
代码:
@Test public void readFileContentTest2() { // 读取文本内容测试 IOUtils.readFileContent("orig.txt"); }
- 测试方法:IOUtils.readFileContent
- 测试覆盖率:
writeFileContentTest
-
代码:
@Test public void writeFileContentTest() { // 写入文本内容测试 IOUtils.writeFileContent("D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\target.txt", "hello"); }
- 测试方法:IOUtils.writeFileContent
- 测试覆盖率:
CheckUtilsTest
-
代码:
@Test public void calculateSimilarityTest() { // 计算文本内容相似度测试 String orig = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig.txt"; String target = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\target.txt"; String imit1 = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig_0.8_add.txt"; String imit2 = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig_0.8_del.txt"; String imit3 = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig_0.8_dis_1.txt"; String imit4 = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig_0.8_dis_10.txt"; String imit5 = "D:\\HYHAO\\Documents\\duplication-check\\3121004860\\duplication-check\\src\\main\\resources\\orig_0.8_dis_15.txt"; // 定义一个抄袭论文集合 List<String> imits = new ArrayList<>(); // 将抄袭论文添加到集合中 Collections.addAll(imits, imit1, imit2, imit3, imit4, imit5); for (String imit : imits) { // 计算相似度并写入target文件中 double result = CheckUtils.calculateSimilarity(IOUtils.readFileContent(orig), IOUtils.readFileContent(imit)); String message = "文件 " + imit + " 查重率为:" + String.format("%.2f", result); System.out.println(message); IOUtils.writeFileContent(target, message); } }
- 测试函数:CheckUtils.calculateSimilarity
- 测试覆盖率:
五、模块部分异常处理说明
IO读写异常处理
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
} catch (IOException e) {
LOGGER.error("文件读取失败...");
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
writer.write(message);
writer.newLine();
writer.flush();
} catch (IOException e) {
LOGGER.error("打开文件失败...");
}
命令行参数输入异常处理
if (args.length != 3) {
LOGGER.error("命令行输入参数个数有误...");
} else {
for (String arg : args) {
if (!arg.endsWith("txt")) {
LOGGER.error("命令行输入参数文本类型有误...");
return;
}
}
}