理解量词

13

我正在学习 Java教程中的量词

其中提到了贪婪、勉强和占有量词之间的差异。

我无法确切地理解它们之间的区别。

如下所示的解释:


Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.
第一个示例使用贪婪量词 .* 查找"任何内容",零次或多次,并跟随字母"f""o""o"。由于量词是贪婪的,表达式中的 .* 部分首先会占用整个输入字符串。此时,整个表达式无法成功,因为最后三个字母("f""o""o")已被消耗。因此,匹配器逐个字母地缓慢后退,直到右侧出现的 "foo" 被反推出来,此时匹配成功并结束搜索。
然而,第二个示例是勉强的,因此它首先将“nothing”消耗掉。由于“foo”不出现在字符串的开头,它被迫吞下第一个字母(一个“x”),这触发了0和4处的第一次匹配。我们的测试工具继续进行该过程,直到输入字符串用尽。它在4和13处找到另一个匹配项。
第三个示例未能找到匹配项,因为量词是独占的。在这种情况下,.*+ 会消耗整个输入字符串,没有留下任何内容来满足表达式末尾的 "foo"。在想要占用所有内容而永远不回退的情况下,请使用独占量词;在没有立即找到匹配项的情况下,它将优于相应的贪婪量词。

我认为理解量词是相当容易的。然而,实现和使用却相当复杂。因此,它是5%的理解和95%的使用。理解可以给你一个复杂使用组合的矩阵。 - user557597
1
你有什么问题?你不明白什么? - Casimir et Hippolyte
你能否通过接受一个答案作为正确答案来结束这个问题? - buræquete
这个回答解决了你的问题吗?贪婪、勉强和占有限定符的区别 - outis
2个回答

9
懒惰(不情愿的)和贪婪模式之间的主要区别在于回溯结构的行为,而所有占有模式都太过于激进
  • 懒惰模式将在单次匹配后,将匹配引擎的焦点放弃给正则表达式中的下一个运算符。如果下一个操作符失败,则回溯结构将强制重复懒惰模式,这将继续直到运算符或目标文本结束;例如,在通过匹配字符f之后,将传递到匹配阶段时,每当您有一个foo短语时,都会获得匹配,因此我们从其使用中获得多个匹配。

.*?foo

xfooxxxxxxfoo
在开始时,懒惰模式将在成功的空匹配之后与x进行了成功的匹配并将焦点传递给下一个运算符;正则表达式的foo部分存在于x之后,因此我们针对该片段获得匹配,对于字符串的第二部分也是同样的道理。

  • 贪婪模式则相反,将一直匹配直到失败为止,永远不会将焦点传递给下一个运算符,只有当匹配失败时回溯才会生效,接下来的操作将被反向匹配;

.*foo

xfooxxxxxxfoo
当贪婪模式到达此点(最后一个字符)时,由于我们无法匹配正则表达式的foo部分,因此匹配将失败。然后回溯将强制贪婪模式回溯其步骤并强制进行下一个运算符foo,与懒惰模式类似;

xfooxxxxxxfoo
在这一点上,foo部分将获得成功匹配,从而以整个字符串的成功匹配结束。

  • 占有模式与贪婪模式非常相似,除了匹配失败会导致回溯之外,这不是占有模式的情况。如果可以匹配,则它将占有并在此过程中牺牲匹配的成功。如果在匹配字符时失败,则仅焦点将传递给正则表达式的下一个运算符。

.*+foo

xfooxxxxxxfoo
与贪婪模式类似,我们已经到达了字符串的结尾,但是占有模式仍然可以匹配它,因此不会将火炬传递给回溯结构,并将导致匹配失败。


7

基本规则

需要了解量词符号 ?*+ 的基本知识(分别表示“零个或一个”、“零个或多个”、“一个或多个”)。

  • 如果一个量词是贪婪的,则尝试尽可能多地匹配字符。
  • 如果一个量词是勉强的(懒惰的),则尝试尽可能少地匹配字符。
  • 如果一个量词是占有的,则它是贪婪的,并且不允许回溯。

只有了解正则表达式解析器的工作原理(请参见下面的“动态示例”),才能理解“回溯”的含义。

单个情况的解释

  • ?:首先尝试匹配 1 次,然后匹配 0 次;如果已经匹配到 1 次并且需要放弃它,可以这样做。
  • ??:首先尝试匹配 0 次,然后匹配 1 次。
  • ?+:首先尝试匹配 1 次,然后匹配 0 次;如果已经匹配到 1 次并且需要放弃它,则不能这样做。
  • *:尝试尽可能多地匹配(甚至是 0 个);如果已经匹配到 N 个字符,然后需要放弃(其中的一些),可以从最后一个开始这样做。
  • *?:尝试尽可能少地匹配(甚至是 0 个)。
  • *+:尝试尽可能多地匹配(甚至是 0 个);如果已经匹配到 N 个字符,然后需要放弃(其中的一些),则不能这样做。
  • +:尝试尽可能多地匹配(至少 1 个);如果已经匹配到 N 个字符,然后需要放弃(其中的一些),可以从最后一个开始这样做。
  • +?:尝试尽可能少地匹配(至少 1 个)。
  • ++:尝试尽可能多地匹配(至少 1 个);如果已经匹配到 N 个字符,然后需要放弃(其中的一些),则不能这样做。

动态示例

在本节中,我将尝试向您展示正则表达式解析器的逻辑:

1) 情况 /.*foo/

首先轮到子模式 /.*/ 进行处理。它开始处理第一个字符:

xfooxxxxxxfoo
^

然后它会尝试尽可能扩展更多字符:
xfooxxxxxxfoo
^^
xfooxxxxxxfoo
^^^
[...]
xfooxxxxxxfoo
^^^^^^^^^^^
xfooxxxxxxfoo
^^^^^^^^^^^^
xfooxxxxxxfoo
^^^^^^^^^^^^^

光标已经到达末尾,但子模式/foo/还没有发挥作用。因此,光标会“回退”,以便子模式/foo/可以匹配:

xfooxxxxxxfoo
^^^^^^^^^^^^
< p > /foo/ 仍然无法匹配,因此我们需要再次返回:

xfooxxxxxxfoo
^^^^^^^^^^^
xfooxxxxxxfoo
^^^^^^^^^^

现在子模式/foo/可以匹配成功:
xfooxxxxxxfoo
^^^^^^^^^^^^^

因此,匹配的字符串是整个字符串xfooxxxxxxfoo

2) 匹配规则/.*?foo/:

首先轮到子模式/.*?/。它是惰性的,所以我们希望它匹配0个字符。但如果这样做,子模式/foo/就不能匹配,所以它必须详细解释一个字符:

xfooxxxxxxfoo
^

现在轮到foo了:
xfooxxxxxxfoo
^^^^

所以这个匹配是xfoo

(如果你为正则表达式设置类型global,那么解析器会从匹配后的第一个字符重新开始,找到第二个匹配项xxxxxxfoo)

3)Case /.*+foo/

首先轮到子模式 /.*+/。 它试图尽可能多地处理字符:

xfooxxxxxxfoo
^
[...]
xfooxxxxxxfoo
^^^^^^^^^^^^^

光标到达了末尾,但是子模式 /foo/ 还没有起作用。所以光标“返回”...哦不,太可惜了,它无法返回(因为它是贪婪的)!因此我们没有匹配项。

还要查看文档:#1#2 - logi-kal

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