我有一个0和1的线性列表,我需要匹配多个简单模式并找到第一个匹配项。例如,我可能需要在长度为800万的列表中找到0001101101
,01010100100
或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(与某些算法不同 预处理要搜索的字符串 然后可以摊还费用 通过搜索进行预处理 重复地)。
这看起来很有趣,但它没有提供任何此类算法的例子。这样的事情也有帮助吗?
答案 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)
可以高效的解决方案:
pattern_length
字符是否在trie中,停止成功(O(1)操作)如果列表不可变,您可以存储匹配模式的偏移量,以避免下次重复计算。
答案 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秒。