转载请注明出处: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. 简单分析beyond作曲

    本人绝对是业余的哈 业余到什么水平呢?正在练习爬格子,还是一个星期练几次那种 先说下<海阔天空> 6,5,4,3 1,2,3,4 简单是简单得不得了,声从低到高,然后再从高到低,产生一种回 ...

  2. 剑指offer编程题Java实现——面试题12打印1到最大的n位数

    题目:打印1到最大的n位数 输入数字n,按顺序打印输出从1到最大的n位十进制数,比如输入3,打印从1到999. 这道题考察的地方是如何表示大数问题.由于n是任意大的数组,如果n太大的话n位数就超过了l ...

  3. MES设备支持快速完工

    1) 在菜单界面点击指定快速键 2) 初始界面 3) 一般流程 a) 扫描任务单号,即可完成工序加工 a1) 获取任务单工序的条件 按任务单卡号或配模的模具卡号搜索行状态为O的工序 a2) 工序完工操 ...

  4. jQuery的基本操作

    jQuery就是一个js的库· 主要分为两部分:            1·寻找元素         (选择器,筛选器)            2·操作元素          (CSS的操作,属性的操 ...

  5. Android: DrawerLayout 侧滑菜单栏

    DrawerLayout是SupportLibrary包中实现的侧滑菜单效果的控件. 分为主内容区域和侧边菜单区域 drawerLayout本身就支持:侧边菜单根据手势展开与隐藏, 开发者只需要实现: ...

  6. webots自学笔记(四)传感器API使用、查看官方文档

           原创文章,来自“博客园,_阿龙clliu” http://www.cnblogs.com/clliu/,转载请注明原文章出处.           不能说webots的学习资料少,只能说 ...

  7. Effective Modern C++ Item 27:重载universal references

    假设有一个接收universal references的模板函数foo,定义如下: template<typename T> void foo(T&& t) { cout ...

  8. ubuntu 切换java环境,配置单独的用户环境

    执行命令:sudo  update-alternatives --config javaThere are 2 choices for the alternative java (providing ...

  9. Spring-Mybatis配置多数据源

    可以参考: http://www.cnblogs.com/ityouknow/p/6102399.html 需要一个DatabaseConfiguration类,实现 TransactionManag ...

  10. struct和typedef struct在c++中的用法

    #include<iostream> using namespace std; struct test{ int a; }test; //定义了结构体类型test,声明变量时候直接test ...