我有一个可能子字符串的列表,例如['cat', 'fish', 'dog']
。在实践中,此列表包含数百个条目。
我正在处理一个字符串,我要找到的是任何这些子字符串第一次出现的索引。
为了澄清,对于'012cat'
,结果是3,对于'0123dog789cat'
,结果是4。
我还需要知道找到了哪个子字符串(例如其在子字符串列表中的索引或文本本身),或者至少匹配的子字符串长度。
有明显的暴力方法来实现这一点,我想知道是否有任何优雅的Python /正则表达式解决方案。
我有一个可能子字符串的列表,例如['cat', 'fish', 'dog']
。在实践中,此列表包含数百个条目。
我正在处理一个字符串,我要找到的是任何这些子字符串第一次出现的索引。
为了澄清,对于'012cat'
,结果是3,对于'0123dog789cat'
,结果是4。
我还需要知道找到了哪个子字符串(例如其在子字符串列表中的索引或文本本身),或者至少匹配的子字符串长度。
有明显的暴力方法来实现这一点,我想知道是否有任何优雅的Python /正则表达式解决方案。
import re
def work():
to_find = re.compile("cat|fish|dog")
search_str = "blah fish cat dog haha"
match_obj = to_find.search(search_str)
the_index = match_obj.start() # produces 5, the index of fish
which_word_matched = match_obj.group() # "fish"
# Note, if no match, match_obj is None
更新:在将单词组合成单个替代单词模式时,应该注意一些细节。以下代码构建了一个正则表达式,但是转义了任何正则表达式特殊字符并对单词进行排序,以便更长的单词在匹配任何相同单词的较短前缀之前有机会匹配:
def wordlist_to_regex(words):
escaped = map(re.escape, words)
combined = '|'.join(sorted(escaped, key=len, reverse=True))
return re.compile(combined)
>>> r.search('smash atomic particles').span()
(6, 10)
>>> r.search('visit usenet:comp.lang.python today').span()
(13, 29)
>>> r.search('a north\south division').span()
(2, 13)
>>> r.search('012cat').span()
(3, 6)
>>> r.search('0123dog789cat').span()
(4, 7)
更新结束
需要注意的是,您应该尽可能少地形成正则表达式(即 - 调用re.compile())。最好的情况是您提前知道您的搜索内容是什么(或者您计算它们一次/不经常),然后将re.compile()的结果保存在某个地方。我的示例只是一个简单的无意义函数,以便您可以看到正则表达式的用法。这里有更多的正则表达式文档:
http://docs.python.org/library/re.html
希望这能帮到你。
更新: 我不确定 Python 如何实现正则表达式,但是为了回答 Rax 的问题,即 re.compile() 是否有限制(例如,一次尝试匹配多少个单词),以及编译需要的时间:这两者似乎都不是问题。我尝试了这段代码,它已经足够让我相信。(我可以通过添加计时和报告结果,以及将单词列表放入集合中以确保没有重复项来改进它...但这两个改进似乎过度设计了)。这段代码运行非常快,让我相信我能够搜索 2000 个大小为 10 的单词,并且它们都会被适当地匹配。以下是代码:
import random
import re
import string
import sys
def main(args):
words = []
letters_and_digits = "%s%s" % (string.letters, string.digits)
for i in range(2000):
chars = []
for j in range(10):
chars.append(random.choice(letters_and_digits))
words.append(("%s"*10) % tuple(chars))
search_for = re.compile("|".join(words))
first, middle, last = words[0], words[len(words) / 2], words[-1]
search_string = "%s, %s, %s" % (last, middle, first)
match_obj = search_for.search(search_string)
if match_obj is None:
print "Ahhhg"
return
index = match_obj.start()
which = match_obj.group()
if index != 0:
print "ahhhg"
return
if words[-1] != which:
print "ahhg"
return
print "success!!! Generated 2000 random words, compiled re, and was able to perform matches."
if __name__ == "__main__":
main(sys.argv)
更新: 需要注意的是正则表达式中OR操作符连接的顺序很重要。看看以下测试,它受到了TZOTZIOY的启发:
>>> search_str = "01catdog"
>>> test1 = re.compile("cat|catdog")
>>> match1 = test1.search(search_str)
>>> match1.group()
'cat'
>>> match1.start()
2
>>> test2 = re.compile("catdog|cat") # reverse order
>>> match2 = test2.search(search_str)
>>> match2.group()
'catdog'
>>> match2.start()
2
subs = ['cat', 'fish', 'dog']
sentences = ['0123dog789cat']
import re
subs = re.compile("|".join(subs))
def search():
for sentence in sentences:
result = subs.search(sentence)
if result != None:
return (result.group(), result.span()[0])
# ('dog', 4)
我想指出DisplacedAussie和Tom回答之间的时间差异。两个回答都很快,使用一次不应该有明显等待时间,但当你计时它们:
import random
import re
import string
words = []
letters_and_digits = "%s%s" % (string.letters, string.digits)
for i in range(2000):
chars = []
for j in range(10):
chars.append(random.choice(letters_and_digits))
words.append(("%s"*10) % tuple(chars))
search_for = re.compile("|".join(words))
first, middle, last = words[0], words[len(words) / 2], words[-1]
search_string = "%s, %s, %s" % (last, middle, first)
def _search():
match_obj = search_for.search(search_string)
# Note, if no match, match_obj is None
if match_obj is not None:
return (match_obj.start(), match_obj.group())
def _map():
search_for = search_for.pattern.split("|")
found = map(lambda x: (search_string.index(x), x), filter(lambda x: x in search_string, search_for))
if found:
return min(found, key=lambda x: x[0])
if __name__ == '__main__':
from timeit import Timer
t = Timer("_search(search_for, search_string)", "from __main__ import _search, search_for, search_string")
print _search(search_for, search_string)
print t.timeit()
t = Timer("_map(search_for, search_string)", "from __main__ import _map, search_for, search_string")
print _map(search_for, search_string)
print t.timeit()
输出:
(0, '841EzpjttV')
14.3660159111
(0, '841EzpjttV')
# I couldn't wait this long
'a'
开头,则添加一个 'a'
节点,如果任何子字符串以 'b'
开头,则添加一个 'b'
节点,依此类推。对于这些节点中的每一个,都要继续添加子节点。'a'
、一个孙子节点 'n'
和一个曾孙节点 't'
。class Node(object):
children = []
def __init__(self, name):
self.name = name
其中name
是一个字符。
逐个遍历您的字符串中的每个字母,并跟踪您所处的字母。在每个字母处,尝试使用接下来的几个字母来遍历树。如果成功,您的字母编号将是子字符串的位置,您的遍历顺序将指示找到的子字符串。
澄清编辑:DFA应该比这种方法快得多,因此我应该支持Tom的答案。 我只保留此答案,以防您的子字符串列表经常更改,在这种情况下,使用树可能更快。
这个怎么样?
>>> substrings = ['cat', 'fish', 'dog']
>>> _string = '0123dog789cat'
>>> found = map(lambda x: (_string.index(x), x), filter(lambda x: x in _string, substrings))
[(10, 'cat'), (4, 'dog')]
>>> if found:
>>> min(found, key=lambda x: x[0])
(4, 'dog')
显然,你可以返回除元组以外的其他东西。
这个方法的实现方式是:
首先,我建议您按升序对初始列表进行排序。因为扫描较短的子字符串比扫描较长的子字符串更快。