第一次個人程式設計作業
這個作業屬於哪個課程 | <軟體工程2024-雙學位> |
---|---|
這個作業要求在哪裡 | <軟體工程第一次個人程式設計作業> |
這個作業的目標 | 完成編碼任務 |
PSP表格
PSP2.1 | Persenonal Software Process Stages | 預計耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | 30 |
Estimate | 估計這個任務需要多少實踐 | 10 | 10 |
Development | 開發 | 120 | 70 |
- Analysis | 需求分析(包括學習新技術) | 30 | 20 |
- Design Spec | 生成設計文件 | 20 | 10 |
- Design Review | 設計複審 | 10 | 5 |
- Coding standard | 程式碼規範(為目前的開發指定合適的規範) | 15 | 10 |
- Design | 具體設計 | 20 | 15 |
- Coding | 具體編碼 | 30 | 20 |
- Test | 程式碼複審 | 15 | 10 |
- Reporting | 測試(自我測試,修改程式碼,提交修改) | 10 | 5 |
- Test Report | 報告 | 10 | 5 |
- Size Measurement | 計算工作量 | 10 | 5 |
- Postmortem & Process Improvement Plan | 事後總結,並提出過程改進計劃 | 20 | 15 |
合計 | 300 | 195 |
2. 計算模組介面設計與實現
主要類設計
-
Document
- 表示一篇文件,包含文字內容
- 提供文字預處理功能,如移除標點、轉小寫等
- 實現將文件切分為詞元序列的方法
-
SimilarityDetector
- 計算兩篇文件相似度的核心類
- 使用最長公共子序列(LCS)演算法計算相似度
- 包含基於動態規劃和記憶化搜尋的LCS計算方法
-
PlagiarismChecker
- 主程式入口
- 處理命令列引數
- 讀取檔案內容
- 使用SimilarityDetector計算相似度
- 輸出結果到指定檔案
演算法關鍵點
- 文件預處理: 過濾掉標點符號、轉為小寫等,使文件標準化
- 詞元序列化: 將文件切分為詞元(token)序列,作為相似度計算的基礎
- LCS演算法: 計算兩文件詞元序列的最長公共子序列長度
- 相似度計算:
相似度 = LCS長度 / max(原文詞元數, 抄襲版詞元數)
該演算法的獨到之處是使用LCS作為相似度計算的核心,能夠規避"交換詞序"這類改動的影響。
3. 效能改進
最初的LCS動態規劃演算法時間複雜度為O(mn),m和n分別為兩文件詞元序列長度,當文件較長時會導致效能低下。
為加速計算,我引入了以下最佳化策略:
- 雜湊表儲存詞元
- 將較短文件的詞元使用雜湊表儲存
- 查詢是否屬於LCS只需O(1)時間
- 記憶化搜尋
- 避免重複計算相同的LCS子問題
- 使用記憶化陣列儲存子問題解,降低時間複雜度至O(mn)
經過上述最佳化,演算法在長文件上的效能有了極大提升:
![演算法效能分析][]
如圖所示,消耗時間最長的函式已由LCS計算變為底層的文件預處理函式。
4. 單元測試
編寫了以下單元測試用例:
import unittest
from plagiarism_checker import *
class TestSimilarityDetector(unittest.TestCase):
def test_identical(self):
doc1 = Document("The quick brown fox jumps over the lazy dog.")
doc2 = Document("The quick brown fox jumps over the lazy dog.")
result = SimilarityDetector.compute_similarity(doc1, doc2)
self.assertAlmostEqual(result, 1.0)
def test_different(self):
doc1 = Document("I have a dream that one day...")
doc2 = Document("In the beginning, God created the heavens and the earth...")
result = SimilarityDetector.compute_similarity(doc1, doc2)
self.assertAlmostEqual(result, 0.0)
def test_reordered(self):
doc1 = Document("The brown quick fox jumps lazy over the dog.")
doc2 = Document("The quick brown fox jumps over the lazy dog.")
result = SimilarityDetector.compute_similarity(doc1, doc2)
self.assertAlmostEqual(result, 1.0)
主要測試點包括:
- 完全相同的文件
- 完全不同的文件
- 存在詞序改動的情況
- 刪除/增加少量詞語的情況
透過IDE的測試覆蓋率分析工具,測試覆蓋率達到了85%:
5. 異常處理
針對以下幾種可能的異常情況,做了處理:
1.檔案不存在異常
-
目標: 提醒使用者輸入了無效的檔案路徑
-
測試用例:
def test_file_not_found(self): with self.assertRaises(FileNotFoundError): PlagiarismChecker.run("fakefile.txt", "orig.txt", "output.txt")
2.檔案讀取異常
-
目標: 捕獲檔案讀取過程中的異常,如許可權、磁碟空間等問題
-
測試用例:
def test_permission_denied(self): # 建立一個臨時只讀檔案 file = open("temp.txt", "w") file.close() os.chmod("temp.txt", 0o400) # 設定為只讀 with self.assertRaises(PermissionError): doc = Document("temp.txt") os.remove("temp.txt")
3.輸入檔案為空
-
目標: 確保輸入檔案有內容,防止除0錯誤
-
測試用例:
def test_empty_file(self): doc1 = Document("") doc2 = Document("This is not empty.") with self.assertRaises(ValueError): SimilarityDetector.compute_similarity(doc1, doc2)