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

时间:2019-02-01 14:09:58

标签: python regex python-3.x string substring

我正在使用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”]。尽管如此,此解决方案的计算时间过长。我必须对大约一百万个字符串进行此检查,而使用正则表达式的解决方案将需要几天才能完成,而使用第一个解决方案则需要1.5个小时。

我想知道是否有一种方法可以使第一个解决方案工作,或者使第二个解决方案运行得更快。预先感谢

编辑:value也可以包含数字或简短的短语,例如“ test1 test2”

3 个答案:

答案 0 :(得分:3)

很难在不查看实际数据的情况下提出最佳解决方案,但是您可以尝试以下操作:

  • 生成匹配所有值的单个模式。这样,您只需要搜索一次字符串(而不是每个值一次)。
  • 跳过转义值,除非它们包含特殊字符(如'^''*')。
  • 将结果直接分配给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']

答案 1 :(得分: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中出现的顺序排列。
  • 如果同一值在temp中出现多次,则它将在str中出现多次。与您原来的方法相反,该值最多在temp中出现一次。
  • search一旦找到匹配项即终止。 findall始终搜索整个字符串。如果您期望大多数字符串与value中的每个单词匹配,并且期望大多数匹配项早于字符串出现,那么findall可能比search慢。另一方面,如果您希望搜索经常增加None,则findall可能会更快一些。

答案 2 :(得分:0)

您可以按空格分隔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?']

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

我希望现在更清楚