工程概论第二次作业
GitHub链接
作业要求
| 这个作业属于哪个课程 | 工程概论 |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/jmu/ComputerScience21/homework/13034 |
| 这个作业的目标 | 在GitHub中实现论文查重程序,并测试 |
需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位。
开发环境
语言:java17
工具:IDEA
环境:windows10
计算模块接口的设计与实现过程
这段代码实现了一个文本相似度计算模块。具体的设计和实现过程如下:
1.导入所需的依赖库:代码中使用了com.hankcs.hanlp库来进行中文文本的分词。
2.定义了一个calculateCosSimilarity方法,用于计算两个向量的余弦相似度。该方法接受两个参数,分别是表示向量的Map<String, Integer>对象,其中键表示词语,值表示该词语出现的次数。余弦相似度的计算公式为:dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)),其中,dotProduct表示两个向量的点积,norm1和norm2分别表示两个向量的归一化因子。
3.main方法是程序的入口方法。它首先读取两个文本文件(原版文件和抄袭版文件)的内容,并保存在StringBuilder对象content1和content2中。
4.将原版文件和抄袭版文件的内容转换为字符串,并使用HanLP分词器对文本进行分词,得到分词结果的列表segList1和segList2。
5.创建ArrayList对象list1和list2,用于保存分词结果。同时创建HashMap对象wordCountMap1和wordCountMap2,用于统计词频。
6.遍历分词结果列表segList1和segList2,将分词结果保存到list1和list2中。
7.遍历list1和list2,将词语及其对应的词频保存到wordCountMap1和wordCountMap2中。
8.创建Set对象wordSet1和wordSet2,用于保存去重后的词语。
9.调用calculateCosSimilarity方法,传入wordCountMap1和wordCountMap2,计算两个向量的余弦相似度,赋值给cosineSimilarity变量。
10.打印输出两篇文章的重复率。
11.将结果写入输出文件。
这样,代码实现了一个基于余弦相似度的文本相似度计算模块,能够对两篇文本之间的相似度进行评估。
计算模块接口部分的性能改进过程
一开始的代码存在以下问题,需要进行性能改进:
1.每次计算相似度时都重新进行分词和统计词频,导致了重复操作。
2.相似度计算未使用并行化,导致计算速度较慢。
为了解决以上问题,作出了如下改进:
1.增量更新词频统计。将分词和词频统计的逻辑提取出来,作为一个单独的方法,并在每次计算之前调用该方法进行增量更新词频统计。这样可以避免每次都重新对整个文本进行分词和统计。
改进代码如下:
点击查看代码
// 将分词结果保存到列表中,并统计词频
for (Term term : segList1) {
list1.add(term.word);
}
for (String word : list1) {
wordCountMap1.put(word, wordCountMap1.getOrDefault(word, 0) + 1);
}
ArrayList<String> list2 = new ArrayList<>();
Map<String, Integer> wordCountMap2 = new HashMap<>();
// 将分词结果保存到列表中,并统计词频
for (Term term : segList2) {
list2.add(term.word);
}
for (String word : list2) {
wordCountMap2.put(word, wordCountMap2.getOrDefault(word, 0) + 1);
}
Set<String> wordSet1 = new HashSet<>(list1);
Set<String> wordSet2 = new HashSet<>(list2);
计算模块部分异常处理说明
在计算模块部分主要涉及到两个函数:calculateCosSimilarity和main函数。下面给出这两个函数的异常处理说明:
calculateCosSimilarity函数异常处理说明:
1.可能引发NullPointerException,例如当vec1或vec2为空引用时。可以在函数开始处添加代码检查向量是否为空,并根据实际情况抛出自定义的异常或返回特定的结果。
2.可能引发ArithmeticException,例如当归一化因子中存在除以零的情况。可以在计算之前检查除数是否为零,并进行相应的异常处理或返回一个合适的值。
main函数异常处理说明:
可能引发IOException,例如当读取文件或写入文件时遇到问题,如文件不存在、文件无法读取、文件无法写入等。可以使用try-catch块来捕获IOException,并输出相应的错误信息,以便及时发现和处理错误。
PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 15 |
| Estimate | 估计这个任务需要多少时间 | 120 | 180 |
| Development | 开发 | 90 | 120 |
| Analysis | 需求分析 (包括学习新技术) | 60 | 60 |
| Design Spec | 生成设计文档 | 60 | 60 |
| Design Review | 设计复审 | 30 | 30 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
| Design | 具体设计 | 60 | 70 |
| Coding | 具体编码 | 360 | 400 |
| Code Review | 代码复审 | 40 | 40 |
| Test | 测试(自我测试,修改代码,提交修改) | 30 | 60 |
| Reporting | 报告 | 30 | 60 |
| Test Repor | 测试报告 | 40 | 40 |
| Size Measurement | 计算工作量 | 10 | 10 |
| Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 60 |
| 合计 | 550 | 750 |
测试
原版:

抄袭版:

输出文件:

测试结果:

代码部分
计算余弦相似度:
点击查看代码
public static double calculateCosSimilarity(Map<String, Integer> vec1, Map<String, Integer> vec2) {
// 计算两个向量的余弦相似度
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 计算两个向量的点积和归一化因子
for (Map.Entry<String, Integer> entry : vec1.entrySet()) {
String word = entry.getKey();
int count = entry.getValue();
dotProduct += count * vec2.getOrDefault(word, 0);
norm1 += Math.pow(count, 2);
}
for (int count : vec2.values()) {
norm2 += Math.pow(count, 2);
}
// 返回余弦相似度
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
读取文件路径:
点击查看代码
// 文件路径
String filePath1 = "C:\\Users\\Liam\\Desktop\\原版.txt";
String filePath2 = "C:\\Users\\Liam\\Desktop\\抄袭版.txt";
String filePath3 = "C:\\Users\\Liam\\Desktop\\输出文件.txt";
StringBuilder content1 = new StringBuilder();
StringBuilder content2 = new StringBuilder();
String line;
BufferedReader reader = null;
// 读取原版文件内容
reader = new BufferedReader(new FileReader(filePath1));
while ((line = reader.readLine()) != null) {
content1.append(line);
content1.append(System.lineSeparator());
}
reader.close();
// 读取抄袭版文件内容
reader = new BufferedReader(new FileReader(filePath2));
while ((line = reader.readLine()) != null) {
content2.append(line);
content2.append(System.lineSeparator());
}
reader.close();
String paper1 = content1.toString();
String paper2 = content2.toString();
使用HanLp分词器分词:
点击查看代码
// 使用HanLP分词器对文本进行分词
List<Term> segList1 = HanLP.segment(paper1);
List<Term> segList2 = HanLP.segment(paper2);
ArrayList<String> list1 = new ArrayList<>();
Map<String, Integer> wordCountMap1 = new HashMap<>();
// 将分词结果保存到列表中,并统计词频
for (Term term : segList1) {
list1.add(term.word);
}
for (String word : list1) {
wordCountMap1.put(word, wordCountMap1.getOrDefault(word, 0) + 1);
}
ArrayList<String> list2 = new ArrayList<>();
Map<String, Integer> wordCountMap2 = new HashMap<>();
// 将分词结果保存到列表中,并统计词频
for (Term term : segList2) {
list2.add(term.word);
}
for (String word : list2) {
wordCountMap2.put(word, wordCountMap2.getOrDefault(word, 0) + 1);
}
Set<String> wordSet1 = new HashSet<>(list1);
Set<String> wordSet2 = new HashSet<>(list2);
完整代码:
点击查看代码
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.io.*;
import java.text.DecimalFormat;
import java.util.*;
public class Checker {
public static double calculateCosSimilarity(Map<String, Integer> vec1, Map<String, Integer> vec2) {
// 计算两个向量的余弦相似度
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 计算两个向量的点积和归一化因子
for (Map.Entry<String, Integer> entry : vec1.entrySet()) {
String word = entry.getKey();
int count = entry.getValue();
dotProduct += count * vec2.getOrDefault(word, 0);
norm1 += Math.pow(count, 2);
}
for (int count : vec2.values()) {
norm2 += Math.pow(count, 2);
}
// 返回余弦相似度
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
public static void main(String[] args) {
// 文件路径
String filePath1 = "C:\\Users\\Liam\\Desktop\\原版.txt";
String filePath2 = "C:\\Users\\Liam\\Desktop\\抄袭版.txt";
String filePath3 = "C:\\Users\\Liam\\Desktop\\输出文件.txt";
try {
StringBuilder content1 = new StringBuilder();
StringBuilder content2 = new StringBuilder();
String line;
BufferedReader reader = null;
// 读取原版文件内容
reader = new BufferedReader(new FileReader(filePath1));
while ((line = reader.readLine()) != null) {
content1.append(line);
content1.append(System.lineSeparator());
}
reader.close();
// 读取抄袭版文件内容
reader = new BufferedReader(new FileReader(filePath2));
while ((line = reader.readLine()) != null) {
content2.append(line);
content2.append(System.lineSeparator());
}
reader.close();
String paper1 = content1.toString();
String paper2 = content2.toString();
// 使用HanLP分词器对文本进行分词
List<Term> segList1 = HanLP.segment(paper1);
List<Term> segList2 = HanLP.segment(paper2);
ArrayList<String> list1 = new ArrayList<>();
Map<String, Integer> wordCountMap1 = new HashMap<>();
// 将分词结果保存到列表中,并统计词频
for (Term term : segList1) {
list1.add(term.word);
}
for (String word : list1) {
wordCountMap1.put(word, wordCountMap1.getOrDefault(word, 0) + 1);
}
ArrayList<String> list2 = new ArrayList<>();
Map<String, Integer> wordCountMap2 = new HashMap<>();
// 将分词结果保存到列表中,并统计词频
for (Term term : segList2) {
list2.add(term.word);
}
for (String word : list2) {
wordCountMap2.put(word, wordCountMap2.getOrDefault(word, 0) + 1);
}
Set<String> wordSet1 = new HashSet<>(list1);
Set<String> wordSet2 = new HashSet<>(list2);
// 计算余弦相似度
double cosineSimilarity = calculateCosSimilarity(wordCountMap1, wordCountMap2);
// 打印结果
System.out.println("两篇文章的重复率为: " + new DecimalFormat("0.00").format(cosineSimilarity));
// 将结果写入输出文件
BufferedWriter out = new BufferedWriter(new FileWriter(filePath3));
out.write(new DecimalFormat("0.00").format(cosineSimilarity));
out.newLine();
out.close();
} catch (IOException e) {
System.out.println("发生I/O错误: " + e.getMessage());
} catch (Exception e) {
System.out.println("发生错误: " + e.getMessage());
}
}
}
浙公网安备 33010602011771号