【问题标题】:How to find all the unique substrings of a very long string?如何找到一个非常长的字符串的所有唯一子字符串?
【发布时间】:2014-07-09 02:45:15
【问题描述】:

我有一个很长的字符串。我想找到这个字符串的所有唯一子字符串。我尝试编写代码,使用 set(python) 来存储所有子字符串以确保唯一性。对于许多中型和大型字符串,我得到了正确的结果,但是在非常大的字符串的情况下,我得到了 MemoryError。我用谷歌搜索了一下,发现 python 中的 set 数据结构占用大量内存,这可能就是我收到 MemoryError 的原因。

这是我的代码:

a = set()
for i in range(n):
    string = raw_input()
    j = 1
    while True:
        for i in xrange(len(string)-j+1):   
            a.add(string[i:i+j])
        if j==len(string):   break
        j+=1
print sorted(list(a))

有没有办法避免大字符串出现这个错误?或者任何人都可以建议对我的代码进行更好的修改来处理这个问题?

P.S:我没有在 32 位和 64 位版本之间切换的选项。

【问题讨论】:

  • 大约有 n² 个子字符串,所以除非您的字符串有大量冗余,否则任何唯一子字符串的显式枚举(即使只给出开始和结束索引)都必然会占用大量内存。 可能有一个更压缩的表示(参见将所有后缀存储在线性内存中的后缀树),但我看不到希望。
  • @delnan:我的意思是,如果我可以在没有“设置”的情况下以某种方式做到这一点,我可以管理的字符串长度比现在要多得多。 SET 确实需要大量 RAM,远远超过 python 中的简单列表。如果可能的话,我只是想找到一种不使用 set 的方法。
  • “很长”有多大?另外,您期望有多少重复?如果您希望大多数较长的字符串是唯一的,那么我怀疑最好的方法是简单地枚举列表中的所有字符串,对列表进行排序,然后过滤重复项。如果子字符串列表不能作为列表放入内存,请将其写入磁盘并使用在磁盘上工作的排序算法,如归并排序。
  • 嗯,您当然可以在一定程度上减少内存需求。您可以维护一组“切片”(对象引用原始字符串的一部分,而无需实际构造该子字符串)。由于您只有一个超字符串,因此存储 (i, i+j) 而不是 string[i:i+j] 就足够了。 如果,在这样做之后,您发现自己几乎但不完全达到目标(例如,在生成 90% 的结果后内存不足),您也可能研究专门用于存储紧凑数据(如整数对)的集合,可能在 C 中实现。
  • 之后你对子字符串做了什么?

标签: python string algorithm memory large-data


【解决方案1】:

如果你真的需要它在内存中,那么你可以尝试制作一个后缀树。 Tries 不是奇异的数据结构,因此对于 Python 这样的主流语言可能有很好的实现,它们可用于实现后缀树。 Marisa-Trie 应该会获得良好的内存使用率。

  1. 创建一个空的 trie。
  2. 对于 [0, len(s)] 中的每个 n,将长度为 n 的后缀添加到 Trie。
  3. 从trie的根开始的每条路径都是字符串中的子字符串,字符串中没有非子字符串的路径,路径是唯一的。

【讨论】:

  • 这会创建一个包含所有后缀的 trie(穷人的后缀树)。 OP 想要所有子字符串。当然,这只需要对您的说明稍作修改。尽管如此,构造一个内存高效的 trie 还是有些复杂(在 Python 中更是如此,因为所有东西都是引用类型,即使是简单的对象也需要 28 个字节或更多)。
  • @delnan,每个子字符串都是后缀的前缀 :) 请注意,当我谈到“从根开始的路径”时,我并没有说“到叶子”。
  • 我们找到了我一直在寻找的压缩存储结构! +1
【解决方案2】:

这是一些基于 O(n) 后缀树构造的 Python 代码,用于从输入字符串的集合中生成唯一的子字符串(输出应按排序顺序显示,因此之后无需对字符串进行排序)。

由于可能有 O(n^2) 个输出字符串,实际输出所有字符串可能需要很长时间。

from collections import defaultdict

class SuffixTree:
    def __init__(self):
        """Returns an empty suffix tree"""
        self.T=''
        self.E={}
        self.nodes=[-1]

    def add(self,s):
        """Adds the input string to the suffix tree.

        This inserts all substrings into the tree.
        End the string with a unique character if you want a leaf-node for every suffix.

        Produces an edge graph keyed by (node,character) that gives (first,last,end)
        This means that the edge has characters from T[first:last+1] and goes to node end."""
        origin,first,last = 0,len(self.T),len(self.T)-1
        self.T+=s
        nc = len(self.nodes)
        self.nodes += [-1]*(2*len(s))
        T=self.T
        E=self.E
        nodes=self.nodes

        Lm1=len(T)-1
        for last_char_index in xrange(first,len(T)):
            c=T[last_char_index]
            last_parent_node = -1                    
            while 1:
                parent_node = origin
                if first>last:
                    if (origin,c) in E:
                        break             
                else:
                    key = origin,T[first]
                    edge_first, edge_last, edge_end = E[key]
                    span = last - first
                    A = edge_first+span
                    m = T[A+1]
                    if m==c:
                        break
                    E[key] = (edge_first, A, nc)
                    nodes[nc] = origin
                    E[nc,m] = (A+1,edge_last,edge_end)
                    parent_node = nc
                    nc+=1  
                E[parent_node,c] = (last_char_index, Lm1, nc)
                nc+=1  
                if last_parent_node>0:
                    nodes[last_parent_node] = parent_node
                last_parent_node = parent_node
                if origin==0:
                    first+=1
                else:
                    origin = nodes[origin]

                if first <= last:
                    edge_first,edge_last,edge_end=E[origin,T[first]]
                    span = edge_last-edge_first
                    while span <= last - first:
                        first+=span+1
                        origin = edge_end
                        if first <= last:
                            edge_first,edge_last,edge_end = E[origin,T[first]]
                            span = edge_last - edge_first

            if last_parent_node>0:
                nodes[last_parent_node] = parent_node
            last+=1
            if first <= last:
                    edge_first,edge_last,edge_end=E[origin,T[first]]
                    span = edge_last-edge_first
                    while span <= last - first:
                        first+=span+1
                        origin = edge_end
                        if first <= last:
                            edge_first,edge_last,edge_end = E[origin,T[first]]
                            span = edge_last - edge_first
        return self

    def make_choices(self):
        """Construct a sorted list for each node of the possible continuing characters"""
        choices = self.choices = [list() for n in xrange(len(self.nodes))] # Contains set of choices for each node
        for (origin,c),edge in self.E.items():
            choices[origin].append(c)
        choices=[sorted(s) for s in choices] # should not have any repeats by construction
        return choices

    def find_substrings(self,A,term):
        """Recurses through the tree appending unique substrings into A.
        Strings assumed to use term as the terminating character"""
        choices = self.make_choices()
        def f(node,depth):
            t=0
            for c in choices[node]:
                if c==term: continue
                first,last,end = self.E[node,c]
                # All end points along this edge result in new unique substrings
                edge_len = last-first+1
                a = first-depth
                for b in range(first,last+1):
                    if self.T[b]!=term:
                        A.append( self.T[a:b+1] )
                f(end,depth+edge_len)
            return t
        return f(0,0)

def fast_find_all_substrings(strings):
    S = SuffixTree()
    term = '\0'
    for string in strings:
        S.add(string+term)
    A=[]
    S.find_substrings(A,term)
    return A

A="abc","abcd","bca"
print fast_find_all_substrings(A)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-11
    • 2014-02-02
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多