作業要求
作業所屬班級 | 軟體工程2024連結 |
---|---|
作業要求 | 作業要求連結 |
作業目標 | 設計一個論文查重演算法,給出一個原文檔案和一個在這份原文上經過了增刪改的抄襲版論文的檔案,在答案檔案中輸出其重複率。 |
PSP
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 60 | 60 |
· Estimate | · 估計這個任務需要多少時間 | 60 | 60 |
Development | 開發 | 1120 | 1280 |
· Analysis | · 需求分析 (包括學習新技術) | 300 | 370 |
· Design Spec | · 生成設計文件 | 70 | 60 |
· Design Review | · 設計複審 | 30 | 30 |
· Coding Standard | · 程式碼規範 (為目前的開發制定合適的規範) | 30 | 30 |
· Design | · 具體設計 | 90 | 90 |
· Coding | · 具體編碼 | 300 | 360 |
· Code Review | · 程式碼複審 | 60 | 60 |
· Test | · 測試(自我測試,修改程式碼,提交修改) | 240 | 300 |
Reporting | 報告 | 240 | 240 |
· Test Repor | · 測試報告 | 60 | 70 |
· Size Measurement | · 計算工作量 | 60 | 60 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 120 | 120 |
· 合計 | 1420 | 1590 |
介面設計
從命令列輸入的路徑名讀取對應的檔案,將檔案的內容轉化為對應的字串,由字串得出對應的 simHash值,由 simHash值求出相似度,把相似度寫入最後的結果檔案中。
點選檢視程式碼
package Utils;
public class HammingUtils {
// 輸入兩個simHash值,計算它們的海明距離
public static int getHammingDistance(String simHash1, String simHash2) {
int distance = 0;
if (simHash1.length() != simHash2.length()) {
// 出錯,返回-1
distance = -1;
} else {
for (int i = 0; i < simHash1.length(); i++) {
// 每一位進行比較
if (simHash1.charAt(i) != simHash2.charAt(i)) {
distance++;
}
}
}
return distance;
}
// 輸入兩個simHash值,輸出相似度
public static double getSimilarity(String simHash1, String simHash2) {
// 透過 simHash1 和 simHash2 獲得它們的海明距離
int distance = getHammingDistance(simHash1, simHash2);
// 透過海明距離計算出相似度,並返回
return 0.01 * (100 - distance * 100 / 128);
}
}
點選檢視程式碼
package Utils;
public class ShortStringException extends Exception {
public ShortStringException() {
super();
}
public ShortStringException(String message) {
super(message);
}
public ShortStringException(String message, Throwable cause) {
super(message, cause);
}
public ShortStringException(Throwable cause) {
super(cause);
}
}
點選檢視程式碼
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.List;
public class SimHashUtils {
/**
* 傳入String,計算出它的hash值,並以字串形式輸出
*
* @param str 傳入的Srting型別字串
* @return 返回str的hash值
*/
public static String getHash(String str) {
try {
// 這裡使用了MD5獲得hash值
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
return new BigInteger(1, messageDigest.digest(str.getBytes("UTF-8"))).toString(2);
} catch (Exception e) {
e.printStackTrace();
return str;
}
}
/**
* 傳入String,計算出它的simHash值,並以字串形式輸出
*
* @param str 傳入的Srting型別字串
* @return 返回str的simHash值
*/
public static String getSimHash(String str) {
// 文字長度太短時HanLp無法取得關鍵字
try {
if (str.length() < 200) throw new ShortStringException("文字過短,難以判斷!");
} catch (ShortStringException e) {
e.printStackTrace();
return null;
}
// 用陣列表示特徵向量,取128位,從 0 1 2 位開始表示從高位到低位
int[] v = new int[128];
// 1、分詞(使用了外部依賴hankcs包提供的介面)
List<String> keywordList = HanLP.extractKeyword(str, str.length());//取出所有關鍵詞
// hash
int size = keywordList.size();
int i = 0;//以i做外層迴圈
for (String keyword : keywordList) {
// 2、獲取hash值
String keywordHash = getHash(keyword);
if (keywordHash.length() < 128) {
// hash值可能少於128位,在低位以0補齊
int dif = 128 - keywordHash.length();
for (int j = 0; j < dif; j++) {
keywordHash += "0";
}
}
// 3、加權、合併
for (int j = 0; j < v.length; j++) {
// 對keywordHash的每一位與'1'進行比較
if (keywordHash.charAt(j) == '1') {
//權重分10級,由詞頻從高到低,取權重10~0
v[j] += (10 - (i / (size / 10)));
} else {
v[j] -= (10 - (i / (size / 10)));
}
}
i++;
}
// 4、降維
String simHash = "";// 儲存返回的simHash值
for (int j = 0; j < v.length; j++) {
// 從高位遍歷到低位
if (v[j] <= 0) {
simHash += "0";
} else {
simHash += "1";
}
}
return simHash;
}
}
點選檢視程式碼
package Utils;
import java.io.*;
import java.util.ArrayList;
public class TxtIOUtil {
public static String readTxt(String txtPath) {
String str = "";
String strLine;
// 將 txt檔案按行讀入 str中
File file = new File(txtPath);
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
// 字串拼接
while ((strLine = bufferedReader.readLine()) != null) {
str += strLine;
}
// 關閉資源
inputStreamReader.close();
bufferedReader.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
public static void writeTxt(double txtElem,String txtPath){
String str = Double.toString(txtElem);
File file = new File(txtPath);
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(file, true);
fileWriter.write("相似度比例為: ");
fileWriter.write(str, 0, (str.length() > 3 ? 4 : str.length()));
fileWriter.write("\r\n");
// 關閉資源
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
效能改進
單元測試
點選檢視程式碼
import Util.HammingUtils;
import Util.SimHashUtils;
import Util.TxtIOUtil;
import org.junit.Test;
public class MainTest {
@Test
public void origAndAllTest(){
String[] str = new String[6];
str[0] = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
str[1] = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_add.txt");
str[2] = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_del.txt");
str[3] = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_dis_1.txt");
str[4] = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_dis_10.txt");
str[5] = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_dis_15.txt");
String ansFileName = "D:/checkDuplicate/ansAll.txt";
for(int i = 0; i <= 5; i++){
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str[0]), SimHashUtils.getSimHash(str[i]));
TxtIOUtil.writeTxt(ans, ansFileName);
}
}
@Test
public void origAndOrigTest(){
String str0 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String str1 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String ansFileName = "D:/checkDuplicate/ansOrigAndOrigTest.txt";
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
TxtIOUtil.writeTxt(ans, ansFileName);
}
@Test
public void origAndAddTest(){
String str0 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String str1 = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_add.txt");
String ansFileName = "D:/checkDuplicate/ansOrigAndAddTest.txt";
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
TxtIOUtil.writeTxt(ans, ansFileName);
}
@Test
public void origAndDelTest(){
String str0 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String str1 = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_del.txt");
String ansFileName = "D:/checkDuplicate/ansOrigAndDelTest.txt";
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
TxtIOUtil.writeTxt(ans, ansFileName);
}
@Test
public void origAndDis1Test(){
String str0 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String str1 = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_dis_1.txt");
String ansFileName = "D:/checkDuplicate/ansOrigAndDis1Test.txt";
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
TxtIOUtil.writeTxt(ans, ansFileName);
}
@Test
public void origAndDis10Test(){
String str0 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String str1 = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_dis_10.txt");
String ansFileName = "D:/checkDuplicate/ansOrigAndDis10Test.txt";
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
TxtIOUtil.writeTxt(ans, ansFileName);
}
@Test
public void origAndDis15Test(){
String str0 = TxtIOUtil.readTxt("D:/checkDuplicate/orig.txt");
String str1 = TxtIOUtil.readTxt("D:/checkDuplicate/orig_0.8_dis_15.txt");
String ansFileName = "D:/checkDuplicate/ansOrigAndDis15Test.txt";
double ans = HammingUtils.getSimilarity(SimHashUtils.getSimHash(str0), SimHashUtils.getSimHash(str1));
TxtIOUtil.writeTxt(ans,ansFileName);
}
}
測試覆蓋率
異常處理
讀取文字出現異常,無法取得關鍵字,需要throw異常。
使用ShortStringException這個類處理此異常。
github地址
github地址