转载请注明出处: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. python性能优化

      注意:本文除非特殊指明,”python“都是代表CPython,即C语言实现的标准python,且本文所讨论的是版本为2.7的CPython. python为什么性能差: 当我们提到一门编程语言的 ...

  2. NDK 开发(Android.mk配置)

         在我写这篇文章的时候,Android Studio已经是2.3版本了,已经集成CMake 编译工具, 用户只需在 新建项目的时候,添加选项(Include C++ support),Andr ...

  3. Servlet的监听

    Servlet监听 在<Servlet和Jsp>中我们使用了ServletConfig获取Servlet的初始配置,用ServletContext来获取整个Web应用的初始配置,但如果需要 ...

  4. HTML5之多媒体

    概览 html5新增了两个关于多媒体的元素:video和audio,前者是用于视频,后者用于音频.而他们使用非常简单 <audio src="xhn.mp3" control ...

  5. C#中static void Main(string[] args) 参数详解

    学习C#编程最常见的示例程序是在控制台应用程序中输出Hello World! using System; namespace DemoMainArgs { class Program { static ...

  6. 微信小程序入门之构建一个简单TODOS应用

    最近开始了解微信小程序,虽然小程序已经出了很久了,刚出的那段时间很火,看到很多关于小程序的技术文章,不过现在似乎没那么火了,anyway,我们还是可以学习下的. 一.了解微信小程序 1.理念:小程序开 ...

  7. java基础:数组查询,同一数组一个元素最多出现两次

  8. 网络信息安全攻防学习平台 上传,解密通关writeup

    上传关 [1]查看源代码,发现JS代码.提交时onclick进行过验证.ctrl+shift+i 打开开发者工具,将conclick修改为 return True,即可以上传上传php文件,拿到KEY ...

  9. 1011: [HNOI2008]遥远的行星

    1011: [HNOI2008]遥远的行星 Time Limit: 10 Sec  Memory Limit: 162 MBSec  Special JudgeSubmit: 2241  Solved ...

  10. 图解Javascript——变量对象和活动对象

    span { line-height: 1.5 } 这是由一段代码引发的思考: var laterDeclaredVar = 'I am a global variable ...'; (functi ...