我有一个大字符串和大量较小的子字符串,我试图检查每个子字符串是否存在于较大的字符串中并获取每个子字符串的位置。
string="some large text here"
sub_strings=["some", "text"]
for each_sub_string in sub_strings:
if each_sub_string in string:
print each_sub_string, string.index(each_sub_string)
问题是,因为我有大量的子串(大约一百万个),所以需要大约一个小时的处理时间。有没有办法减少这个时间,可能是通过使用正则表达式或其他方式?
答案 0 :(得分:4)
解决此问题的最佳方法是使用树实现。正如里沙夫所说,你在这里重复了很多工作。理想情况下,这应该实现为基于树的FSM。想象一下以下示例:
Large String: 'The cat sat on the mat, it was great'
Small Strings: ['cat', 'sat', 'ca']
然后想象一棵树,每个级别都是一个额外的字母。
small_lookup = {
'c':
['a', {
'a': ['t']
}], {
's':
['at']
}
}
粗略格式化的道歉,但我认为直接映射回python数据结构会很有帮助。您可以构建一个树,其中顶级条目是起始字母,并且它们映射到可以完成的潜在最终子字符串列表。如果你点击了一个列表元素并且没有更多嵌套在你的下面你已经击中了一片叶子而你知道你已经击中了那个子串的第一个实例。
将该树保存在内存中有点大,但如果你只有一百万字符串,这应该是最有效的实现。您还应该确保在找到第一个单词实例时修剪树。
对于那些有CS chops的人,或者如果你想了解更多关于这种方法的知识,它是Aho-Corasick string matching algorithm的简化版本。
如果您有兴趣了解这些方法的更多信息,实践中使用了三种主要算法:
在某些领域中,所有这些算法都会胜过其他算法,但基于这样一个事实,即您搜索的子字符串数量非常多,并且它们之间可能存在很多重叠我敢打赌Aho-Corasick会比其他两种方法给你带来更好的性能,因为它避免了O(mn)
最坏情况
还有一个很棒的python库,它实现了Aho-Corasick
找到的here算法,可以让你避免自己编写粗略的实现细节。
答案 1 :(得分:2)
根据子串长度的分布,您可以使用预处理来节省大量时间。
假设子串的长度集合形成集合 {23,33,45} (意味着您可能有数百万个子字符串,但每个子字符串都采用这三个长度中的一个)。
然后,对于每个长度,在大字符串上找到Rabin Window,并将结果放入该长度的字典中。也就是说,让我们走23.转过大字符串,找到23窗口的哈希值。假设位置0的哈希是13.所以你插入到字典rabin23
中,13被映射到[0]
。然后你会看到,对于位置1,哈希值也是13。然后在rabin23
中,更新13将映射到[0, 1]
。然后在位置2,哈希值为4.因此在rabin23
中,4被映射到[2]。
现在,给定一个子字符串,您可以计算其Rabin哈希值,并立即检查相关字典中的出现指数(然后您需要比较)。
顺便说一句,在许多情况下,你的子串的长度将表现出帕累托行为,其中90%的字符串是10%的长度。如果是这样,您只能为这些长度执行此操作。答案 2 :(得分:0)
与其他答案相比,这种方法是次优的,但无论如何都可能足够好,并且易于实施。我们的想法是改变算法,以便不是依次针对较大的字符串测试每个子字符串,而是迭代大字符串并在每个位置测试可能匹配的子字符串,使用字典来缩小数量。你需要测试的子字符串。
输出与原始代码的不同之处在于它将按索引的升序排序而不是按子字符串排序,但是如果您愿意,可以对输出进行后处理以按字符串排序。 / p>
创建一个包含每个可能1-3个字符的子字符串列表的字典。然后迭代字符串,在每个字符后面读取1-3个字符,并检查字符中每个以1-3个字符开头的子字符串在该位置的匹配:
string="some large text here"
sub_strings=["some", "text"]
# add each of the substrings to a dictionary based the first 1-3 characters
dict = {}
for s in sub_strings:
if s[0:3] in dict:
dict[s[0:3]].append(s)
else:
dict[s[0:3]] = [s];
# iterate over the chars in string, testing words that match on first 1-3 chars
for i in range(0, len(string)):
for j in range(1,4):
char = string[i:i+j]
if char in dict:
for word in dict[char]:
if string[i:i+len(word)] == word:
print word, i
如果您不需要匹配任何长度为1或2个字符的子字符串,那么您可以摆脱for j
循环并只使用char = string[i:3]
使用第二种方法,我通过阅读Tolstoy的War and Peace并将其分成独特的单词来计算算法,如下所示:
with open ("warandpeace.txt", "r") as textfile:
string=textfile.read().replace('\n', '')
sub_strings=list(set(string.split()))
对文本中的每个唯一单词进行完整搜索并输出每个单词的每个实例需要124秒。