如何在Python正则表达式中匹配字符串列表中的任意字符串?

43

假设我有一个字符串列表,

string_lst = ['fun', 'dum', 'sun', 'gum']

我想创建一个正则表达式,在其中的某个位置,我可以匹配列表中包含的任何字符串,在一个组内,例如:

import re
template = re.compile(r".*(elem for elem in string_lst).*")
template.match("I love to have fun.")

应该如何正确地做到这一点?还是必须制作多个正则表达式并将它们全部分别匹配到字符串中?


1
使用|作为粘合剂将数组元素连接起来,将形成字符串fun|dum|sun|gum,可以在正则表达式中使用。 - Tushar
7
re.search('|'.join(string_lst), input_string) - Avinash Raj
any(z in string_list for z in re.findall(r"['\w]+", 'This is just for fun')) - Burhan Khalid
你关心找到哪个字符串,还是只要找到任何一个就可以? - Burhan Khalid
答案是可以的,但不是最优的。你的问题是想要自动找到正则表达式r"[fs]un|[dg]u[m]"吗? 这是一个非常有趣的问题,顺便说一下,它是诸如音韵学等领域的基础,但我需要知道你是否打算解决这个问题,以及是否可以假设相似长度或至少在插入、删除和替换之间设置一些权衡,在什么条件下正则表达式是最小的,这些方面的事情。 - Veltzer Doron
5个回答

56

使用管道符号|将项目添加到列表中,该符号代表正则表达式中的不同选项。

string_lst = ['fun', 'dum', 'sun', 'gum']
x="I love to have fun."

print re.findall(r"(?=("+'|'.join(string_lst)+r"))", x)

输出结果:['fun']

你不能使用match,因为它将从开头开始匹配。 使用search只会得到第一个匹配项。因此,请改用findall

如果有重叠的匹配项不从同一点开始,请使用lookahead


2
但是如果存在像“funny”这样的单词,则会返回“['fun']”。 - Marlon Abeykoon
1
哦,不错。re.findall(r"(?=\b("+'|'.join(string_lst)+r")\b)",x) 对我有效。 - Marlon Abeykoon
1
这种方法是正确的,但无法完成全部任务。它将匹配给定字符串中列表单词的每个出现,甚至在其他具有类似单词部分的单词中也会匹配。例如,请尝试提供 x =“我喜欢有趣”并进行检查。适当的原始格式应为:print(re.findall(r"(?=(\b" + '|'.join(string_lst) + r"\b))", x)) - Pranzell
@Pranzell,我已经删除了你的编辑。请在现有答案下面添加你的答案,并说明更好的条件 :) - vks

25

regex模块命名列表(实际上是集合):

#!/usr/bin/env python
import regex as re # $ pip install regex

p = re.compile(r"\L<words>", words=['fun', 'dum', 'sun', 'gum'])
if p.search("I love to have fun."):
    print('matched')

这里的words只是一个名称,你可以使用任何你喜欢的名字代替。
在命名列表前/后使用.search()方法而不是.*

要使用stdlib的re模块模拟命名列表:

#!/usr/bin/env python
import re

words = ['fun', 'dum', 'sun', 'gum']
longest_first = sorted(words, key=len, reverse=True)
p = re.compile(r'(?:{})'.format('|'.join(map(re.escape, longest_first))))
if p.search("I love to have fun."):
    print('matched')

re.escape() 用于转义正则表达式元字符,例如在单词中的 .*? (以实现文字匹配)。
sorted() 模拟了 regex 的行为,并将最长的单词放在替代方案中的第一位,比较如下:

>>> import re
>>> re.findall("(funny|fun)", "it is funny")
['funny']
>>> re.findall("(fun|funny)", "it is funny")
['fun']
>>> import regex
>>> regex.findall(r"\L<words>", "it is funny", words=['fun', 'funny'])
['funny']
>>> regex.findall(r"\L<words>", "it is funny", words=['funny', 'fun'])
['funny']

1
你可以补充说明它解决了 a|b|c|d ... 方法(线性搜索)的复杂性问题。 - Jean-François Fabre
@Jean-FrançoisFabre 我不确定是否有区别(两个接口都可以编译成相同的输入算法线性时间(实际实现可能会有所不同--在这种情况下,如果对您的输入很重要,请进行基准测试))。 - jfs

5

除了正则表达式,您还可以使用列表推导式,希望不会偏离主题。

import re
def match(input_string, string_list):
    words = re.findall(r'\w+', input_string)
    return [word for word in words if word in string_list]

>>> string_lst = ['fun', 'dum', 'sun', 'gum']
>>> match("I love to have fun.", string_lst)
['fun']

5

在将字符串组合成正则表达式之前,您应该确保正确转义字符串。

>>> import re
>>> string_lst = ['fun', 'dum', 'sun', 'gum']
>>> x = "I love to have fun."
>>> regex = re.compile("(?=(" + "|".join(map(re.escape, string_lst)) + "))")
>>> re.findall(regex, x)
['fun']

有没有办法在这里使用re.search而不是re.findall。我尝试使用re.search,但输出结果很糟糕:<re.Match object; span=(15, 15), match=''>。 - ZZZ

3

与@vks的回复一致-我认为这实际上完成了整个任务...

finds = re.findall(r"(?=(\b" + '\\b|\\b'.join(string_lst) + r"\b))", x)

加入单词边界即可完成任务!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接