线性模式匹配算法?

时间:2009-08-09 02:19:50

标签: algorithm language-agnostic pattern-matching

我有一个0和1的线性列表,我需要匹配多个简单模式并找到第一个匹配项。例如,我可能需要在长度为800万的列表中找到000110110101010100100或OR 10100100010。我只需要找到第一个出现的,然后返回它发生的索引。但是,对大型列表进行循环和访问可能很昂贵,而且我宁愿不要这么做太多次。

有没有比做

更快的方法
foreach (patterns) {
    for (i=0; i < listLength; i++)
        for(t=0; t < patternlength; t++)
            if( list[i+t] != pattern[t] ) {
                 break;
            }
            if( t == patternlength - 1 ) {
                 return i;  // pattern found!
            }
        }
    }
}

编辑: BTW,我已按照上面的伪代码实现了这个程序,性能还可以,但没什么了不起的。我估计我在处理器的单个核心上每秒处理大约600万比特。我正在使用它进行图像处理,它必须经过几千万像素的图像,所以每一点都有帮助。

编辑:如果不清楚,我正在处理一个数组,所以只有两种可能:ONE和ZERO。它是用C ++编写的。

编辑:感谢指向BM和KMP算法的指针。我注意到,在BM的维基百科页面上,它说

  

算法预处理目标   正在搜索的字符串(键)   for,但不是被搜索的字符串   in(与某些算法不同   预处理要搜索的字符串   然后可以摊还费用   通过搜索进行预处理   重复地)。

这看起来很有趣,但它没有提供任何此类算法的例子。这样的事情也有帮助吗?

7 个答案:

答案 0 :(得分:11)

Google搜索的关键是“多模式”字符串匹配。

早在1975年,Aho and Corasick发布了一个(线性时间)算法,该算法在fgrep的原始版本中使用。随后许多研究人员对该算法进行了改进。例如,Commentz-Walter (1979)将Aho&amp; Corasick与Boyer&Moore匹配相结合。 Baeza-Yates (1989)将AC与Boyer-Moore-Horspool变体相结合。 Wu and Manber (1994)做了类似的工作。

多模式匹配算法的AC线的替代方案是Rabin and Karp's算法。

我建议先阅读Aho-Corasick和Rabin-Karp维基百科页面,然后再决定这是否适用于你的情况。如果是这样,也许已经存在可用于您的语言/运行时的实现。

答案 1 :(得分:4)

答案 2 :(得分:2)

您可以构建SuffixArray并搜索运行时是疯狂的:O(长度(模式))。 但是......你必须构建那个数组。 当Text是静态的并且模式是动态的时,它是值得的。

答案 3 :(得分:1)

可以高效的解决方案:

  1. 将模式存储在trie数据结构
  2. 开始搜索列表
  3. 检查下一个pattern_length字符是否在trie中,停止成功(O(1)操作)
  4. 第一步,重复#3
  5. 如果列表不可变,您可以存储匹配模式的偏移量,以避免下次重复计算。

答案 4 :(得分:0)

如果您的字符串需要灵活,我还建议根据Mitch Wheat修改“The Boyer-Moore字符串搜索算法”。如果您的字符串不需要灵活,您应该能够折叠您的模式匹配更多。 Boyer-Moore的模型非常有效地搜索大量数据以匹配多个字符串中的一个。

雅各

答案 5 :(得分:0)

如果只是交替0和1,则将文本编码为运行。 n 0的运行是-n,n 1的运行是n。然后编码您的搜索字符串。然后创建一个使用编码字符串的搜索功能。

代码如下所示:

try:
    import psyco
    psyco.full()
except ImportError:
    pass

def encode(s):
    def calc_count(count, c):
        return count * (-1 if c == '0' else 1)
    result = []
    c = s[0]
    count = 1
    for i in range(1, len(s)):
        d = s[i]
        if d == c:
            count += 1
        else:
            result.append(calc_count(count, c))
            count = 1
            c = d
    result.append(calc_count(count, c))
    return result

def search(encoded_source, targets):
    def match(encoded_source, t, max_search_len, len_source):
        x = len(t)-1
        # Get the indexes of the longest segments and search them first
        most_restrictive = [bb[0] for bb in sorted(((i, abs(t[i])) for i in range(1,x)), key=lambda x: x[1], reverse=True)]
        # Align the signs of the source and target
        index = (0 if encoded_source[0] * t[0] > 0 else 1)
        unencoded_pos = sum(abs(c) for c in encoded_source[:index])
        start_t, end_t = abs(t[0]), abs(t[x])
        for i in range(index, len(encoded_source)-x, 2):
            if all(t[j] == encoded_source[j+i] for j in most_restrictive):
                encoded_start, encoded_end = abs(encoded_source[i]), abs(encoded_source[i+x])
                if start_t <= encoded_start and end_t <= encoded_end:
                    return unencoded_pos + (abs(encoded_source[i]) - start_t)
            unencoded_pos += abs(encoded_source[i]) + abs(encoded_source[i+1])
            if unencoded_pos > max_search_len:
                return len_source
        return len_source
    len_source = sum(abs(c) for c in encoded_source)
    i, found, target_index = len_source, None, -1
    for j, t in enumerate(targets):
        x = match(encoded_source, t, i, len_source)
        print "Match at: ", x
        if x < i:
            i, found, target_index = x, t, j
    return (i, found, target_index)


if __name__ == "__main__":
    import datetime
    def make_source_text(len):
        from random import randint
        item_len = 8
        item_count = 2**item_len
        table = ["".join("1" if (j & (1 << i)) else "0" for i in reversed(range(item_len))) for j in range(item_count)]
        return "".join(table[randint(0,item_count-1)] for _ in range(len//item_len))
    targets = ['0001101101'*2, '01010100100'*2, '10100100010'*2]
    encoded_targets = [encode(t) for t in targets]
    data_len = 10*1000*1000
    s = datetime.datetime.now()
    source_text = make_source_text(data_len)
    e = datetime.datetime.now()
    print "Make source text(length %d): " % data_len,  (e - s)
    s = datetime.datetime.now()
    encoded_source = encode(source_text)
    e = datetime.datetime.now()
    print "Encode source text: ", (e - s)

    s = datetime.datetime.now()
    (i, found, target_index) = search(encoded_source, encoded_targets)
    print (i, found, target_index)
    print "Target was: ", targets[target_index]
    print "Source matched here: ", source_text[i:i+len(targets[target_index])]
    e = datetime.datetime.now()
    print "Search time: ", (e - s)

在你提供的字符串的两倍长度上,找到1000万个字符中三个目标的最早匹配大约需要7秒钟。当然,由于我使用的是随机文本,因此每次运行都会有所不同。

psyco是一个用于在运行时优化代码的python模块。使用它,您可以获得出色的性能,并且可以将其估计为C / C ++性能的上限。这是最近的表现:

Make source text(length 10000000):  0:00:02.277000
Encode source text:  0:00:00.329000
Match at:  2517905
Match at:  494990
Match at:  450986
(450986, [1, -1, 1, -2, 1, -3, 1, -1, 1, -1, 1, -2, 1, -3, 1, -1], 2)
Target was:  1010010001010100100010
Source matched here:  1010010001010100100010
Search time:  0:00:04.325000

编码1000万个字符大约需要300毫秒,搜索三个编码字符串大约需要4秒。我不认为C / C ++中的编码时间会很长。

答案 6 :(得分:0)

如果它是一个阵列,我想做一个滚动的总和将是一个改进。如果pattern是长度n,则将第一个n位加起来,看它是否与模式的总和相匹配。始终存储总和的第一位。然后,对于每个下一位,从和中减去第一位并添加下一位,并查看总和是否与模式的总和相匹配。这样可以在模式上保存线性循环。

看起来BM算法并不像它看起来那么棒,因为在这里我只有两个可能的值,零和一,所以第一个表并没有帮助很多。第二个表可能有所帮助,但这意味着BMH几乎毫无价值。

编辑:在我睡眠不足的状态下,我无法理解BM,所以我只是实现了这个滚动总和(它非常简单),它使我的搜索速度提高了3倍。感谢无论谁提到“滚动哈希”。我现在可以在5.45秒(而且是单线程)中搜索321,750,000位的两个30位模式,而不是之前的17.3秒。

相关问题