高效的质量字符串搜索问题

时间:2010-03-28 08:10:37

标签: string search design-patterns dfa glob

问题:提供了一个大的静态字符串列表。由数据和通配符元素(*和?)组成的模式字符串。我的想法是返回与模式匹配的所有字符串 - 足够简单。

当前解决方案:我目前正在使用线性方法扫描大型列表,并根据模式对每个条目进行全局化。

我的问题:是否有任何合适的数据结构可以存储大型列表,以便搜索的复杂性小于O(n)?

也许类似于 suffix-trie ?我也考虑过在哈希表中使用双克和三克,但是根据返回的单词列表和模式的合并来评估匹配所需的逻辑是一场噩梦,而且我不相信它是正确的方法

6 个答案:

答案 0 :(得分:1)

您可以构建常规trie并添加通配符边缘。然后你的复杂性将是O(n),其中n是模式的长度。您必须首先在模式中用**替换*的运行(也是O(n)操作)。

如果单词列表我是牛那么trie看起来有点像这样:

  (I ($ [I])
   a (m ($ [am])
      n ($ [an])
      ? ($ [am an])
      * ($ [am an]))
   o (x ($ [ox])
      ? ($ [ox])
      * ($ [ox]))
   ? ($ [I]
      m ($ [am])
      n ($ [an])
      x ($ [ox])
      ? ($ [am an ox])
      * ($ [I am an ox]
         m ($ [am]) ...)
   * ($ [I am an ox]
      I ...
    ...

这是一个示例python程序:

import sys

def addWord(root, word):
    add(root, word, word, '')

def add(root, word, tail, prev):
    if tail == '':
        addLeaf(root, word)
    else:
        head = tail[0]
        tail2 = tail[1:]
        add(addEdge(root, head), word, tail2, head)
        add(addEdge(root, '?'), word, tail2, head)
    if prev != '*':
        for l in range(len(tail)+1):
           add(addEdge(root, '*'), word, tail[l:], '*')

def addEdge(root, char):
    if not root.has_key(char):
        root[char] = {}
    return root[char]

def addLeaf(root, word):
    if not root.has_key('$'):
        root['$'] = []
    leaf = root['$']
    if word not in leaf:
        leaf.append(word)

def findWord(root, pattern):
    prev = ''
    for p in pattern:
        if p == '*' and prev == '*':
            continue
        prev = p
        if not root.has_key(p):
            return []
        root = root[p]
    if not root.has_key('$'):
        return []
    return root['$']

def run():
    print("Enter words, one per line terminate with a . on a line")
    root = {}
    while 1:
        line = sys.stdin.readline()[:-1]
        if line == '.': break
        addWord(root, line)
    print(repr(root))
    print("Now enter search patterns. Do not use multiple sequential '*'s")
    while 1:
        line = sys.stdin.readline()[:-1]
        if line == '.': break
        print(findWord(root, line))

run()

答案 1 :(得分:1)

我同意后缀trie是一个不错的主意,除了你的数据集的庞大大小可能会使它的构造耗尽其使用时节省的时间。如果你需要多次查询它们以分摊建筑成本,它们是最好的。也许几百个问题。

另请注意,这是并行性的一个很好的借口。将列表分成两部分并将其分配给两个不同的处理器,并使您的工作完成两倍。

答案 2 :(得分:0)

如果您不关心内存并且可以预先处理列表,请创建每个后缀的排序数组,指向原始单词,例如,对于['hello','world'],store这样:

[('d'    , 'world'),
 ('ello' , 'hello'),
 ('hello', 'hello'),
 ('ld'   , 'world'),
 ('llo'  , 'hello'),
 ('lo'   , 'hello'),
 ('o'    , 'hello'),
 ('orld' , 'world'),
 ('rld'  , 'world'),
 ('world', 'world')]

使用此数组使用模式的片段构建候选匹配集。

例如,如果模式为*or*,则使用子字符串('orld' , 'world')上的二进制方法找到候选匹配or,然后使用正常的globbing方法确认匹配。

如果通配符更复杂,例如h*o,则构建ho的候选集合,并在最终的线性glob之前找到它们的交集。

答案 3 :(得分:0)

你说你正在进行线性搜索。这是否为您提供有关最常执行的查询模式的任何数据?例如blah*比当前用户中的bl?h(我认为是这样)更常见吗?

通过这种先验知识,您可以将索引工作集中在常用案例上,并将其降低到O(1),而不是试图解决更难,但更不值得做的问题< em>每个可能的查询同样快速。

答案 4 :(得分:0)

您可以通过保留字符串中的字符数来实现简单的加速。没有b s或单个b的字符串永远不能与查询abba*匹配,因此测试它没有意义。如果你的字符串由那些字符串组成,那么整个单词的效果要好得多,因为字数比字符多得多;此外,还有很多库可以为您构建索引。另一方面,它与你提到的n-gram方法非常相似。

如果您不使用为您执行此操作的库,则可以通过在索引中首先查找最不全局的字符(或单词或n-gram)来优化查询。这允许您预先丢弃更多不匹配的字符串。

一般来说,所有加速都将基于丢弃不可能匹配的东西的想法。索引的内容和数量取决于您的数据。例如,如果典型的图案长度接近字符串长度,您只需检查字符串是否足够长以容纳图案。

答案 5 :(得分:0)

有很多好的多字符串搜索算法。谷歌“Navarro字符串搜索”,你会看到一个很好的多字符串选项分析。许多算法对于“正常”情况非常有用(搜索字符串相当长:Wu-Manber;搜索字符串,其中字符在要搜索的文本中非常罕见:并行Horspool)。 Aho-Corasick是一种算法,无论输入文本如何调整以在搜索中创建最差行为,都可以保证每个输入字符的(微小)有限工作量。对于像Snort这样的程序,面对拒绝服务攻击,这非常重要。如果您对如何实现真正有效的Aho-Corasick搜索感兴趣,请查看ACISM - an Aho-Corasick Interleaved State Matrix