在python中使用后缀树

时间:2012-04-10 09:54:34

标签: python hash suffix-tree

我是python的新手,我开始使用后缀树。我可以构建它们,但是当字符串变大时我遇到了内存问题。我知道它们可以用于处理大小为4 ^ 10或4 ^ 12的DNA字符串,但每当我尝试实现一种方法时,我最终会遇到内存问题。

这是我生成字符串和后缀树的代码。

import random

def get_string(length):
    string=""
    for i in range(length):
        string += random.choice("ATGC")
    return string

word=get_string(4**4)+"$"

def suffixtree(string):
    for i in xrange(len(string)):
        if tree.has_key(string[i]):
            tree[string[i]].append([string[i+1:]][0])
        else:
            tree[string[i]]=[string[i+1:]]
    return tree

tree={}
suffixtree(word)

当我达到4 ** 8左右时,我遇到了严重的记忆问题。我对此很陌生,所以我确定我错过了存储这些东西的东西。任何建议将不胜感激。

作为注释:我想进行字符串搜索以查找非常大的字符串中的匹配字符串。搜索字符串匹配大小为16.因此,这将在大字符串中查找大小为16的字符串,然后移动到下一个字符串并执行另一个搜索。由于我将进行大量搜索,因此建议使用后缀树。

非常感谢

4 个答案:

答案 0 :(得分:4)

对我来说,这看起来不像是一棵树。看起来您正在生成所有可能的后缀,并将它们存储在哈希表中。

如果使用实际树,则可能会获得更小的内存性能。我建议使用库实现。

答案 1 :(得分:2)

如果你的内存问题在于创建后缀树,你确定需要一个吗?您可以在单个字符串中找到所有匹配项,如下所示:

word=get_string(4**12)+"$"

def matcher(word, match_string):
    positions = [-1]
    while 1:
        positions.append(word.find(match_string, positions[-1] + 1))
        if positions[-1] == -1:
            return positions[1:-1]

print matcher(word,'AAAAAAAAAAAA')
[13331731, 13331732, 13331733]
print matcher('AACTATAAATTTACCA','AT')
[4, 8]

我的机器很老了,这需要30秒才能运行,有4 ^ 12个字符串。我使用了12位数的目标,所以会有一些比赛。此解决方案也会找到重叠的结果 - 如果有的话。

Here是您可以尝试的后缀树模块,如下所示:

import suffixtree
stree = suffixtree.SuffixTree(word)
print stree.find_substring("AAAAAAAAAAAA")

不幸的是,我的机器太慢,无法用长琴弦正确测试。但据推测,一旦构建了后缀树,搜索速度就会非常快,因此对于大量搜索来说,它应该是一个很好的调用。进一步find_substring只返回第一个匹配(不知道这是否是一个问题,我相信你可以轻松地调整它)。

更新:将字符串拆分为较小的后缀树,从而避免内存问题

因此,如果您需要在4 ^ 12长度字符串上进行1000万次搜索,我们显然不想等待9。5年(标准简单搜索,我首先建议,在我的慢速机器上......)。但是,我们仍然可以使用后缀树(因此更快),并避免内存问题。将大字符串拆分为可管理的块(我们知道机器内存可以处理)并将块转换为后缀树,搜索1000万次,然后丢弃该块并移动到下一块。我们还需要记住搜索每个块之间的重叠。我写了一些代码来执行此操作(它假定要搜索的大字符串,word是我们最大可管理字符串长度的倍数max_length,您必须调整代码以检查最后的余数,如果不是这样的话):

def split_find(word,search_words,max_length):
    number_sub_trees = len(word)/max_length
    matches = {}
    for i in xrange(0,number_sub_trees):
        stree = suffixtree.SuffixTree(word[max_length*i:max_length*(i+1)])
        for search in search_words:
            if search not in matches:
                match = stree.find_substring(search)
                if match > -1:
                    matches[search] = match + max_length*i,i
            if i < number_sub_trees:
                match = word[max_length*(i+1) - len(search):max_length*(i+1) + len(search)].find(search)
                if match > -1:
                    matches[search] = match + max_length*i,i
    return matches

word=get_string(4**12)
search_words = ['AAAAAAAAAAAAAAAA'] #list of all words to find matches for
max_length = 4**10 #as large as your machine can cope with (multiple of word)
print split_find(word,search_words,max_length)

在此示例中,我将最大后缀树长度限制为长度为4 ^ 10,这需要大约700MB。 使用此代码,对于一个4 ^ 12长度的字符串,1000万次搜索应该花费大约13个小时(完整搜索,零匹配,所以如果有匹配则会更快)。但是,作为其中的一部分,我们需要构建100个后缀树,这将需要大约100 ... 41秒= 1小时。

所以总的运行时间大约是14个小时,没有内存问题...... 9.5年的大改进。 请注意,我在具有1GB RAM的1.6GHz CPU上运行它,所以你应该能够比这更好!

答案 2 :(得分:2)

您遇到内存问题的原因是输入'banana'正在生成{'b': ['anana$'], 'a': ['nana$', 'na$', '$'], 'n': ['ana$', 'a$']}。那不是树结构。您可以在其中一个列表中创建和存储输入的每个可能后缀。这需要O(n ^ 2)存储空间。此外,要使后缀树正常工作,您希望叶节点为您提供索引位置。

result you want to get{'banana$': 0, 'a': {'$': 5, 'na': {'$': 3, 'na$': 1}}, 'na': {'$': 4, 'na$': 2}}。 (这是一种优化的表示;更简单的方法将我们限制为单字符标签。)

答案 3 :(得分:2)

正如其他人已经说过的那样,您构建的数据结构不是后缀树。但是,内存问题很大程度上源于您的数据结构涉及大量显式字符串副本。这样的调用

string[i+1:]

i+1开始创建子字符串的实际(深层)副本。

如果您仍然对构建原始数据结构(无论其用途如何)感兴趣,一个好的解决方案是使用缓冲区而不是字符串副本。您的算法将如下所示:

def suffixtree(string):
    N = len(string)
    for i in xrange(N):
        if tree.has_key(string[i]):
            tree[string[i]].append(buffer(string,i+1,N))
        else:
            tree[string[i]]=[buffer(string,i+1,N)]
    return tree

我尝试将其嵌入到其余代码中,并确认即使总长度为8 ^ 11个字符,它也只需要少于1 GB的主内存。

请注意,即使切换到实际后缀树,这也可能是相关的。正确的后缀树实现不会在树边缘存储副本(甚至不是缓冲区);但是,在树构造期间,您可能需要大量的字符串临时副本。对于这些使用buffer类型是一个非常好的想法,以避免对所有不必要的显式字符串副本的垃圾收集器造成沉重负担。