惰性量词 {,}?没有像我预期的那样工作

Lazy quantifier {,}? not working as I would expect

我对惰性量词有疑问。或者很可能我误解了我应该如何使用它们。

测试 Regex101 我的测试字符串是:123456789D123456789

.{1,5} 匹配 12345

.{1,5}? 匹配 1

我对两场比赛都满意。

.{1,5}?D 匹配 56789D !!我希望它匹配 9D

感谢您澄清这一点。

首先,请不要将正则表达式中的贪婪和懒惰视为获得longest/shortest 匹配 的手段。 "Greedy" 和 "lazy" 项仅属于模式可以匹配的 最右边的字符 ,它对最左边的字符没有任何影响。当你使用惰性量词时,它会保证你匹配的子串的末尾是第一个找到的,而不是最后一个找到的(那将是returned with a greedy quantifier)。

正则表达式引擎从左到右分析字符串。因此,它会搜索符合模式的第一个字符,然后,一旦找到匹配的子字符串,它就会被 return 编辑为匹配项。

让我们看看它如何解析带有 .{1,5}D 的字符串:找到 1 并测试 D。找到1后没有D,正则表达式引擎扩展惰性量词匹配12并尝试匹配D2后面还有3,引擎又把惰性点展开,做了5次。展开到最大值后,看到有12345,下一个字符不是D。由于引擎达到最大限制量词值,匹配失败,测试下一个位置。

同样的情况发生在 5 之前的位置。当引擎达到 5 时,它尝试匹配 5D,失败,尝试 56D,失败,567D,失败,5678D - 再次失败,当它试图匹配 56789D - Bingo! - 找到匹配项。

这清楚地表明,在模式开头的 惰性量化子模式将默认执行 "greedily",即不会匹配最短子字符串 .

这是来自 regex101.com 的可视化:

现在,一个有趣的事实:模式末尾的 .{1,5}? 将始终匹配 1 个字符(如果有的话),因为要求是至少匹配 1 个,并且 return 一个有效匹配就足够了。所以,如果你写 D.{1,5}?,你将在 123456789D12345D678904.

中得到 D1D6

有趣的事实 2:在 .NET 中,您可以 "ask" 正则表达式引擎在 RightToLeft 的帮助下从右到左分析字符串修饰符。然后,用.{1,5}?D,你会得到9D,见this demo

有趣的事实 3:在 .NET 中,如果 123456789D 作为输入传递,(?<=(.{1,5}?))D 会将 9 捕获到组 1 中。发生这种情况是因为 lookbehind is implemented in .NET regex 的方式(.NET 反转字符串以及 lookbehind 内的模式,然后尝试匹配反转字符串上的单个模式)。而在 Java 中,(?<=(.{1,5}))D(贪婪版本)将捕获 9,因为它会尝试范围内所有可能的固定宽度模式,从最短到最长,直到一个成功。

解决方案是:如果您知道需要 1 个字符后跟 D,只需使用

/.D/

如果你有一个包含数字后跟一个非数字的字符串,最小集合{1,5}?将始终为 1(因此没有必要具有范围。)我认为惰性运算符实际上并没有像我们在数字范围内所想的那样工作。

如果你让第一个 \d+ 变得贪婪,如下所示,你将获得 D 之前的最少位数。

(\d+)(\d{1,5}D) 匹配第二组9D

如果您使第一组数字变懒,那么您将获得最大位数 (5)

(\d+?)(\d{1,5}D) 匹配第二组56789D

我觉得这些正则表达式可能更符合你的需要

你的正则表达式是

.{1,5}?D

匹配

123456789D123456789
    ------

但是你说你期望 9D 因为使用了 "non-greedy quantifier".

总之,这个怎么样?

D.{1,5}?

匹配的结果是什么?

是的!如您所料,它匹配

123456789D123456789
         --

那么,为什么?

好的,首先,我认为您需要了解正则表达式引擎通常会从输入字符串的左侧到右侧读取字符。考虑到您使用非贪婪量词的示例,一旦引擎匹配

123456789D123456789
    ------

不会更进一步

123456789D123456789
     -----
123456789D123456789
      ----
...
123456789D123456789
        --

因为正则表达式引擎会尽可能少地评估文本,这就是为什么它也称为 "Lazy quantifiers"。

它在我的正则表达式 D.{1,5}? 上也以同样的方式工作,不应该更进一步

123456789D123456789
         ---
123456789D123456789
         ----
...
123456789D123456789
         ------

但在第一场比赛就停止了

123456789D123456789
         --