天天看点

第一次个人编程作业

https://github.com/zlplease/sensitiveWord

一、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 10 20
Development 开发
· Analysis · 需求分析 (包括学习新技术) 360 480
· Design Spec · 生成设计文档 60
· Design Review · 设计复审 30
· Coding Standard · 代码规范 (为目前的开发制定合适的规范)
· Design · 具体设计 90
· Coding · 具体编码 600 720
· Code Review · 代码复审
· Test · 测试(自我测试,修改代码,提交修改) 120
Reporting 报告
· Test Repor · 测试报告
· Size Measurement · 计算工作量 8
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划
· 合计 1290 1748

二、计算模块接口

代码第一步,首先上百度。在比较DFA与AC自动机的难易程度后,果断选择了DFA。

2.1 计算模块接口的设计与实现过程

2.1.1算法概述

DFA通过event和当前的state得到下一个state,event+state=nextstate。即建立嵌套字典,并在字典的末尾设立限制符。由于本题存在偏旁部首拆分,繁体以及同音字的干扰项,在建立树的时候选择用拼音建树。建树之前对敏感词初始化,并通过排列组合得到改敏感词所有出现的可能性。例如

你好 >>> ['你好', '你hao', '你h', '你女子', 'ni好', 'nihao', 'nih', 'ni女子', 'n好', 'nhao', 'nh', 'n女子', '亻尔好', '亻尔hao', '亻尔h', ' 亻尔女子']

同时还建立了将各项单独拆分的列表。便于对拼音整体的处理

你好 >>> [('你', '好'), ('你', 'hao'), ('你', 'h'), ('你', '女子'), ('ni', '好'),

​ ('ni', 'hao'), ('ni', 'h'), ('ni', '女子'), ('n', '好'), ('n', 'hao'), ('n', 'h'), ('n', '女子'),

​ ('亻尔', '好'), ('亻尔', 'hao'), ('亻尔', 'h'), ('亻尔', '女子')]

通过对字典元素的查询判断不断增加新的分支结点,并在末尾设置限制符。考虑到题目输出要求,限制符作为敏感词字典的结束标志,可以为其设置不同的值,从而还起到标志为哪一个敏感词的作用。按照以上步骤得到的部分树如下

{
	'ni': {
		'h': {
			'a': {
				'o': {
					'is_end': 0
				},
			},
		},
		'hao': {
			'is_end': 0
		},
	},
	'n': {
		'i': {
			'h': {
				'a': {
					'o': {
						'is_end': 0
					},
				},
			},
			'hao': {
				'is_end': 0
			},
		}
	}
}
           

将检测文本逐字转换为拼音进行判断,并更新状态。读到限制符就记录结果,利用切片截取所需字符串。

2.1.2 函数设计

所有通用函数都封装在solve.py文件中,便于处理和复用

  • isChinese函数:根据unicode划分进行汉字判断
    def isChinese(word):
        return '\u4e00' <= word <= '\u9fff‘
               
  • isMatch函数:检测匹配项,剔除符号数字等干扰
    def isMatch(word):
        if isChinese(word):
            return True
        if word.isalpha():
            return True
        if word == '/n':
            return True
               
  • transform函数:将敏感词的每个字的不同情况进行一键对多值的处理,统一英文为小写
    def transform(words):
               
  • forest函数:生成多种敏感词情况,并返回整体与分割两种情况
    def forest(sameDic):
        forest = []
        loop_val = []
        finalDic = []
        for key in sameDic:
            loop_val.append(sameDic[key])
        for i in product(*loop_val):
            forest.append(i)
        for i in range(len(forest)):
            newWord = ''
            for item in forest[i]:
                newWord += item
            finalDic.append(newWord)
        # return forest
        return finalDic, forest
               

2.1.3 类设计

本次作业仅设计了一个DFA类,初始化该类成员如下

def __init__(self):
    	# 生成的敏感词字典
        self.keyword_chains = {}
        # 敏感词字典限制符
        self.delimit = 'is_end'
        # 敏感词检测的结果
        self.answer = []
        # 敏感词
        self.sensitiveWords = []
        # 敏感词对应数量
        self.counts = []
        # 检测出的总数
        self.total = 0
           

并对该类设计了一下函数:

  1. 建树函数
    def generate_dict(self, word, count):
               
  2. 匹配文本的过滤器函数
    def filter(self, texts, line):
               
  3. 首行插入检测总数,然后返回检测结果
    def getAnswer(self):
            self.answer.insert(0, 'Total: ' + str(self.total))
            return self.answer
               

2.1.4 词云图

词云图采用pyecharts包的WordCloud,将收集的敏感极其数量进行处理以符合WordCloud数据格式,设置相关属性进行渲染,生成sensitiveWordCloud.html文件,通过浏览器查看

def wordCloud(words, counts) -> WordCloud:
    initData(words, counts)
    final = (
        WordCloud()
            .add(series_name="敏感词统计", data_pair=data, shape="circle", word_size_range=[6, 66])
            .set_global_opts(
            title_opts=opts.TitleOpts(
                title="敏感词统计", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
            ),
            tooltip_opts=opts.TooltipOpts(is_show=True),
        )
            .render("sensitiveWordCloud.html")
    )
    return final
           
第一次个人编程作业

(数据量太小了,效果不是很好,第一次用pyecharts,不知道为啥有些属性没有生效

2.2 计算模块接口部分的性能改进

函数总时间降序图

第一次个人编程作业

函数自用时间降序图

第一次个人编程作业

不难发现,由于引用pypinyin包,代码中存在着多次拼音转换,存在多次调用lazy_pinyin,占用时间较大。加之filter函数前期没想清楚,造成很多代码冗余未优化。起初想的是将所有情况按字符建树,这样会造成过滤繁体或者同音字时,需要一个字母一个字母匹配,匹配完成后状态改变,这样会造成循环嵌套过多;后重新将分割的敏感词组进行建树,这样就能快速处理拼音的过滤,但这会导致像“功”这种偏旁部首读音跟原字一样的不会完全匹配,所有最后干脆直接建一个全是英文的树,过滤文本时通过转换拼音再进行处理。

2.3 计算模块部分单元测试展示

  • 验证是否为汉字:test_isChinese函数
  • 验证敏感词的不同情况:test_transform函数
    def test_transform(self):
            global_ans = ['你好', '你hao', '你h', '你女子', 'ni好', 'nihao', 'nih', 'ni女子',
                          'n好', 'nhao', 'nh', 'n女子', '亻尔好', '亻尔hao', '亻尔h', ' 亻尔女子']
            separate_ans = [('你', '好'), ('你', 'hao'), ('你', 'h'), ('你', '女子'), ('ni', '好'),
                            ('ni', 'hao'), ('ni', 'h'), ('ni', '女子'), ('n', '好'), ('n', 'hao'), ('n', 'h'), ('n', '女子'),
                            ('亻尔', '好'), ('亻尔', 'hao'), ('亻尔', 'h'), ('亻尔', '女子')]
            global_test, separate_test = solve.transform('你好')
            self.assertEqual(global_test.sort(), global_ans.sort())
            self.assertEqual(separate_test.sort(), separate_ans.sort())
               
  • 验证DFA过滤器是否正常运行:test_DFAfilter函数
    def test_DFAfilter(self):
            sensitiveWord = ['哈利波特', '作家']
            texts = ['《哈利·波$!*&特是英国zjJ.K.罗琳(J. K. Rowling)于1997~2007年所著的魔幻文学系列小说,共7部。',
                     '其中前六部以霍哥沃z魔法学校(Hogwarts School of Witchcraft and Wizardry)为主要舞台,描写的是主人公——年轻的巫师学生h利·波特在霍格沃茨前后六年的学习生活和冒险故事',
                     '第七本描写的是hl&*%^·b特在第二次魔法界大战中在外寻找魂器并消灭fd的故事。']
            ans = ['Total: 4', 'line1: <哈利波特> 哈利·波$!*&特', 'line1: <作家> zj', 'line2: <哈利波特> h利·波特',
                   'line3: <哈利波特> hl&*%^·b特']
            test = DFA()
            count = 0
            for keyword in sensitiveWord:
                test.initWords(keyword.strip())
                finalKey, pinyinKey = solve.transform(keyword.strip())
                for i in pinyinKey:
                    test.generate_dict1(i, count)
                for i in finalKey:
                    test.generate_dict(i, count)
                count += 1
    
            for i in range(len(texts)):
                test.filter(texts[i].strip(), i + 1)
    
            answer = test.getAnswer()
            self.assertEqual(ans, answer)
               

单元测试覆盖率:

2.4 计算模块部分异常处理说明

def test_IOError(self):
        try:
            with open(path.join(path.dirname(__file__), 'chai_zi.json'), 'r', encoding='utf-8') as f:
                pass
        except IOError:
            print('IOError:未找到文件或读取文件失败')
        else:
            print("文件读取成功")
           

三、心得

继续阅读