【问题标题】:Why "ab(cd|c)*d" matches "abcdcdd" completely but "ab(c|cd)*d" does not match that? Whereas they're like each other为什么 "ab(cd|c)*d" 完全匹配 "abcdcdd" 但 "ab(c|cd)*d" 不匹配?尽管他们彼此相似
【发布时间】:2019-08-31 12:11:51
【问题描述】:

我试过这个正则表达式:

ab(cd|c)*d

regex101RegExr 网站中。它与此文本完全匹配

abcdcdd

现在让我们在正则表达式中交换 "cd""c"

ab(c|cd)*d

当我在网站上尝试这个正则表达式时,我发现这个正则表达式 不完全匹配相同的文本。

为什么正则表达式引擎无法识别ab(cd|c)*dab(c|cd)*d 相同,如何说服ab(c|cd)*d 匹配最长的字符串?


正则表达式:ab(cd|c)*d

13 步骤中匹配的完整文本:abcdcdd


正则表达式:ab(c|cd)*d

9 步骤中匹配的部分文本:abcdcdd

【问题讨论】:

  • “为什么正则表达式引擎无法识别 ab(cd|c)*dab(c|cd)*d 是相同的”。好吧,因为正如您所观察到的,它们相同...
  • 如果d 是可选的,您可以省略管道并使d 可选。 ab(cd?)*d。请注意,它会重复捕获组。 regex101.com/r/frwMkI/1
  • 这里有很多非常技术性的答案,但简单的答案是正则表达式匹配|(或语句)优先于最左边的模式,如果它永远不会尝试第二个模式第一场比赛,没有任何事情会导致比赛头回溯。在您的第二个示例中,cc|cd 匹配,因此我们退出“或”部分,然后匹配 d,使 cdd 不匹配。
  • 另请参阅Use the right regex flavor!,了解匹配最长序列的正则表达式引擎示例。

标签: regex


【解决方案1】:

@MurrayW 的回答很好,但我想补充一些背景信息。

作为有限状态自动机的正则表达式

当我在大学里第一次学习正则表达式时,我们学会了将它们转换为有限状态自动机,本质上是将它们编译成图形,然后进行处理以匹配字符串。当你这样做时,(cd|c)(c|cd) 被编译到同一个图中,在这种情况下,你的两个正则表达式都将匹配整个字符串。这就是grep 实际所做的:

两者

echo abcdcdd | grep --color -E 'ab(c|cd)*d'

echo abcdcdd | grep --color -E 'ab(cd|c)*d'

将整个字符串涂成红色。

我们称之为“正则表达式”的模式

真正的有限状态自动机有许多程序员不喜欢的限制,例如无法捕获匹配组,无法在模式的后期重用这些组,以及我忘记的其他限制,所以我们使用的正则表达式库在大多数编程语言中,都实现了更复杂的形式。我不记得它们究竟是什么,也许是下推自动机,但我们有记忆,我们有回溯,以及我们不假思索地使用的各种好东西。

冒着显得迂腐的风险,我们使用的模式根本不是“常规”的。我知道,差异通常是无关紧要的,我们只是希望我们的代码能够工作,但有时它很重要。

因此,虽然正则表达式 (cd|c)(c|cd) 将被编译成相同的有限状态自动机,但这两个(非正则)模式反而变成了逻辑,表示从左到右尝试变体,并且仅当模式的其余部分稍后无法匹配时才回溯,因此您观察到的结果。

速度

虽然我们的“正则表达式”库支持的模式为我们提供了许多我们喜欢的好东西,但这些都是以性能为代价的。真正的正则表达式非常快,而我们的模式虽然通常很快,但有时可能非常昂贵。在此站点上搜索“灾难性回溯”,以获取许多需要指数时间才能失败的模式示例。与grep 一起使用的相同模式将被编译成一个图形,该图形以线性时间应用于字符串以进行匹配。

【讨论】:

  • 通常构造算法首先从正则表达式到非确定性有限状态自动机,然后再到有限状态自动机。那些模拟 NFSA 的正则表达式“引擎”应该进行回溯,因此 (cd|c) 和 (c|cd) 应该是等价的。我承认我对 OP 的帖子感到惊讶。当您说“我们使用的模式根本不规则”时,我不确定您的意思是什么,除非您的意思是暗示我们没有得到预期的结果。我认为 (cd|c) 不等同于 (c|cd) 是一个错误。
  • @RonaldAaronson 我同意如果表达式确实是常规的,它应该被视为一个错误。但我们在大多数“正则”表达式库中命名错误的模式实际上并不是为了成为正则,我们只是出于历史原因坚持使用该名称。
  • 我试图了解ab(cd|c)*d常规ab(cd?)*d 在我看来也是等价的呢?
  • 您误解了我和问题:OP 和我不是谈论正则表达式,我们谈论的是 regex101.com,Perl 中的// 运算符,各种语言的“正则表达式”库。这些东西实现了支持看起来像正则表达式但实际上并不作为正则表达式处理的表达式的库。所有这些库都是错误命名的,并且不遵守正则表达式的严格规则。
  • @RonaldAaronson 再补充一个想法:您指出这两种模式看起来就像正则表达式。确实,我认为这可能就是为什么我们所有的库都被称为正则表达式库的原因,即使应用于它们的形式不同。
【解决方案2】:

因为| 字符通过首先测试最左边的条件来执行or 操作。如果匹配,则不会在or 中进行进一步测试。如果失败,则测试下一个 or 元素,依此类推。

使用正则表达式模式ab(cd|c)*d,您可以看到(cd|c)*cd 部分在您的字符串中匹配,并且也重复:abcdcdd。

但是,在模式 ab(c|cd)*d 中,c 匹配来自 abcdcdd 中的 or 操作,因此根本不测试 cd。然后,模式末尾的d 匹配第一个c 之后的d,然后模式停止,只匹配abcdcdd

【讨论】:

    【解决方案3】:

    正如之前在 cmets 中所回答的,它们不是相同的模式。第一个中的 alternation 首先尝试匹配 cd,第二个尝试匹配 c 首先。

    第一个模式

    abcdcdd
      ^^^^
       ||
       ||
    ab(cd|c)*d
    

    第二种模式

     abcdcdd
       ^^____
       |     |
       |     |
    ab(c|cd)*d
    

    如果d 是可选的,您可以省略管道进行交替,并使d 可选。

    ab(cd?)*d.
    

    Regex demo

    请注意,这样您会重复捕获组,该组将保存最后一次迭代的值。

    如果您对组的价值不感兴趣并且支持非捕获组,您可以使用ab(?:cd?)*d.

    【讨论】:

      【解决方案4】:

      正则表达式始终是从左到右的命题。
      正则表达式引擎将忽略先前的交替构造的唯一方法
      是它是否必须满足交替组右侧的一项
      否则无法满足。

      正则表达式规则是模式从左到右遍历,
      但由从左到右遍历的目标字符串控制。

      共生..

      鉴于目标字符串的匹配方式如下“abcdcdd”
      很容易假设完整正则表达式的正则表达式子集

       ab
       ( c | cd )*                   # (1)
       d
      

      很明显

       ab
       c* 
       d
      

      从来不需要右边交替的cd术语
      一场成功的比赛。

      这证明了正则表达式引擎是一个从左到右的偏见机器。

      【讨论】:

      • 恐怕我不得不不同意:正确编译为有限状态自动机的正则表达式,例如grep,不是从左到右的命题。相反,它将始终接受它可以接受的最长的子字符串。但是我们的正则表达式库不使用有限状态自动机,您对我们的库的回答是正确的。 (我只是反对“总是”这个词——是的,它们在某些情况下是从左到右的,但并非总是如此。)
      • @joanis - 总是匹配最长的匹配比匹配第一个匹配更糟糕,你不同意吗?想象一下 4000 次交替,设计最长匹配之类的东西简直就是胡扯。我要重复自己Regex is always a left to right proposition 先到先得的提议,总是
      • @joanis - 虽然我确实在 Formal Symbolic Logic 中获得了 A,但我将把这些形式语言的东西留给你,这是一门选修课 I上学了。我确实带来了您在本网站上找不到的一定程度的正则表达式专业知识。
      • @joanis - 我比理论家更多。虽然只是因为我有阅读障碍。不过,我确实设法修改/修复了 boost 正则表达式引擎并学到了一些东西
      猜你喜欢
      • 2013-07-15
      • 1970-01-01
      • 1970-01-01
      • 2012-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多