转载请注明出处:http://www.cnblogs.com/kirai/ 作者:Kirai

零.问题的提出

  最近希望在分布式平台上实现一个AC自动机,但是如何在这样的分布式平台上表示这样的非线性数据结构就难住我了。因为一直在使用RDD提供的一些基本的操作,没有需要什么复杂的操作。所以突然想到了在分布式的平台上实现一个AC自动机一定很有趣。网上搜了下,发现没有人实现,因此决定尝试实现。或许就是一个玩具,不过也是能帮助自己更深理解分布式平台上进行编程和普通编程的区别吧。

  这个问题对我来讲还是有一定的难度的,加上课业、复习考研、竞赛三重压力,对于这个问题的研究时间可能会比较长,不过一定会抽出时间来考虑的。

  文章仅仅是在记录自己对问题的思考过程和代码,并不能保证每一步都是合理、有用、正确的。

一.实现可持久化字典树

  AC自动机是个什么东西就不赘述了,我首先用python实现了一个比较朴素的版本,这里查询是返回所有字典中出现的单词的起始位置,每一个单词一个list,最终组成一个dict。哈哈,也许有人看出来了这是去年的一道ICPC网赛题,没错。但是我忘记是哪一道了,只记得当时没做出来,所以用python写一个就当是对自己的一个补偿吧:)

 # -*- coding: utf-8 -*-
class Node:
"""
A Trie's basic data structure.
"""
def __init__(self):
self.next_letter = {}
self.is_word = False
self.letter = ''
self.depth = -1
self.pre = None
self.fail = None class Trie:
def __init__(self):
self.root = Node() def insert(self, word):
"""
insert a word into the trie.
:param word: the word
:type word: str
:return: None
"""
cur_node = self.root
pre = self.root
depth = 1
for letter in word:
if not cur_node.next_letter.has_key(letter):
cur_node.next_letter[letter] = Node()
cur_node = cur_node.next_letter[letter] cur_node.pre = pre
cur_node.letter = letter
cur_node.depth = depth depth += 1
pre = cur_node cur_node.is_word = True def in_trie(self, word):
"""
judge if the word is in the trie or not.
:param word: the word
:type word: str
:return: if the word is in the trie or not.
:rtype: bool
"""
cur_node = self.root
for letter in word:
if not cur_node.next_letter.has_key(letter):
return False
cur_node = cur_node.next_letter[letter]
return cur_node.is_word class AcAutomation:
def __init__(self):
self._trie = Trie()
self._dict = set([]) def in_trie(self, word):
return self._trie.in_trie(word) def insert(self, word):
map(self._dict.add, word)
self._trie.insert(word) def build_ac_automation(self):
"""
build the fail pointers to make the ac automation work.
:return:
"""
queue = []
queue.append(self._trie.root)
cur_node, tmp_node = None, None while len(queue) != 0:
cur_node = queue.pop(0)
for letter in cur_node.next_letter:
if cur_node == self._trie.root:
cur_node.next_letter[letter].fail = self._trie.root
else:
tmp_node = cur_node.fail
while tmp_node != None:
if tmp_node.next_letter.has_key(letter):
cur_node.next_letter[letter].fail = \
tmp_node.next_letter[letter]
break
tmp_node = tmp_node.fail
if tmp_node == None:
cur_node.next_letter[letter].fail = self._trie.root queue.append(cur_node.next_letter[letter]) def get_word_position(self, sentence):
"""
find the word's positions in the sentence according to
the dictionary.
:param sentence:
:rtype: Dict[List[]]
:return: an dictionary include the word that appears in the sentence
and the start and end positions.
"""
cur_node = self._trie.root
tmp_node = None
result = {}
length = len(sentence)
for idx in range(0, length):
letter = sentence[idx]
if letter not in self._dict:
cur_node = self._trie.root
continue if cur_node.next_letter.has_key(letter):
cur_node = cur_node.next_letter[letter]
else:
while cur_node != None and cur_node.next_letter.has_key(letter) == False:
cur_node = cur_node.fail
if cur_node == None:
cur_node = self._trie.root if cur_node.next_letter.has_key(letter):
cur_node = cur_node.next_letter[letter] tmp_node = cur_node
while tmp_node != self._trie.root:
if tmp_node.is_word == True:
word = ''
word_node = tmp_node
while word_node != self._trie.root:
word += word_node.letter
word_node = word_node.pre
word = word[::-1]
if not result.has_key(word):
result[word] = []
result[word].append((idx - tmp_node.depth + 1, idx))
tmp_node = tmp_node.fail return result s = AcAutomation()
s.insert('AA')
s.insert('BB')
s.insert('CC')
s.insert('ACM')
s.build_ac_automation()
q = s.get_word_position('ooxxCC%dAAAAAAAAaaAAaoenGGACM') print q

返回的结果是这样的:

 {'CC': [(4, 5)], 'AA': [(8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (18, 19)], 'ACM': [(26, 28)]}

  如何在分布式的平台上实现这样的运算?显然这种实现是有内存共享的,如何做到消除这些共享?

  确实想不到,看来还要多看资料学习。但是可以肯定的一点是,AC自动机的结构一定不允许是这样的。

  由于Spark使用RDD对分布式内存抽象,支持的操作均是熟悉的函数式操作。所以考虑是不是可以把Trie“扁平化”成这样的结构?

  于是定义:

{ letter: [{next level..}, is_word depth] }

  代表一个节点,letter代表当前节点的字符,后接一个list,list第一个元素表示此节点的后继,后面跟着的是一些属性。这些属性是可以扩充的。稍微转换一下思路,就可以把这个字典树写出来:

 def build_trie():
"""
build a empty trie
:return:
"""
return {} def insert(root, word, depth=1):
"""
insert a word into the trie recursively.
:param root:
:param word:
:param depth:
:return:
"""
letter = word[0]
if len(word) == 1:
if(root.has_key(letter)):
root[letter][1] = True
return root
else:
return {letter: [{}, True, depth]}
if root.has_key(letter) == False:
root[letter] = [{}, False, depth]
root[letter][0] = insert(root[letter][0], word[1:], depth+1)
else:
root[letter][0] = dict(root[letter][0], **insert(root[letter][0], word[1:], depth+1))
return root def find(root, word):
"""
find a word if it's in the trie or not.
:param root:
:param word:
:return:
"""
letter = word[0]
if root.has_key(letter) == False:
return False
if len(word) == 1:
if root[letter][1] == True:
return True
else:
return False
return find(root[letter][0], word[1:])

  我考虑过字典树如何从一个分布式平台上构建,可以这样:

  通过平时对单词的搜集,可以在单机上生成一个又一个小的字典树,保存在分布式文件系统上,在使用的时候只需要把这一个个小字典树合并成一个就行了。

  所以这棵字典树仅仅这样是不够的,因为我希望这字典树支持这个合并的函数式操作,于是加上了归并的操作,这样这棵字典树变成了一棵可持久字典树:

 def merge(a_root, b_root):
"""
merge two tries.
:param a_root:
:param b_root:
:return:
"""
def _merge(a_root, b_root):
for letter in b_root.keys():
if a_root.has_key(letter):
if b_root[letter][1] == True:
a_root[letter][1] = True
a_root[letter][0] = dict(a_root[letter][0], **_merge(a_root[letter][0], b_root[letter][0]))
else:
a_root[letter] = copy.deepcopy(b_root[letter])
return a_root
a_root = _merge(a_root, b_root)
return a_root

  合并的操作思路很简单,就是同时深入,假如有一棵树上没有另一棵树上的儿子,整棵子树都拷贝过来即可(这也是用python的dict方便的地方)。

  测试了一下:

 if __name__ == '__main__':
trie_a = build_trie()
trie_b = build_trie() trie_a = insert(trie_a, 'acm')
trie_a = insert(trie_a, 'acr')
trie_a = insert(trie_a, 'ak')
trie_a = insert(trie_a, 'bc') trie_b = insert(trie_b, 'ac')
trie_b = insert(trie_b, 'cm')
trie_b = insert(trie_b, 'br') s = [trie_a, trie_b]
s = reduce(merge, s)
print json.dumps(s)

  输出的结构dumps成json后格式化一下是这样的:

{
"a": [
{
"c": [
{
"r": [
{},
true,
3
],
"m": [
{},
true,
3
]
},
true,
2
],
"k": [
{},
true,
2
]
},
false,
1
],
"c": [
{
"m": [
{},
true,
2
]
},
false,
1
],
"b": [
{
"c": [
{},
true,
2
],
"r": [
{},
true,
2
]
},
false,
1
]
}

  这样一棵可持久化的字典树就实现了,接下来考虑将这段程序翻译成Spark的程序,并且生成一些字典数据,看看是否符合自己的预期。

[Python] Spark平台下实现分布式AC自动机(一)的更多相关文章

  1. 在Window平台下安装xgboost的Python版本

    原文:http://blog.csdn.net/pengyulong/article/details/50515916 原文修改了两个地方才安装成功,第3步可以不用,第2步重新生成所有的就行了. 第4 ...

  2. python平台下实现xgboost算法及输出的解释

    python平台下实现xgboost算法及输出的解释 1. 问题描述 ​ 近来, 在python环境下使用xgboost算法作若干的机器学习任务, 在这个过程中也使用了其内置的函数来可视化树的结果, ...

  3. (转载)Linux平台下安装 python 模块包

    https://blog.csdn.net/aiwangtingyun/article/details/79121145 一.安装Python Windows平台下: 进入Python官网下载页面下载 ...

  4. python爬虫学习(11) —— 也写个AC自动机

    0. 写在前面 本文记录了一个AC自动机的诞生! 之前看过有人用C++写过AC自动机,也有用C#写的,还有一个用nodejs写的.. C# 逆袭--自制日刷千题的AC自动机攻克HDU OJ HDU 自 ...

  5. .NET平台下开源框架

    一.AOP框架Encase 是C#编写开发的为.NET平台提供的AOP框架.Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式.这种部署方面(asp ...

  6. .NET平台下使用MongoDB入门教程

    适合人群:完全没有接触MongoDB或对MongoDB有一点了解的C#开发人员.因为本文是一篇入门级的文章. 一.了解MongoDB  MongoDB是一个基于分布式文件存储的数据库.由C++语言编写 ...

  7. Tachyon:Spark生态系统中的分布式内存文件系统

    转自: http://www.csdn.net/article/2015-06-25/2825056  摘要:Tachyon把内存存储的功能从Spark中分离出来, 使Spark可以更专注计算的本身, ...

  8. 转 【.NET平台下使用MongoDB入门教程】

    目录 一.了解MongoDB 二.MongoDB特点 三.安装及常用命令 3.1 下载安装 3.2 启动服务器 3.3 常用操作 3.4 其他命令 3.5 做成windows服务 四.批处理程序开启M ...

  9. 基于trie树做一个ac自动机

    基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...

随机推荐

  1. JavaWeb从0开始学(一)-----搭建第一个Web应用程序与JSP工作原理

    以往学习的时候大多是看完书或者看完视频,动手实践一下就OK了.然而过了一段时间我发现东西都忘差不多了,需要复习才能重新掌握.现在开始学习JavaWeb了,我将在这里记录自己的学习的一点一滴,不仅便于自 ...

  2. Lambda表达式和Java集合框架

    本文github地址 前言 我们先从最熟悉的Java集合框架(Java Collections Framework, JCF)开始说起. 为引入Lambda表达式,Java8新增了java.util. ...

  3. 如何用好消息推送为app拉新、留存、促活

    作为移动端APP产品运营最重要的运营手段,JPush消息推送被越来越多的APP厂商所重视,在信息泛滥的移动互联网时代,手机APP应用安装得越来越多,小小的手机屏幕每天收到的消息推送也越来越多,站在用户 ...

  4. Asp.net mvc 知多少(十)

    本系列主要翻译自<ASP.NET MVC Interview Questions and Answers >- By Shailendra Chauhan,想看英文原版的可访问http:/ ...

  5. yii2的变量是如何注入到视图中去的?

    我对yii2的控制器中的变量如何渲染到视图中这个问题一直很好奇,另外,我还对yii2如何加载静态资源,如js和css比较好奇,于是趁着周末就看了下yii2的相关源码想把这两个问题都弄明白.变量如何渲染 ...

  6. oracle xe 数据库用户操作

    在system账号登录获得system权限,然后对用户进行操作 --创建表空间create tablespace tablespace_name datafile 'D:\tablespace_nam ...

  7. java利用url实现网页内容的抓取

    闲来无事,刚学会把git部署到远程服务器,没事做,所以简单做了一个抓取网页信息的小工具,里面的一些数值如果设成参数的话可能扩展性能会更好!希望这是一个好的开始把,也让我对字符串的读取掌握的更加熟练了, ...

  8. python + selenium <一>

    python 安装 python 下载地址: http://python.org/getit/ ez_setup.py 下载地址: https://pypi.python.org/packages/s ...

  9. Struts 基本概念,优点及不同版本之间的关系

    strutx 1.x struts 是 apache 基金会的一个开源项目. struts 是一套实现 MVC的框架. MVC = 程序分层设计的思想 = Model(数据访问层1) / View(视 ...

  10. javaScript绑定事件委托 demo

    事件绑定通常发生在 onload 或 DOMContentReady , 事件绑定占用 处理时间 占用内存, 而且不是每个事件都会被 点击执行. 由此 事件委托 可以优化事件绑定行为.. 事件逐层冒泡 ...