实验目的
熟悉高级语言内置的模式匹配工具 find() index() 初步了解有限状态自动机的概念 绘制简单的DFA图 了解工业界基于DFA算法的高级应用:敏感词滤波器 从深度上和广度上理解字符串匹配的过程与关键 体会高效的匹配算法在海量文本信息处理中的作用 学习用图的知识抽象问题 增强建立模型运用模型的能力
实验原理
尽管计算机图形学越来越受重视,但是文字信息处理依然是重要的研究领域,特别是在长字符串中寻找模式。这种模式常被称作子串。为了找到模式,会进行某种搜索,至少要能找到模式首次出现的位置。我们也可以想想这个问题的扩展版本,即如何找到模式出现的所有位置
Python有一个内置的find方法,可用于在给定字符串的情况下返回模式首次出现的位置,如下所示。局限是无法全局进行多次匹配。
In [59]: seq = "ATGATATAAGGTC"
...: pat = "TA"
...: seq.find(pat)
Out[59]: 4
如果对模式做一些预处理,就可以创建时间复杂度为O(n)的模式匹配器。一种做法是用图来表示模式,从而构建确定有限状态自动机,或称DFA。在DFA图中,每个顶点是一个状态,用于记录匹配成功的模式数。图中每一条边代表处理文本中的一个字母后发生的转变。
因为文本中的每个字母都作为DFA图的输入被使用一次,所以这种算法的时间复杂度是O(n)。不过,还需要考虑构建DFA的预处理步骤。有很多知名算法可以根据模式生成DFA图。
实验内容
实现基于DFA的模式匹配算法
下载敏感词库 /banner_words.txt
DFA 匹配关键词过程主要有两部分组成:
- 构建存储结构 --- 树形结构
把敏感词组成成树形结构有什么好处呢?最大的好处就是可以减少检索次数,我们只需要遍历一次待检测文本,然后在敏感词库中检索出有没有该字符对应的子树就行了,如果没有相应的子树,说明当前检测的字符不在敏感词库中,则直接跳过继续检测下一个字符;如果有相应的子树,则接着检查下一个字符是不是前一个字符对应的子树的子节点,这样迭代下去,就能找出待检测文本中是否包含敏感词了。
- 关键词匹配 --- 状态记录
# -*- coding:utf-8 -*-
import time
time1 = time.time()
# DFA算法
class DFAFilter(object):
def __init__(self):
self.keyword_chains = {} # 关键词链表
self.delimit = '\x00' # 限定
def add(self, keyword):
keyword = keyword.lower() # 关键词英文变为小写
chars = keyword.strip() # 关键字去除首尾空格和换行
if not chars: # 如果关键词为空直接返回
return
level = self.keyword_chains
# 遍历关键字的每个字
for i in range(len(chars)):
# 如果这个字已经存在字符链的key中就进入其子字典
if chars[i] in level:
level = level[chars[i]]
else:
if not isinstance(level, dict):
break
for j in range(i, len(chars)):
level[chars[j]] = {}
last_level, last_char = level, chars[j]
level = level[chars[j]]
last_level[last_char] = {self.delimit: 0}
break
if i == len(chars) - 1:
level[self.delimit] = 0
def parse(self, path):
with open(path, encoding='utf-8') as f:
for keyword in f:
self.add(str(keyword).strip())
print(self.keyword_chains)
def filter(self, message, repl="*"):
message = message.lower()
ret = []
start = 0
while start < len(message):
level = self.keyword_chains
step_ins = 0
for char in message[start:]:
if char in level:
step_ins += 1
if self.delimit not in level[char]:
level = level[char]
else:
ret.append(repl * step_ins)
start += step_ins - 1
break
else:
ret.append(message[start])
break
else:
ret.append(message[start])
start += 1
return ''.join(ret)
if __name__ == "__main__":
gfw = DFAFilter()
path = "/home/feilong/Code/Py_files/words.txt"
gfw.parse(path)
text = "!!!猪队友太狗了真是废物、搞心态、神经病、大笨蛋、脑子进水???"
result = gfw.filter(text)
print(text)
print(result)
time2 = time.time()
print('总共耗时:' + str(time2 - time1) + 's')
[Running] python3 -u "/home/feilong/Code/Py_files/Jun14.py"
{'笨': {'蛋': {'\x00': 0}}, '废': {'物': {'\x00': 0}}, '猪': {'\x00': 0}, '狗': {'\x00': 0}}
...猪队友太狗了真是废物、搞心态、神经病、大笨蛋、脑子进水...
...*队友太*了真是**、搞心态、神经病、大**、脑子进水...
总共耗时:0.00032639503479003906s
效果是实现了垃圾话的屏蔽 就像我们平时打王者的时候聊天输入框很多词语都变成“*” 敏感词存入了文件 写了函数来读取
上述代码摘自互联网 还需要再进一步研究
因为常见的敏感词都不会太长 用嵌套的字典带来的空间开销有限
画出KMP算法对应的状态图
实验总结
❏ DFA图易于使用,但不易构建。
❏ KMP图既易于使用,也易于构建
从状态转移的角度理解模式匹配
从动态规划的角度理解KMP算法
思考题
实现字符串的多级嵌套字典 以模拟树结构
md = {}
cur = md
for w in "words":
# 可以在下面的字典里填充内容
nxt = {}
cur[w] = nxt
cur = nxt
print(md)
[Running] python3 -u "/home/feilong/Code/Py_files/Jun14.py"
{'w': {'o': {'r': {'d': {'s': {}}}}}}
参考资料
<<离散数学>> 形式语言与自动机初步 <<算法>> 第四版 <<Python数据结构与算法分析>> 第二版 <> cheapter 6 Finding Patterns in Sequences zhuanlan.zhihu.com/p/83334559 labuladong zhuanlan.zhihu.com/p/214347618 blog.csdn.net/weixin_4337…