第一次个人编程作业
一、PSP表格
- (2.1)在开始实现程序之前,在附录提供PSP表格记录下你估计将在程序的各个模块的开发上耗费的时间。
- (2.2)在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块的开发上实际花费的时间。
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 40 |
Development | 开发 | 1750 | 3275 |
· Analysis | · 需求分析 (包括学习新技术) | 240 | 360 |
· Design Spec | · 生成设计文档 | 10 | 30 |
· Design Review | · 设计复审 | 5 | 25 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 45 |
· Design | · 具体设计 | 120 | 255 |
· Coding | · 具体编码 | 600 | 1500 |
· Code Review | · 代码复审 | 660 | 1000 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 100 | 120 |
· Test Repor | · 测试报告 | 60 | 80 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
· 合计 | 1870 | 3435 |
二、计算模块接口
-
(3.1)计算模块接口的设计与实现过程。
-
使用的算法是DFA算法,算法的主要思想是构造一个Trid树,形成如下图的结构:
-
要构成上图这样的结构,选择使用dict()数据结构,如图所示
-
算法优势:DFA 算法在关键词匹配方面要快于关键词迭代方法(for 循环),因为for 循环需要遍历一遍关键词表,随着关键词表的扩充,所需的时间也会越来越长。而DFA 算法找到“匹”时,只会按照事件走向特定的序列,例如“匹配关键词”,而不会走向“匹配算法”,因此遍历的次数要小于 for 循环。
-
算法劣势:DFA算法在寻找待检测文本中的敏感词时,如果没找到的话,会回到根节点,从根节点再次开始遍历。这样一来会浪费大量的时间。如果加上失配指针,构成AC自动机的话,将会大大提高速度,但因为本人实力不足,使用AC自动机时一直报错,失配指针的转移一直出现问题。所以最后只能退而求其次选择了我认为较好理解的DFA算法。
-
代码共包含1个类和5个函数,其中
-
一个类——DFAUtils类。类中包含add_word、search、findkey三个函数。
-
add_word函数。用于构建Trid树。
-
search函数。对文本内容进行读取,并调用findkey函数进行敏感词检测。
-
findkey函数。寻找文本中的敏感词,并将找到的敏感词以以(敏感词,文本中敏感词初始位置,文本中敏感词末位置加1)的三元组形式存入result中。
-
五个函数——main、take_second、is_chinese、getword、output
-
main函数。这个函数不必多说,起总领作用。
-
take_second函数。用于辅助sort函数排序时指定按哪个元素排序。因为检测结果以三元组保存时是无序的,所以要将三元组按照第二个元素升序排序。
-
is_chinese函数。用于判断一个字符是否是汉字。
-
getword函数。提取敏感词,并将敏感词的拼音替换、拼音首字母替换、繁体形式、敏感词拆分偏旁部首等情况进行重组最后存入wordcreat列表中。
-
output函数。输出函数,将结果保存在指定的文件中。
-
代码执行的流程如下图:
-
getword函数的执行流程如下图:
-
findkey函数的执行流程如下图:
-
-
解题思路:我觉得这次作业有两个难点。第一个难点就在于如何把敏感词的敏感词的拼音,拼音首字母,敏感词的拆分结果,敏感词的繁体等情况全部找出来重组,并添加到Trid树中。第二个难点在于如何处理待检测文本中的谐音情况。接下来我会对这两个难点讲诉我的处理方法,也算是我算法中的独到之处
(也许算是吧)。- 敏感词拼音和首字母、繁体:不同于其他同学使用pypinyin库,我在github上找到了开源的获取汉字拼音和简体转繁体的库Pin_yin.py、Tran_slate.py
(真就是面向Github编程呗) - 敏感词拆分:在网上找了一个汉字拆分字典,然后编成了一个npy文件,以便于调用
- 待检测文本中的谐音情况:我的方法是将待检测文本所有与敏感词同音的字全部更换成对应的敏感词。但要注意一个问题,比如“轮”可能拆分为“车仑”,但此时的“仑”却不能转为谐音字“轮”,否则在检测时会出现问题。对于这个问题,我对解决方法是将所有敏感词的拆分情况记录下来存在列表中,但凡出现在这个列表中的谐音字就不进行处理。
- 敏感词拼音和首字母、繁体:不同于其他同学使用pypinyin库,我在github上找到了开源的获取汉字拼音和简体转繁体的库Pin_yin.py、Tran_slate.py
-
最后附上样例的运行结果。根据其他大佬的运行结果,我应该是全部找出来了。
-
-
(3.2)计算模块接口部分的性能改进
- 初始性能。最初我在重组敏感词的各种形式时使用的是递归。使用递归很是麻烦,因为不同敏感词的长度不一致,在递归循环之前还得先判断敏感词的长度。而且使用递归所需的时间会很长。下面给出初始性能分析图和函数调用图:
- 我们可以看到耗时最长的是getword函数,其次是GetPinyin函数(这是Pin_yin.py中的函数)
- 性能优化:之后在网上找到了itertools库,可以快速得将敏感词的拼音,拼音首字母,敏感词的拆分结果,敏感词的繁体等情况进行快速重组。可以看到这样比使用递归进行重组速度要快一些,虽然总耗时依旧要2353+ms,但我实力有限,暂时找不到可以优化的地方了。至于GetPinyin函数,由于是调用外部的函数,暂时也没有什么更好的替换方法。接下来附上优化后的性能分析图和函数调用图:
- 最后附上耗时最长的函数:
def getword(path1):
"""
提取敏感词,并将敏感词的拼音替换、拼音首字母替换、繁体形式、敏感词拆分偏旁部首等情况进行重组
最后存入wordcreat列表中
"""
wordcreat = []
with open(path1, "r", encoding="utf-8") as file:
for line in file:
a = line.strip()
b = a
if is_chinese(a[0]):
ALL = []
for i in a:
one_word = []
one_word.append(pinyin.GetPinyin(i))
"""
获取敏感词的拼音
"""
xieyin[pinyin.GetPinyin(i)] = i
one_word.append(i)
one_word.append(pinyin.GetPinyin(i)[0])
"""
获取敏感词的拼音首字母
"""
one_word.append(translate.ToTraditionalChinese(i))
"""
获取敏感词的繁体形式
"""
if i in read_dictionary:
one_word.append(read_dictionary[i])
"""
获取敏感词的拆分偏旁部首形式
"""
cai.append(read_dictionary[i])
ALL.append(one_word)
for i in range(0, len(ALL) - 1):
"""
利用itertools库将敏感词的各种形式进行快速重组
"""
if i == 0:
g = [k for k in itertools.product(ALL[i], ALL[i + 1])]
else:
g = [k for k in itertools.product(g, ALL[i + 1])]
ALL.clear()
for i in g:
ALL.append(''.join(e for e in str(i) if e.isalnum()))
if is_chinese(b[0]):
"""
如果敏感词是中文,则将敏感词的各种形式都加入到wordcreat列表中
如果敏感词是英文,则直接把敏感词加入wordcreat列表中
"""
j = 0
while j < len(ALL):
ALL[j] = ALL[j].lower()
duiying[ALL[j]] = b
wordcreat.append(ALL[j])
j += 1
else:
wordcreat.append(b)
duiying[b] = b
return wordcreat
-
(3.3)计算模块部分单元测试展示
-
本次作业的单元测试我采用的是测试组给出的样例,word.txt,org.txt,ans.txt。样例中包含繁简体、中英文、拼音混合、谐音、敏感词拆分等诸多情况,每种情况都不止10个例子了。因为我采用的是传递文件地址,与一些大佬采用传递字符串不同,所以我认为使用样例做单元测试效果比较好,测试的数据也比较多。(绝对不是为了偷懒,希望测试组的哥哥姐姐们手下留情)接下来附上我的单元测试代码、单元测试覆盖率和样例运行结果。
-
单元测试代码:
-
单元测试覆盖率:
-
样例运行结果:应该是都找到了
-
-
(3.4)计算模块部分异常处理说明
-
这次项目中只有两种异常
-
一:读取敏感词文件、待测文本文件、输出文本文件时发生错误。
-
二:使用命令行参数输入文件地址错误。
-
三、心得
- 对于这次作业我只能说:太难搞了。一开始的构思还好,每种问题都能找到一些好方法,但写完运行时,编译器返回的一堆的报错每次都让我心中凉凉,然后一点点地去debug,往往刚修完这个bug,有多出了好几个新的bug,只能说很绝望。
- 这次的作业一开始没怎么思路
(其实是拖延症),到了最后几天才开始写,熬夜爆肝写代码的痛苦我再也不想经历了呜呜呜。最后肝到我看见电脑屏幕就头脑发昏,看到代码就想吐,太难受了。以后一定要好好准备,不能拖到最后几天再写了。 - 虽然过程很痛苦,但是结果很喜人。我学会了很多东西,比如我用了这么久的pycharm,居然不知道pycharm还有性能分析和单元测试功能。虽然改bug改到流泪,但最后看到检测结果我把全部都找出来时,那种满足感无法用语言形容。
- 经过这次作业,我发现自己还有很多不会,还得继续努力。