第一次个人编程作业

这个作业属于哪个课程 22级计科1班
这个作业要求在哪里 作业要求
这个作业的目标 完成一次简单的个人项目开发的流程;设计一个论文查重算法

一、GitHub 链接

cloudsdong/3122004780 at master (github.com)

二、PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 15 12
· Estimate · 估计这个任务需要多少时间 15 12
Development 开发 200 270
· Analysis · 需求分析 (包括学习新技术) 30 25
· Design Spec · 生成设计文档 20 25
· Design Review · 设计复审 10 7
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 20
· Design · 具体设计 30 22
· Coding · 具体编码 40 85
· Code Review · 代码复审 20 12
· Test · 测试(自我测试,修改代码,提交修改) 40 74
Reporting 报告 95 140
· Test Repor · 测试报告 60 113
· Size Measurement · 计算工作量 5 3
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 24
合计 310 422

三、计算模块接口的设计与实现

1. Main 模块

  • 功能: 作为程序的入口,负责接收命令行参数,调用工具类进行文件的读取和相似度计算,并将最终结果输出到指定的文件。
  • 实现:
    • 从命令行获取三个参数:原文文件路径、抄袭版文件路径、结果输出文件路径。
    • 使用工具类读取文件内容。
    • 调用 StringUtils 模块来计算两个文本之间的相似度。
    • 将计算结果写入到输出文件中,确保浮点数格式精确到小数点后两位。

2. FileUtils模块

  • 功能: 文件处理模块,负责文件的读取和写入操作,保证输入输出的高效性和正确性。
  • 实现:
    • readFile(String filePath): 读取指定路径的文件,返回文件内容。
    • writeFile(String filePath, String content): 将指定内容写入到指定文件路径,如果文件不存在则创建新文件。
    • 该模块使用异常处理来捕获文件操作中可能发生的错误(如文件不存在、读写错误等),并反馈给用户或日志系统。

3. StringUtils模块

  • 功能: 文本处理与相似度计算的核心模块,包含计算两个文本之间的编辑距离(Levenshtein 距离)和计算文本相似度的功能。
  • 实现:
    • preprocessText(String text): 用于预处理文本内容,移除标点符号、空白字符以及进行大小写的统一。
    • levenshteinDistance(String s1, String s2): 计算两个字符串的 Levenshtein 编辑距离。这个函数使用动态规划算法,避免使用过多的内存。
    • calculateSimilarity(String original, String plagiarized): 计算两个文本的相似度,使用公式 similarity = (1 - (distance / maxLength)) * 100,并确保结果保留两位小数。

4. 算法的关键与独到之处

(1) levenshteinDistance 函数
这个函数是文本相似度计算的核心,负责计算两个字符串之间的编辑距离。其算法的复杂度为 O(n * m),n 和 m 分别为两个字符串的长度。

关键算法步骤:

初始化一个二维数组,大小为 (n + 1) x (m + 1),表示字符串 A 的每个字符与字符串 B 的每个字符的操作代价。
填充表格,表格的每个值表示从一个字符串变为另一个字符串所需的编辑操作数。
在表格中选择最小的操作数(插入、删除、替换),最终表格的右下角即为两个字符串的 Levenshtein 距离。

(2) calculateSimilarity 函数
这个函数使用 levenshteinDistance 计算两个文本的相似度,并输出百分比。

关键算法步骤

  1. 调用 levenshteinDistance函数,获取两个文本的编辑距离。

  2. 计算相似度,使用公式:
    $$
    similarity= \left(1 - \frac{distance}{\max(\text{length of A}, \text{length of B})}\right) \times 100
    $$

  3. 返回相似度值,保留两位小数。

  • 文本预处理:通过移除标点符号、空格和其他不必要的字符,保证了比较的有效性。这使得算法对常见的文本格式化差异具有鲁棒性。
  • Levenshtein 编辑距离的优化:使用动态规划算法减少计算时间,同时通过空间优化(例如仅保留两行的滚动数组),大幅减少内存使用。这是应对大文件比较时性能瓶颈的关键。
  • 模块化设计:将文本预处理与相似度计算分离,保持了高内聚性和低耦合性,为扩展其他相似度计算算法提供了灵活性。

5.组织和关系

(1)类的组织:

StringUtils 类负责核心的文本相似度计算,SimilarityCalculator 提供了高层接口封装,使得算法的调用更加简洁和灵活。
(2)函数的关系:

Main 函数通过调用 FileUtils 读取文件内容,再通过 SimilarityCalculator 计算文本相似度。

四、性能分析和覆盖率

1.性能分析

性能优化:

  1. 文件读取优化:使用缓冲读取(BufferedReader)代替一次性读取大文件,减少内存占用。
  2. 优化相似度算法:改进 Levenshtein 距离算法,或使用更快的相似度算法(如 Jaccard、余弦相似度),并考虑分块并行计算。
  3. 多线程处理:对多个抄袭文件进行相似度计算时,使用多线程并行处理提高效率。
  4. 减少 I/O 操作:将结果一次性写入文件,减少频繁的文件操作。
  5. 使用缓存:缓存相似度计算结果,避免重复计算相同的内容。
  6. 使用高效的数据结构和工具:如 StringBuilder 进行字符串拼接,并利用第三方库优化文本处理。

2.覆盖率

五、单元测试和异常处理

1. 完全相同的文本

设计目标

  • 验证在原文和抄袭文完全相同的情况下,计算出的相似度应为 100%。确保相同文本的相似度计算结果准确无误。

单元测试样例

// 测试1:完全相同的文本
 @Test
 public void testIdenticalText() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天,天气晴,今天晚上我要去看电影。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "今天是星期天,天气晴,今天晚上我要去看电影。");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("100.00", result);
 }

错误对应的场景:原文和抄袭文完全相同,期望相似度为 100%。如果结果不正确,则表示相似度计算存在问题。

2. 完全不同的文本

设计目标

  • 验证在原文和抄袭文完全不同的情况下,计算出的相似度应为 0%。确保不同文本的相似度计算结果准确无误。

单元测试样例

// 测试2:完全不同的文本
 @Test
 public void testCompletelyDifferentText() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天,天气晴。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "我要去公园玩!");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("0.00", result);
 }

错误对应的场景:原文和抄袭文完全不同,期望相似度为 0%。如果结果不正确,则表示相似度计算存在问题。

3. 原文为空

设计目标

  • 验证当原文件为空时,计算出的相似度应为 0%。确保当原文为空而抄袭文有内容时,计算结果正确。

单元测试样例

// 测试3:原文为空
 @Test
 public void testEmptyFile() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "");
     FileUtils.writeFile(PLAGIARIZED_FILE, "我今天要去公园玩。");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("0.00", result);
 }

错误对应的场景:原文件为空,抄袭文有内容,期望相似度为 0%。如果结果不正确,则表示空文件处理存在问题。

4. 抄袭文为空

设计目标

  • 验证当抄袭文件为空时,计算出的相似度应为 0%。确保当抄袭文为空而原文有内容时,计算结果正确。

单元测试样例

// 测试4:抄袭文为空
 @Test
 public void testEmptyPlagiarizedFile() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "我今天要去公园玩。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("0.00", result);
 }

错误对应的场景:抄袭文件为空,原文件有内容,期望相似度为 0%。如果结果不正确,则表示空文件处理存在问题。

5. 包含特殊字符

设计目标

  • 验证在处理包含特殊字符的文本时,程序能够正确计算相似度,特殊字符应被正确处理或忽略。

单元测试样例

// 测试5:包含特殊字符
 @Test
 public void testSpecialCharacters() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天!天气很好。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "今天是星期天?天气很好!");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("83.33", result);
 }

错误对应的场景:文本中含有不同的特殊字符,期望相似度计算正确。如果结果不符合预期,则表示特殊字符处理存在问题。

6. 标点符号的差异

设计目标

  • 验证标点符号的差异是否会影响相似度计算。程序应忽略标点符号的差异。

单元测试样例

// 测试6:标点符号的差异
 @Test
 public void testPunctuationDifference() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天,天气晴,今天晚上我要去看电影。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "今天是星期天 天气晴 今天晚上我要去看电影");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("86.36", result);
 }

错误对应的场景:文本中的标点符号有所不同,期望相似度计算应忽略这些标点符号的差异。如果结果不符合预期,则表示标点符号处理存在问题。

7. 多余的空格

设计目标

  • 验证文本中多余的空格是否会影响相似度计算。程序应正确处理和忽略多余的空格。

单元测试样例

// 测试7:多余的空格
 @Test
 public void testMultipleSpaces() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天,天气晴,今天晚上我要去看电影。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "今天 是 星期天 , 天气 晴 , 今天 晚上 我要 去 看 电影 。");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("100.00", result);
 }

错误对应的场景:文本中的空格多于正常情况,期望相似度计算应忽略多余的空格。如果结果不符合预期,则表示空格处理存在问题。

8. 部分相似文本

设计目标

  • 验证在部分相似文本或者近乎完全相同但有小改动的情况下,程序能够正确计算相似度。测试文本在细节上有所不同,但大体相似。

单元测试样例

// 测试8:部分相似文本
 @Test
 public void testPartiallySimilarText() throws IOException {
     FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天,天气晴,今天晚上我要去看电影。");
     FileUtils.writeFile(PLAGIARIZED_FILE, "今天是周天,天气晴朗,我晚上要去看电影。");

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("72.73", result);
 }

// 测试10:近乎完全相同但有小改动
@Test
public void testSlightlyModifiedText() throws IOException {
 FileUtils.writeFile(ORIGINAL_FILE, "今天是星期天,天气晴,今天晚上我要去看电影。");
 FileUtils.writeFile(PLAGIARIZED_FILE, "今天是星期天,天气晴,今天晚上我要去看电影了。");

 Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

 String result = FileUtils.readFile(ANSWER_FILE);
 assertEquals("95.65", result);
}

错误对应的场景:文本在某些方面相似,但有细微差异,期望计算出的相似度应反映这些差异。如果结果不符合预期,则表示相似度计算存在问题。

9. 大文件处理

设计目标

  • 验证程序能否处理非常大的文件,并确保大文件的相似度计算能够正确完成且不会导致内存不足问题。

单元测试样例

// 测试9:大文件处理
 @Test
 public void testLargeFiles() throws IOException {
     StringBuilder originalText = new StringBuilder();
     StringBuilder plagiarizedText = new StringBuilder();

     // 生成大量文本数据
     for (int i = 0; i < 1000; i++) { // 调整循环次数以适应系统内存
         originalText.append("今天是星期天,天气晴,今天晚上我要去看电影。");
         plagiarizedText.append("今天是周天,天气晴朗,我晚上要去看电影。");
     }

     FileUtils.writeFile(ORIGINAL_FILE, originalText.toString());
     FileUtils.writeFile(PLAGIARIZED_FILE, plagiarizedText.toString());

     Main.main(new String[]{ORIGINAL_FILE, PLAGIARIZED_FILE, ANSWER_FILE});

     String result = FileUtils.readFile(ANSWER_FILE);
     assertEquals("72.73", result);
 }

错误对应的场景:处理非常大的文件时,期望程序能正常计算相似度。如果发生内存不足或处理超时,表示程序在处理大文件时存在问题。

10. 命令行参数错误

设计目标

  • 验证当命令行参数不正确时,程序能够给出明确的错误提示。确保用户在输入参数时能够获得有用的反馈。

单元测试样例

@Test
public void testInvalidCommandLineArguments() {
    String[] args = {};  // 缺少所有必要的参数
    Main.main(args);

    // 捕获程序输出的错误消息,并验证
    String output = getProgramOutput();  // 方法用于捕获程序输出
    assertTrue(output.contains("请输入正确的参数数量:原文文件、抄袭文件和答案输出文件。"));
}

错误对应的场景:用户未提供必要的命令行参数时,期望程序给出相关的错误提示。如果程序没有输出正确的错误信息,则表示参数处理存在问题。

这些测试用例和设计目标确保了在多种情况下程序能够正确计算文本相似度,并处理可能出现的各种异常情况。

posted @ 2024-09-13 00:21  詩玖  阅读(70)  评论(0)    收藏  举报