有没有办法在正则表达式中以任意顺序匹配一组分组?

0 投票
2 回答
2249 浏览
提问于 2025-04-16 03:28

我看了一下相关的问题,发现有不少,但我觉得没有一个能回答我的问题。我对正则表达式(Regex)还很陌生,但我想学得更好,所以请耐心点。我想在一个字符串中匹配几个组,但顺序可以不固定。这种情况我应该用正则表达式吗?如果可以的话,应该怎么做?如果有关系的话,我打算在IronPython中使用这些。

补充说明:有人让我更具体一点,所以这里是详细信息:

我想用 re.match 和一个像这样的正则表达式:

\[image\s*(?(@alt:(?<alt>.*?);).*(@title:(?<title>.*?);))*.*\](?<arg>.*?)\[\/image\]

但是它只会在这些命名组按正确顺序并且用空格分开的情况下匹配。我希望能在任何顺序下匹配这些命名组,只要它们出现在正则表达式中现在的位置。

一个典型的字符串可能是这样的:

[image @alt:alien; @title:reddit alien;]http://www.reddit.com/alien.png[/image]

但我应该也能匹配:

[image @title:reddit alien; @alt:alien;]http://www.reddit.com/alien.png[/image]

所以这些“属性”(在第一个“标签”中,@和;之间的内容)应该能以任何顺序匹配,只要它们都出现。

2 个回答

2

你标题中的问题答案是“不”——要匹配N组“无论顺序如何”,正则表达式中需要有一个“或”符号(|),这意味着你需要考虑所有可能的N组排列组合,总共有(N的阶乘)种可能性。这个数字增长得非常快——比如N等于6时,已经是720,N等于7时,几乎是5000,之后的增长速度更是让人眼花缭乱——所以这种方法对于N不小的情况来说完全不现实。

解决方案可能有很多,具体取决于你希望用什么来分隔这些组。假设,比如说,你不在乎分隔符是什么(如果你在乎,请在问题中提供更详细的说明)。

在这种情况下,如果重叠匹配不可能或者你可以接受重叠,可以为每个组创建N个独立的正则表达式——假设这N个编译好的正则表达式对象放在一个名为grps的列表中,那么

mos = [g.search(thestring) for g in grps]

就是这些组的匹配对象列表(对于没有匹配的组,值为None)。通过mos列表,你可以进行各种检查和进一步的操作,比如all(mos)只有在所有组都匹配时才会返回True,在这种情况下,[m.group() for m in mos]就是匹配到的子字符串列表,等等。

如果你需要不重叠的匹配,那就稍微复杂一些——你可能需要提取每个组所有可能匹配的边界,然后看看是否能从这个列表中提取出个区间,每个列表一个,这样它们之间就不会有交集。这是一个相对复杂的算法(当然,如果你希望在大的情况下有合理的速度),所以我觉得这值得单独提问,而且在这里讨论是否需要这个算法也没有意义,因为这取决于你没有说明的很多因素。

所以,请先修改你的问题,提供更准确的说明,然后我们或许可以更清楚地为你提供所需的代码和/或算法。

编辑:我看到提问者现在至少澄清了提供示例的范围——不过,令人困惑的是,他提供了一个正则表达式模式示例和一个应该匹配的字符串示例,无论顺序如何(正则表达式指定了一个子字符串@title,而示例字符串中没有这个子字符串——真让人费解!)。

无论如何,如果示例中的组数(两个看起来可以互换,一个似乎必须出现在特定位置)代表了提问者实际的问题,那么感兴趣的排列总数只有两个,因此用一个竖线|将这“两个”排列连接起来当然是可行的。不过,提问者的实际问题真的是这样吗……?

编辑:如果感兴趣的排列数量很小,这里有一个避免在模式中重复组名问题的方法示例(语法要求Python 2.7或更高版本,但这只是为了最后的“字典推导”,在许多早期版本的Python中也有相同的功能,只是语法不那么优雅dict(('a', ...;-)...:

>>> r = re.compile(r'(?P<a1>a.*?a).*?(?P<b1>b.*?b)|(?P<b2>b.*?b).*?(?P<a2>a.*?a)')
>>> m = r.search('zzzakkkavvvbxxxbnnn')
>>> g = m.groupdict()
>>> d = {'a':(g.get('a1') or g.get('a2')), 'b':(g.get('b1') or g.get('b2'))}
>>> d
{'a': 'akkka', 'b': 'bxxxb'}
0

这跟用正则表达式解析HTML时遇到的一个大问题很像——属性的顺序并不固定,而且很多标签有些意想不到的属性(比如 <br clear="all">)。所以你在处理的标记语法也差不多。

Pyparsing 以一种间接的方式解决了这个问题——它不是试图解析所有可能的排列组合,而是解析一种通用的 "@attrname:属性值;" 语法,并在一个属性映射的数据结构中跟踪属性的键和值。这个映射让你可以轻松获取“title”属性,无论它是在图片标签的最前面还是最后面。这个功能已经内置在pyparsing的API方法中,比如makeHTMLTags和makeXMLTags。

当然,这种标记并不是XML,但类似的方法可以得到一些相对容易处理的结果:

text = """[image @alt:alien; @title:reddit alien;]http://www.reddit.com/alien1.png[/image]

But I should have no problem matching:

[image @title:reddit alien; @alt:alien;]http://www.reddit.com/alien2.png[/image]
"""

from pyparsing import Suppress, Group, Word, alphas, SkipTo, Dict, ZeroOrMore

LBRACK,RBRACK,COLON,SEMI,AT = map(Suppress,"[]:;@")
tagAttribute = Group(AT + Word(alphas) + COLON + SkipTo(SEMI) + SEMI)
imageTag = LBRACK + "image" + Dict(ZeroOrMore(tagAttribute)) + RBRACK
imageLink = imageTag + SkipTo("[/image]")("text")

for taginfo in imageLink.searchString(text):
    print taginfo.alt
    print taginfo.title
    print taginfo.text
    print

输出结果:

alien
reddit alien
http://www.reddit.com/alien1.png

alien
reddit alien
http://www.reddit.com/alien2.png

撰写回答