相互包含的高效字符串

时间:2011-11-27 20:55:19

标签: python regex string

我有两组字符串(AB),我想知道所有字符串对a in Ab in B,其中ab的子字符串。

编码的第一步如下:

for a in A:
    for b in B:
        if a in b:
            print (a,b)

但是,我想知道 - 使用正则表达式是否有更有效的方法(例如,而不是检查if a in b:,检查正则表达式'.*' + a + '.*':是否匹配'b'。我想也许使用这样的东西可以让我为所有a缓存Knuth-Morris-Pratt失败函数。而且,使用内部for b in B:循环的列表推导可能会带来相当大的加速(和嵌套列表理解可能会更好。)

我对在算法的渐近运行时间中实现巨大飞跃并不是很感兴趣(例如,使用后缀树或其他任何复杂和聪明的东西)。我更关注常量(我只需要为几对AB集合执行此操作,并且我不希望它在整周运行。)

您是否知道任何技巧或有任何通用建议可以更快地完成此任务?非常感谢您可以分享的任何见解!


修改

使用@ninjagecko和@Sven Marnach的建议,我建立了一个10-mer的快速前缀表:

    import collections
    prefix_table = collections.defaultdict(set)
    for k, b in enumerate(B):
        for i in xrange(len(prot_seq)-10):
            j = i+10+1
            prefix_table[b[i:j]].add(k)

    for a in A:
        if len(a) >= 10:
            for k in prefix_table[a[:10]]:
                # check if a is in b
                # (missing_edges is necessary, but not sufficient)
                if a in B[k]:
                    print (a,b)
        else:
            for k in xrange(len(prots_and_seqs)):
                # a is too small to use the table; check if
                # a is in any b
                if a in B[k]:
                    print (a, b)

4 个答案:

答案 0 :(得分:10)

当然,您可以轻松地将其写为列表理解:

[(a, b) for a in A for b in B if a in b]

这可能会略微加快循环速度,但不要期望太多。我怀疑使用正则表达式会对此有任何帮助。

修改:以下是一些时间安排:

import itertools
import timeit
import re
import collections

with open("/usr/share/dict/british-english") as f:
    A = [s.strip() for s in itertools.islice(f, 28000, 30000)]
    B = [s.strip() for s in itertools.islice(f, 23000, 25000)]

def f():
    result = []
    for a in A:
        for b in B:
            if a in b:
                result.append((a, b))
    return result

def g():
    return [(a, b) for a in A for b in B if a in b]

def h():
    res = [re.compile(re.escape(a)) for a in A]
    return [(a, b) for a in res for b in B if a.search(b)]

def ninjagecko():
    d = collections.defaultdict(set)
    for k, b in enumerate(B):
        for i, j in itertools.combinations(range(len(b) + 1), 2):
            d[b[i:j]].add(k)
    return [(a, B[k]) for a in A for k in d[a]]

print "Nested loop", timeit.repeat(f, number=1)
print "List comprehension", timeit.repeat(g, number=1)
print "Regular expressions", timeit.repeat(h, number=1)
print "ninjagecko", timeit.repeat(ninjagecko, number=1)

结果:

Nested loop [0.3641810417175293, 0.36279606819152832, 0.36295199394226074]
List comprehension [0.362030029296875, 0.36148500442504883, 0.36158299446105957]
Regular expressions [1.6498990058898926, 1.6494300365447998, 1.6480278968811035]
ninjagecko [0.06402897834777832, 0.063711881637573242, 0.06389307975769043]

修改2 :为时间添加了alogrithm suggested by ninjagecko的变体。你可以看到它比所有蛮力方法都要好得多。

编辑3:使用已设置而不是列表来消除重复项。 (我没有更新时间 - 它们基本保持不变。)

答案 1 :(得分:7)

让我们假设你的单词有一个合理的大小(比方说10个字母)。执行以下操作以实现线性(!)时间复杂度,即O(A+B)

  • 初始化哈希表或特里
  • 对于B中的每个字符串b:
    • 对于该字符串的每个子字符串
      • 将子字符串添加到hashtable / trie(这不比55*O(B) = O(B)更差),其中包含属于哪个字符串的元数据
  • 对于A中的每个字符串a:
    • 对哈希表/ trie执行O(1)查询以找到它所在的所有B字符串,产生那些

(截至写这个答案时,如果OP的“单词”有界,则没有任何反应。如果它们没有界限,这个解决方案仍然适用,但是存在O(maxwordsize^2)的依赖,但实际上它实际上更好并非所有单词都具有相同的大小,因此它可能与具有正确分布的O(averagewordsize^2)一样好。例如,如果所有单词的大小均为20,则问题大小将比它们增加4倍大小为10但是如果从10-> 20的大小增加了很少的单词,那么复杂性就不会有太大变化。)

编辑 https://stackoverflow.com/q/8289199/711085实际上是一个理论上更好的答案。在发布该答案之前,我正在查看链接的维基百科页面,并且正在考虑“字符串大小中的线性不是您想要的”,并且后来才意识到它正是您想要的。你建立正则表达式(Aword1|Aword2|Aword3|...)的直觉是正确的,因为在幕后生成的有限自动机将快速执行匹配,如果它支持同时重叠匹配,这并非所有正则表达式引擎都可能。最终你应该使用的取决于你是否计划重复使用As或Bs,或者这只是一次性的事情。上述技术更容易实现,但仅在您的单词有界时才有效(如果您不拒绝超过特定大小限制的单词,则会引入DoS漏洞),但如果您不想要,可能就是您正在寻找的Aho-Corasick string matching finite automaton或类似的,或者作为库不可用。

答案 2 :(得分:6)

搜索大量字符串的一种非常快速的方法是使用有限自动机(因此你对regexp的猜测并不是那么远),即Aho Corasick string matching机器,用于 grep 病毒扫描程序等工具。

首先,它将您要搜索的字符串(在您的情况下是A中的单词)编译为具有失败功能的有限状态自动机(如果您对细节感兴趣,请参阅'paper来自'75)。然后,此自动机读取输入字符串并输出所有找到的搜索字符串(可能您想稍微修改它,以便输出搜索字符串所在的字符串)。

此方法的优点是它可以同时搜索所有搜索字符串,因此只需要查看输入字符串的每个字符一次(线性复杂度)!

implementations of the aho corasick pattern matcher at pypi,但我没有对它们进行过测试,因此我无法对这些实现的性能,可用性或正确性做任何说明。


编辑:我尝试了this Aho-Corasick自动机的实现,它确实是迄今为止建议方法中最快的,也很容易使用:

import pyahocorasick

def aho(A, B):
    t = pyahocorasick.Trie();
    for a in A:
        t.add_word(a, a)
    t.make_automaton()
    return [(s,b) for b in B for (i,res) in t.iter(b) for s in res]

我观察到的一件事是,在使用@SvenMarnachs脚本测试此实现时,它比其他方法产生的结果略少,我不知道为什么。我给创作者写了一封邮件,也许他会搞清楚。

答案 3 :(得分:0)

这有专门的索引结构,例如参见   http://en.wikipedia.org/wiki/Suffix_tree

你要为B构建一个后缀树或类似的东西,然后用A来查询它。