第一次个人编程作业
前言
本文代码已经上传github , 已经注册软工在线网站
这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 熟悉个人软件开发流程 |
编程题目:
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
实现思路
-
读取源文档和待检测文档
-
利用jieba包将文档分词,得到关键词
-
获取两个文档各自的词频向量
-
计算两词频向量的余弦值,得到两个文档的相似度(余弦值越大,相似度越大)
核心算法:余弦相似度
公式:
(公式截图来源: 百度百科 )
PSP表格记录
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 35 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 35 |
Development | 开发 | 245 | 265 |
· Design Spec | · 生成设计文档 | 30 | 20 |
· Design Review | · 设计复审 | 15 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 25 |
· Design | · 具体设计 | 30 | 35 |
· Coding | · 具体编码 | 100 | 120 |
· Code Review | · 代码复审 | 30 | 15 |
· Test | · 测试(自我测试,修改代码,提交修改) | 20 | 40 |
Reporting | 报告 | 70 | 50 |
· Test Repor | · 测试报告 | 30 | 20 |
· Size Measurement | · 计算工作量 | 20 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 15 |
· 合计 | 345 | 350 |
计算模块接口的设计与实现过程
图解:
项目主要包括main.py、util.py、FileException.py。
- util.py: 实现工具函数 ,包括获取关键词、计算文本向量、计算余弦相似度。
- main.py: 读取命令行参数获得文件路径、检测路径是否存在等一系列逻辑操作
- FileException.py: 自定义异常
性能分析
程序消耗最大的函数: def get_keywords(context1,context2)
代码如下:
def get_keywords(context1,context2):
# 读入停用词
stopwords = [line.strip() for line in open('StopWords.txt',encoding='UTF-8').readlines()]
stopwords.append("\n")
# 对两文档分别分词
keywords1 = [i for i in jieba.cut(context1, cut_all=True) if i != '' and i != ' ']
keywords2 = [i for i in jieba.cut(context2, cut_all=True) if i != '' and i != ' ']
# 对两个关键词列表进行合并去重
word_set = set(keywords1).union(set(keywords2)) - set(stopwords)
return keywords1,keywords2,word_set
由于get_keywords一时没有好的替代方法,故对get_freq(keywords1,keywords2,word_set)和main.py进行改进,考虑到程序有冗余功能,所以主要是进行删减提升速度。
改进前:
def get_freq(keywords1,keywords2,word_set):
# 用字典保存两篇文章中出现的所有词并编上号
word_dict = dict()
i = 0
for word in word_set:
word_dict[word] = i
i += 1
# 根据词袋模型统计词在每篇文档中出现的次数,形成向量
k1_cut_freq = [0] * len(word_dict)
k2_cut_freq = [0] * len(word_dict)
for word in keywords1:
if word in word_dict:
k1_cut_freq[word_dict[word]] += 1
for word in keywords2:
if word in word_dict:
k2_cut_freq[word_dict[word]] += 1
print("freq1: length")
print("{}".format(len(k1_cut_freq)))
print("freq2:length")
print("{}".format(len(k2_cut_freq)))
return k1_cut_freq,k2_cut_freq
改进后:
def get_freq(keywords1,keywords2,word_set):
# 用字典保存两篇文章中出现的所有词并编上号
word_dict = dict()
i = 0
for word in word_set:
word_dict[word] = i
i += 1
length = len(word_dict)
# 根据词袋模型统计词在每篇文档中出现的次数,形成向量
k1_cut_freq = [0] * length
k2_cut_freq = [0] * length
for word in keywords1:
if word in word_dict:
k1_cut_freq[word_dict[word]] += 1
for word in keywords2:
if word in word_dict:
k2_cut_freq[word_dict[word]] += 1
return k1_cut_freq,k2_cut_freq
此外,删除main.py中的获取文件名操作
再次进行性能测试,运行时间明显缩小,在两秒多即能给出答案
单元测试展示
先将main函数的文件路径读取方式改为手动输入形式,以便进行测试
from util import *
def main_test():
path1 = input("输入论文原文的文件的绝对路径:")
print("path1:")
print(path1)
path2 = input("输入抄袭版论文的文件的绝对路径:")
print("path2:")
print(path2)
path3 = input("输入答案文件的绝对路径")
print("path3:")
print(path3)
str1 = path1.strip()
str2 = path2.strip()
str3 = path3.strip()
print("str1:")
print(str1)
text1 = open(str1, 'rb')
text2 = open(str2, 'rb')
orig = text1.read() # 读取文件
orig_check = text2.read()
ans = open(str3, 'w+') # 如果输出文件不存在则创建,存在则覆盖
# 获取关键词
keywords1, keywords2, word_set = get_keywords(orig, orig_check)
# 获取词频向量
freq1, freq2 = get_freq(keywords1, keywords2, word_set)
# 计算余弦值
similarity = CosineSimilarity(freq1, freq2)
# 获取文件名
#orig_filename = text1[len(os.path.dirname(str1)) + 1:]
#orig_check_filename = text2[len(os.path.dirname(str2)) + 1:]
sim = "重复率为:{:.2f}".format(similarity)
answer = round(similarity,2)
print(sim)
ans.write(sim)
text1.close()
text2.close()
ans.close()
return answer
if __name__ == '__main__':
main_test()
建立测试文件 :
import unittest
from test import main_test
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertEqual(main_test(), 0.99)
if __name__ == '__main__':
unittest.main()
测试成功!
代码覆盖率如下:
异常处理
处理两个异常:
- 文件路径不存在
if not os.path.exists(arg1):
print("标准文件不存在!")
exit()
if not os.path.exists(arg2):
print("要查重文件不存在!")
exit()
- 文件为空
FileException.py:
#读取的文件为空
class empty_error(Exception):
def __init__(self):
print("您输入的文本为空!!")
main.py的调用片段:
orig = text1.read() # 读取文件
orig_check = text2.read()
# 空文本异常
if(len(orig)==0 or len(orig_check)==0):
text1.close()
text2.close()
raise empty_error
本文原创,转载请注明作者和链接
-------------------------------------------
个性签名:在0和1的世界里,实现从0到1的蜕变~