软件工程导论作业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

四.代码设计

  1. 整个代码由一个主函数以及四个子函数组成,分别为主函数PaperCheck,子函数tokenizeFile buildWordFrequency buildVector calculateCosineSimilarity

  2. 对于tokenizeFile 其作用主要是:读取文件内容,并将其分词,去掉标点符号。

  3. 对于buildWordFrequency 其作用主要是:为文件中的每个单词统计出现的频率。

  4. 对于buildvector 其作用主要是:基于词汇表,将每个单词的频率转换为向量表示。

  5. 对于calculateCosineSimilarity 其作用主要是:根据公式计算两个向量的余弦相似度。

五.性能改进

  • 通过java idea中自带的profiler插件可以很清楚的观察到这个查重系统的主要占存以及可改进目标:



  1. 可以优化文件的提取和处理优化,当前实现中每次读取文件时都使用 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());
}

  1. 优化余弦相似度计算: 使用 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));
}

4. 增强错误处理: 在处理文件和 I/O 操作时,捕捉和记录更多类型的异常,例如 FileNotFoundException 和 IOException,并提供更多上下文信息。
点击查看代码
catch (FileNotFoundException e) {
    System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Error reading or writing file: " + e.getMessage());
}

5. 减少内存占用: 在处理大文件时,可以使用流处理技术来减少内存占用,例如 Stream API 来处理词频计算和向量构建。
点击查看代码
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);
    }
}

posted @ 2024-09-12 17:00  ra1n1  阅读(55)  评论(0编辑  收藏  举报