Python:匹配字符串中的多个子字符串

Posted

技术标签:

【中文标题】Python:匹配字符串中的多个子字符串【英文标题】:Python: match multiple substrings in a string 【发布时间】:2019-06-26 03:08:38 【问题描述】:

我正在使用 Python,我想将给定的字符串与多个子字符串匹配。我试图以两种不同的方式解决这个问题。我的第一个解决方案是将子字符串与以下字符串匹配:

str = "This is a test string from which I want to match multiple substrings"
value = ["test", "match", "multiple", "ring"]
temp = []
temp.extend([x.upper() for x in value if x.lower() in str.lower()])
print(temp)

导致 temp = ["TEST", "MATCH", "MULTIPLE", "RING"]

但是,这不是我想要的结果。子字符串应该完全匹配,所以“ring”不应该与“string”匹配。

这就是我尝试用正则表达式解决这个问题的原因,如下所示:

str = "This is a test string from which I want to match multiple substrings"
value = ["test", "match", "multiple", "ring"]
temp = []
temp.extend([x.upper() for x in value if regex.search(r"\b" + regex.escape(x) + r"\b", str,
                                                   regex.IGNORECASE) is not None])
print(temp)

这导致 ["TEST", "MATCH", "MULTIPLE"] 正确的解决方案。尽管如此,这个解决方案的计算时间太长了。我必须对大约 100 万个字符串进行此检查,与使用第一个解决方案需要 1.5 小时相比,使用正则表达式的解决方案需要几天时间才能完成。

我想知道是否有办法让第一个解决方案运行起来,或者让第二个解决方案运行得更快。提前致谢

编辑:value 也可以包含数字,或者像“test1 test2”这样的短语

【问题讨论】:

您可以通过编译解决方案并在数百万个字符串上运行编译版本来节省大量时间 @jeremycg “编译你的解决方案”到底是什么意思? but this does not work when value contains substrings like "test1 test2"。那么如果 value 有一个单词包含在 str 中,那么会有匹配项吗? 使用@Kevin 在他的回答中提到的re.compile 【参考方案1】:

在没有看到实际数据的情况下很难提出最佳解决方案,但您可以尝试以下方法:

生成一个匹配所有值的单一模式。这样,您只需搜索字符串一次(而不是每个值一次)。 跳过转义值,除非它们包含特殊字符(如 '^''*')。 将结果直接分配给temp,避免使用temp.extend()进行不必要的复制。
import regex

# 'str' is a built-in name, so use 'string' instead
string = 'This is a Test string from which I want to match multiple substrings'
values = ['test', 'test2', 'Multiple', 'ring', 'match']
pattern = r'\b()\b'.format('|'.join(map(regex.escape, values)))

# unique matches, lowercased
matches = set(map(str.lower, regex.findall(pattern, string, regex.IGNORECASE)))

# arrange the results as they appear in `values`
temp = [x.upper() for x in values if x.lower() in matches]
print(temp)  # ['TEST', 'MULTIPLE', 'MATCH']

【讨论】:

感谢您的回答。它帮助我解决了我的问题。我只需要将if x in matches 调整为if x.lower() in matches 并为string 做同样的事情以不区分大小写。我还使用了temp.extend(),因为在进行匹配之前,我首先在temp 上附加了一些内容。 我已经使用这段代码很长一段时间了,但不幸的是我发现了一个问题。如果我有string = "test1 test2"values = ['test1', 'test1 test2'],它需要匹配这两个实例。现在我只得到第一场比赛。你知道我该如何解决这个问题吗? @jv3768 不幸的是,单次搜索是不可能的,因为正则表达式引擎会返回第一个有效匹配项而跳过其他替代项。您可以尝试分析作为其他值的子字符串的输入和过滤值,然后将它们包含在结果中。【参考方案2】:

想到了两种可能的优化:

使用re.compile 预编译模式,因此它不会在您每次调用match 时重新编译。 与其匹配四个独立的正则表达式,不如创建一个匹配所有值的正则表达式。

 

import re

str = "This is a test string from which I want to match test1 test2 multiple substrings"
values = ["test", "match", "multiple", "ring", "test1 test2"]

pattern = re.compile("|".join(r"\b" + re.escape(x) + r"\b" for x in values))
temp = []

temp.extend([x.upper() for x in pattern.findall(str, re.IGNORECASE)])
print(temp)

结果:

['TEST', 'MATCH', 'TEST1 TEST2', 'MULTIPLE']

这种方法的潜在缺点:

输出的顺序可能不同。您的原始方法将结果按照它们在values 中出现的顺序排列。这种方法将结果按照它们在str 中出现的顺序排列。 如果在str 中出现多次,相同的值将在temp 中出现多次。与您的原始方法相反,该值在 temp 中最多出现一次。 search 在找到匹配项后立即终止。 findall 总是搜索整个字符串。如果您希望大多数字符串匹配value 中的每个单词,并希望大多数匹配出现在字符串的早期,那么findall 可能比search 慢。另一方面,如果您希望搜索经常出现None,那么findall 可能会更快一些。

【讨论】:

'编译'模式在这里不会提供任何好处,因为您只使用一次。即使多次使用,difference 也很可能可以忽略不计。 我对实际问题的解释是必须搜索数百万个字符串,而不仅仅是示例代码中的那个。想象一下在我的temp = [] 后面有一个for str in millions_of_strings: 行。 感谢您的回答,但您的第一个潜在缺点是我的代码的真正缺点。我想将第一场比赛优先于所有其他比赛。我认为尤金的回答会解决我的问题 @jv3768 据我所知,尤金的回答也会有这个缺点,因为它也使用 findall。 @Kevin:可以根据value 中的 idx 对结果进行排序(我已经添加了该步骤)。【参考方案3】:

您可以将str 按空格分开,然后将value 中的元素与== 匹配

编辑:

所以你说values 中的某些字符串可以在它们之前或之后有空格。你可以用这行来解决这个问题:

values = [i.strip() for i in values]

这将删除字符串之前和之后的所有空白字符(在您的情况下为每个元素)。

此外,您提到如果将str 用空格分割,一些单词会在分割后留下标点符号-> 'Hi, how are you?' 将导致['Hi,', 'how', 'are', 'you?']。您可以通过使用字符串startswith() 内置方法来过滤以values 中的元素开头的所有单词来解决此问题,如下所示:

str = ['Hi,', 'how', 'are', 'you?']`
values = ['how', 'you', 'time', 'space']

new_str = []
for word in str:
  for j in values:
    if word.startswith(j):
      new_str.append(word)

# result -> ['how', 'you?']

然后您可以使用一些正则表达式从结果列表中删除标点符号,但现在您将有一个小得多的列表进行迭代。删除所有标点符号后,您可以按照我在原始答案中的建议匹配整个字符串。

我希望现在更清楚了。

【讨论】:

如果value 的元素包含空格,或者如果句子中包含标点符号的单词旁边会被匹配,则不起作用。 那么您可以使用字符串方法startswith() 执行value = [i.strip() for i in value] 并从str 过滤项目,然后从过滤列表中删除标点符号并查看结果 抱歉,我不确定我是否理解。你能在你的答案中编辑一些完整的代码并演示它是如何工作的吗? 感谢您的编辑,但我想我仍然缺少一些东西。当我运行该代码块时,我得到NameError: name 'i' is not defined “所以你说值中的某些字符串可以在它们之前或之后有空格。”我不认为这是他在说什么。我认为他的意思是价值观可以在其中有一个空间,不一定在开头或结尾。例如,“test1 test2”包含一个空格。删除它是错误的,因为“test1test2”不应该匹配,并且将它分成多个元素是错误的,因为“test1”不应该被匹配,除非它紧跟在一个空格之前,然后是“test2” .

以上是关于Python:匹配字符串中的多个子字符串的主要内容,如果未能解决你的问题,请参考以下文章

[在python中使用正则表达式搜索字符串子字符串

使用 Python 的字符串子序列内核和 SVM

PB中取字符串子串的函数是啥

PB中取字符串子串的函数是啥

数组篇在python中如何查找最长字符串子串

字符串匹配----后缀数组算法