软件工程导论作业2 : Java实现论文查重
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13229 |
这个作业的目标 | <通过java开发完成论文查重算法并实行相对应的操作以及分析> |
一.GitHub项目地址
地址 | https://github.com/123Weather/3122004710 |
---|
二.需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
三.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 40 |
Estimate | 估计这个任务需要多少时间 | 300 | 360 |
Development | 开发 | 200 | 120 |
Analysis | 需求分析(包括学习新技术 | 180 | 120 |
Design Review | 生成设计文档 | 120 | 60 |
Coding Standard | 设计复审(为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 60 | 60 |
Coding | 具体编码 | 60 | 60 |
Code Review | 代码复审 | 30 | 20 |
Test | 测试(自我测试,修改代码,提交修改) | 90 | 120 |
Reporting | 报告 | 50 | 40 |
Test Repor | 测试报告 | 40 | 30 |
Size Measurement | 计算工作量 | 40 | 30 |
Postmortern & Process Improvement Plan | 事后总结,并提出过程改进计划 | 30 | 40 |
合计 | 1290 | 1130 |
四.代码设计
-
整个代码由一个主函数以及四个子函数组成,分别为主函数
PaperCheck
,子函数tokenizeFile
buildWordFrequency
buildVector
calculateCosineSimilarity
-
对于
tokenizeFile
其作用主要是:读取文件内容,并将其分词,去掉标点符号。 -
对于
buildWordFrequency
其作用主要是:为文件中的每个单词统计出现的频率。 -
对于
buildvector
其作用主要是:基于词汇表,将每个单词的频率转换为向量表示。 -
对于
calculateCosineSimilarity
其作用主要是:根据公式计算两个向量的余弦相似度。
五.性能改进
- 通过java idea中自带的profiler插件可以很清楚的观察到这个查重系统的主要占存以及可改进目标:
- 可以优化文件的提取和处理优化,当前实现中每次读取文件时都使用 Files.readString 读取整个文件内容。这在处理大文件时可能会导致内存消耗较大。可以考虑逐行读取文件,这样可以减少内存使用,并适用于大文件的处理。
private static List<String> tokenizeFile(String filePath) throws IOException {
List<String> words = new ArrayList<>();
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
words.addAll(Arrays.asList(line.split("\\W+")));
}
}
return words.stream()
.filter(word -> !word.isEmpty())
.collect(Collectors.toList());
}
2. 优化 buildVector 方法: 使用 Map 代替 List 来存储词频向量,避免在 Set 迭代时频繁查找词频。可以使用 List 进行排序后直接索引,而不是在构建词频向量时查找。
点击查看代码
private static List<Integer> buildVector(Set<String> allWords, Map<String, Integer> wordFreq) {
return allWords.stream()
.map(word -> wordFreq.getOrDefault(word, 0))
.collect(Collectors.toList());
}
- 优化余弦相似度计算: 使用 double 类型计算 dotProduct 和 norm 时,可以使用更精确的 Math.pow 来避免整数溢出或精度损失。
点击查看代码
private static double calculateCosineSimilarity(List<Integer> vectorA, List<Integer> vectorB) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vectorA.size(); i++) {
dotProduct += vectorA.get(i) * vectorB.get(i);
normA += Math.pow(vectorA.get(i), 2);
normB += Math.pow(vectorB.get(i), 2);
}
if (normA == 0 || normB == 0) {
return 0.0;
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
点击查看代码
catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error reading or writing file: " + e.getMessage());
}
点击查看代码
private static Map<String, Integer> buildWordFrequency(List<String> words) {
return words.stream()
.collect(Collectors.toMap(word -> word, word -> 1, Integer::sum));
}
六.单元测试、
- 冒烟
点击查看代码
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class PaperCheckSmokeTest {
@Test
public void testPaperCheckMain() throws IOException {
// Step 1: Setup - Create test files
Path origFile = Files.createTempFile("orig", ".txt");
Path plagiarizedFile = Files.createTempFile("plagiarized", ".txt");
Path outputFile = Files.createTempFile("output", ".txt");
Files.write(origFile, List.of("This is a test."));
Files.write(plagiarizedFile, List.of("This is a simple test."));
// Step 2: Run the main method of PaperCheck
PaperCheck.main(new String[]{origFile.toString(), plagiarizedFile.toString(), outputFile.toString()});
// Step 3: Verify output
List<String> outputLines = Files.readAllLines(outputFile);
// Check if output file contains some result, assume result will be a similarity score
assert !outputLines.isEmpty() : "Output file is empty";
// Clean up temporary files
Files.deleteIfExists(origFile);
Files.deleteIfExists(plagiarizedFile);
Files.deleteIfExists(outputFile);
}
}
- 读文件单元测试
点击查看代码
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PaperCheckTest {
@Test
public void testReadFile() throws IOException {
// Step 1: Setup - Create a test file
Path testFile = Files.createTempFile("test", ".txt");
String expectedContent = "This is a test file.";
Files.write(testFile, List.of(expectedContent));
// Step 2: Call the method to read the file
PaperCheck paperCheck = new PaperCheck();
String actualContent = paperCheck.readFile(testFile.toString());
// Step 3: Verify the output
assertEquals(expectedContent, actualContent, "File content does not match expected content.");
// Clean up temporary file
Files.deleteIfExists(testFile);
}
}