正则表达式交替顺序

5

我设置了一个复杂的正则表达式,从文本页面中提取数据。但是出现了一些问题,备选项的顺序不符合我的预期。一个简单的例子如下:

((13th|(Executive |Residential)|((\w+) ){1,3})Floor)

简单来说,我想要获取楼层数、已知的具名楼层,或者在后备情况下获取跟着1-3个未知单词的“floor”,以便稍后查看(实际上我使用一个组名来标识这个,但是我不想让事情变得混乱)。
问题在于如果字符串是:
on the 13th Floor

我不理解13楼,但我理解的是在第13层楼,这似乎表明它匹配了第三个选择。我本来期望它能匹配到13楼。我特意设置了这个规则(或者我以为是这样),以便优先匹配某些类型,只有当其他匹配失败时才留下模糊的匹配。我想他们说正则表达式很贪婪,这一点并不是开玩笑,但我不清楚如何设置它才能变得“贪婪”,并按照我想要的方式运行。

2个回答

5

一个自动机胜过千言万语:

正则表达式可视化

试一试

你的问题在于你在交替中使用了贪婪的 \w+ 子正则表达式。因为正如 @rigderunner 在他的评论中所述,NFA 匹配最长最左侧的子串,\w+ 将总是匹配在 Floor 之前出现的任何内容,无论它是一系列单词,还是 13thExecutiveResidential 或者这三个单词。括号不会改变交替的行为。

所以最糟糕的情况是它匹配了你不想匹配的部分:

xxxx yyyy zzz tttt Floor

你的正则表达式问题在于你期望它做一些实际上普通的正则表达式无法做到的事情:如果备选项不起作用,你期望它匹配单词。因为普通语言无法跟踪状态,普通的正则表达式无法表达这个意思。
我其实不确定是否可以使用某种前瞻来帮助你在一个正则表达式中完成这个操作,即使可以,你最终会得到一个非常复杂、难以理解甚至不高效的正则表达式。
因此,你可能更喜欢使用两个正则表达式,如果第一个失败了,从第二个正则表达式获取组。
((13th|Executive|Residential) +Floor)

如果没有匹配项

((\w+ +){1:3}Floor)

注意:为了避免重复,可以参考我提供的另一个答案,其中列出了关于正则表达式和NFA的有趣资源。这将帮助您了解正则表达式的实际工作原理。请查看该答案

1
不,\w+(或{1,3})量词的贪婪性并不是问题所在。问题在于NFA正则表达式引擎匹配“最长最左侧”子字符串。只要在“floor”之前有三个单词,无论任何量词的贪婪/懒惰程度,其他两个选项都永远没有机会进行匹配。 - ridgerunner
错误的是,\w+ 总是匹配最长的最左边的子字符串! - zmo
那么这就有意义了。如果有一种方法可以指示表达式本身从左到右停止第一个匹配,那将是很好的。但是似乎您是在说最好的解决方案实际上是在一个步骤中运行非冲突表达式(# vs 显式名称),然后如果没有找到,则在其自己的步骤中运行贪婪表达式。事实上,我刚刚重新测试了一下,如果我将w+变成单词,那么它就不会覆盖其他内容。因此,我可以进行一次通行证,其中将获取#、已知单词或未知单词,如果失败,则在之后进行单个捕获。 - user3649739
\w+ 贪婪地匹配一个单词。在这种情况下,如果变成懒惰模式对整体匹配没有影响。同样适用于 {1,3} 量词。正则表达式引擎必须尝试所有可能性才能放弃,即使所有量词都是懒惰的,在其他备选项之前,最后一个备选项始终能够匹配。 - ridgerunner
我不明白为什么你一直试图在没有问题的情况下寻找问题。原帖作者说他想要获取最多三个单词,以防他的备选匹配不起作用,因此第二个正则表达式匹配至少一个空格分隔的一个到三个单词。 - zmo
显示剩余2条评论

3

首先,这是您的正则表达式在自由空间模式下:

tidied = re.compile(r"""
    (                   # $1: ...
      (                 # $2: One ... from 3 alternatives.
        13th            # Either a1of3.
      | (               # Or a2of3 $3: One ... from 2 alternatives.
          Executive[ ]  # Either a1of2.
        | Residential   # Or a2of2.
        )               # End $3: One ... from 2 alternatives.
      | (               # Or a3of3 $4: Last match from 1 to 3 ...
          (\w+)         # $5: ...
          [ ]           #
        ){1,3}          # End $4: Last match from 1 to 3 ...
      )                 # End $2: One ... from 3 alternatives.
      Floor             #
    )                   # End $1: ...
    """, re.VERBOSE)

请注意,上述模式有额外的括号,但对匹配结果没有影响。以下是一个更简化的表达式,与原表达式功能等效:
tidied = re.compile(r"""
    (               # $1: One ... from 4 alternatives.
      13th          # Either a1of4.
    | Executive[ ]  # Or a2of4.
    | Residential   # Or a3of4.
    | (             # Or a4of4 $2: Last match from 1 to 3 ...
        (\w+)       # $3: ...
        [ ]         #
      ){1,3}        # End $2: Last match from 1 to 3 ...
    )               # End $1: One ... from 4 alternatives.
    Floor           #
    """, re.VERBOSE)

最长左匹配

在所需单词“Floor”之前,实际上有四个分组选项。前三个选项仅为一个单词,但第四个选项匹配了三个单词。NFA正则表达式引擎从左到右工作,并始终尝试找到最长的左匹配。在这种情况下,当正则表达式一次一个字符地遍历时,它会在每个字符位置测试所有四个选项。由于第四个选项总是可以匹配到其他三个选项两个单词之前,因此它将始终首先匹配(假设给定文本中有三个单词在“Floor”之前)。如果在Floor之前没有三个单词,则可能会匹配前三个选项中的其中一个。

还要注意的是,13thResidential选项后面没有必需的空格,因此只有在出现连接后的文本ResidentialFloor13thFloor时才会匹配。


@zmo - 请不要编辑我的答案。你所做的更改使它变得不正确! - ridgerunner
确实很抱歉。我自己注意到了这个问题,所以我回滚了更改。 - zmo

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