前段时间在跟师兄打机器学习比赛的时候,师兄让我去匹配1.5w标签在20w数据中的出现次数,一开始的时候我用正则表达式,双重循环遍历1.5w标签和20w数据,粗略计算了一下,发现全部标签跑完需要大概6天的时间(这就很让我绝望啊)。后来师兄告诉我去用AC自动机,我看了一个下午还是没有实现AC自动机,无奈师兄只能抽空完成了我的任务。。。
经过几天摸索,我粗略实现了AC自动机。
首先介绍一下什么是AC自动机:Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。多模匹配算法,完美契合我的任务(多标签匹配问题)
简单地讲,AC自动机就是字典树+kmp算法+失配指针
python建立字典树(实际上就是建树),由于python没有结构体,所以树的节点就用一个类来表示:
class node(object):
def __init__(self):
self.next = {} #相当于指针,指向树节点的下一层节点
self.fail = None #失配指针,这个是AC自动机的关键
self.isWord = False #标记,用来判断是否是一个标签的结尾
self.word = "" #用来储存标签
接下来就是定义一个AC自动机的类:
class ac_automation(object):
def __init__(self):
self.root = node() #定义根节点
建立字典树:
def add(self, word):
temp_root = self.root
for char in word: #遍历标签的每个字
if char not in temp_root.next: #如果节点下没有这个字,就加入这个字的节点
temp_root.next[char] = node()
temp_root = temp_root.next[char] #沿着标签建立字典
temp_root.isWord = True #标签结束,表示从根到这个节点是一个完整的标签
temp_root.word = word #储存这个标签
创建fail指针:
def make_fail(self):
temp_que = [] #使用BFS来遍历这棵字典树
temp_que.append(self.root)
while len(temp_que) != 0:
temp = temp_que.pop(0)
p = None
for key,value in temp.next.item():
if temp == self.root: #根节点的孩子们的fail都指向根
temp.next[key].fail = self.root
else:
p = temp.fail
while p is not None: #寻找fial指针指向的地方
if key in p.next: #如果是另一个fail指针下面找到了,就储存
temp.next[key].fail = p.fail
break
p = p.fail
if p is None: #如果该节点fail指针不存在,就指向根
temp.next[key].fail = self.root
temp_que.append(temp.next[key])
多模匹配:
def search(self, content):
p = self.root
result = []
currentposition = 0 #用来标记当前标签的汉字
while currentposition < len(content):
word = content[currentposition]
while word in p.next == False and p != self.root: #搜索状态机,直到匹配
p = p.fail
if word in p.next:
p = p.next[word]
else:
p = self.root
if p.isWord: #若状态到达标签结尾,就添加入结果中
result.append(p.word)
currentposition += 1
把上面3个结合起来就是一个AC自动机的类了:
class ac_automation(object):
def __init__(self):
self.root = node()
def add(self, word):
temp_root = self.root
for char in word:
if char not in temp_root.next:
temp_root.next[char] = node()
temp_root = temp_root.next[char]
temp_root.isWord = True
temp_root.word = word
def make_fail(self):
temp_que = []
temp_que.append(self.root)
while len(temp_que) != 0:
temp = temp_que.pop(0)
p = None
for key,value in temp.next.item():
if temp == self.root:
temp.next[key].fail = self.root
else:
p = temp.fail
while p is not None:
if key in p.next:
temp.next[key].fail = p.fail
break
p = p.fail
if p is None:
temp.next[key].fail = self.root
temp_que.append(temp.next[key])
def search(self, content):
p = self.root
result = []
currentposition = 0
while currentposition < len(content):
word = content[currentposition]
while word in p.next == False and p != self.root:
p = p.fail
if word in p.next:
p = p.next[word]
else:
p = self.root
if p.isWord:
result.append(p.word)
currentposition += 1
return result
测试:
ac = ac_automation()
ac.add('潮汕话')
ac.add('垃圾话')
ac.add('666')
ac.add('生存游戏')
ac.search('说潮汕话的人不要轻易喷垃圾话,有时间还不如玩玩生存类的游戏')
['潮汕话', '垃圾话']
AC自动机真是个好东西!