为什么一个简单的.*?非贪婪的正则表达式在匹配之前贪婪地包含其他字符?

IT技术 javascript regex non-greedy
2021-02-06 03:33:15

我有一个与此类似的非常简单的正则表达式:

HOHO.*?_HO_

有了这个测试字符串...

fiwgu_HOHO_HOHO_HOHOrgh_HOHO_feh_HOHO___HO_fbguyev

  • 我希望它匹配_HOHO___HO_(最短匹配,非贪婪)
  • 相反,它匹配_HOHO_HOHO_HOHOrgh_HOHO_feh_HOHO___HO_(最长匹配,看起来很贪婪)。

为什么?我怎样才能让它匹配最短的匹配?

添加和删​​除?给出了相同的结果。

编辑- 更好的测试字符串,显示为什么[^HOHO]不起作用:fiwgu_HOHO_HOHO_HOHOrgh_HOHO_feh_HOHO_H_O_H_O_HO_fbguye


我能想到的可能是它可能多次匹配 - 但只有一个匹配_HO_,所以我不明白为什么它不采用以 结束的最短匹配_HO_,丢弃其余的。

我浏览了所有我能找到的标题为“非贪婪的正则表达式行为贪婪”的问题,但它们似乎都有一些其他问题。

3个回答

我在Regex lazy vs greedy混淆的帮助下找到了一个解决方案

在像 Javascript 使用的正则表达式引擎(我相信NFA 引擎)中,非贪婪只为您提供从左到右最短的匹配- 从适合最接近的右手匹配的第一个左手匹配。

如果一个右手匹配有许多左手匹配,它总是从它到达的第一个开始(实际上这将给出最长的匹配)。

本质上,它一次一个字符地遍历字符串,询问“是否有来自这个字符的匹配项?如果有,匹配最短的并完成。如果没有,移动到下一个字符,重复”。我希望它是“这个字符串中的任何地方都有匹配吗?如果有,匹配所有这些中最短的”。


您可以通过将 the 替换.为“不是左侧匹配”的否定来近似在两个方向上都非贪婪的正则表达式否定这样的字符串需要否定前瞻和非捕获组,但这就像将字符串放入(?:(?!).). 例如,(?:(?!HOHO).)

例如,HOHO.*?_HO_左右两边非贪婪的等价物是:

HOHO(?:(?!HOHO).)*?_HO_

所以正则表达式引擎本质上是这样处理每个字符的:

  • HOHO - 这与左侧匹配吗?
  • (?:(?!HOHO).)* - 如果是这样,我可以在不重复左侧的情况下到达右侧吗?
  • _HO_ - 如果是这样,抓住一切,直到右手比赛
  • ?修饰符 on*+- 如果有多个右侧匹配项,请选择最接近的一项
你是对的,谢谢,它正在寻找不是 H 或 O 的字符,而不是针对字符串进行测试,我需要使用类似于此stackoverflow.com/questions/977251 /...当我有时间
2021-03-15 03:33:15
我花了一段时间才明白它是(?:(?!HOHO).)*什么的,或者更确切地说它是如何工作的。对我来说,这就是我发现的解释清楚:“从当前光标开始,接下来的 4 个字符是“HOHO”吗?如果是,我们就完成了这一部分并继续进行正则表达式的下一部分。如果不,然后抓住光标处的字符,将光标移过它,重复。”
2021-03-20 03:33:15
好的,我想我现在已经解决了
2021-03-21 03:33:15
第一部分的解释很好,你解决当前问题的例子是错误的。你需要更好地理解字符类的概念以及为什么写作[^HOHO]没有意义。
2021-03-26 03:33:15

为什么它匹配整个字符串?

这是因为正则表达式模式匹配是通过查找字符串中可能匹配的第一个位置来完成的。由于匹配可能从字符串的第一个字符开始,因此从不考虑从后续字符开始的较短匹配。

示例:
让我们考虑一个正则表达式/a+?b/和测试字符串"aaaaaaaaab"当应用于字符串时,它匹配整个字符串。不只是最后a& b这是因为字符串中可能匹配的第一个位置是第一个a.

因此,如果您想匹配abin aaaaaaaaab,请使用基于否定字符类的正则表达式而不是惰性点:

a[^ab]*b

请参阅正则表达式演示

来源: Javascript:权威指南,第六版,页码:255

请阅读如何参考他人撰写的材料TL;DR:您需要链接到来源并提供作者或组织的名称(如果没有个人作者)。在这种情况下,大卫弗拉纳根似乎是这本书的作者。
2021-03-31 03:33:15
也很高兴提及您的消息来源
2021-04-04 03:33:15

结果是非贪婪的,因为它是从第一次出现HOHO直到_HO_到达的最短匹配引擎从左到右遍历字符串,因为它不必回溯,它不会尝试缩短任何东西。

为了让它以这里预期的方式工作,你需要在你的表达式中有一个贪婪的前缀:

/.*(HOHO.*?_HO_)/

第一个内存捕获包含您想要的字符串;贪婪前缀会尝试跳过尽可能多的字符,因此它将匹配最后一次出现的字符HOHO

我一直在阅读blog.stevenlevithan.com/archives/greedy-lazy-performance试图更好地理解回溯。在这种情况下,它会匹配整个字符串,然后回溯直到找到字符串的最后一个HOHO,然后向前匹配直到到达 ,这是真的_HO_吗?那么在一个字符串中HOHO_1_HO_|HOHO_2_HO_|HOHO_3_HO_它只会匹配HOHO_3_HO_吗?我还想知道我的答案中的示例在字符串很长的情况下是否可能更有效?
2021-03-17 03:33:15
@ user568458 是的,这就是我的答案所暗示的;即它将首先匹配最后一次出现的“HOHO”:) 性能可能会受到影响,但只有基准才能提供一些保证。
2021-03-28 03:33:15