贪婪与不愿意与拥有量词

我发现了这个关于正则expression式的优秀教程 ,虽然我直观地理解了“贪婪”,“不情愿”和“占有”量词的含义,但我的理解似乎存在一个严重的漏洞。

具体来说,在下面的例子中:

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. 

这个解释提到了整个inputstring,字母被消耗 ,匹配器退后,“foo”的最右边的事件已经被反刍 ,等等。

不幸的是,尽pipe有很好的比喻,我仍然不明白被谁吃了什么…你知道另一个教程(简洁地) 解释正则expression式引擎是如何工作的吗?

或者,如果有人能够在下面的段落中有所不同的解释,那将会非常感激:

第一个例子使用贪婪的量词。*find“任何东西”,零次或多次,后面跟着字母“f”“o”“o”。 由于量词是贪婪的,expression式的。*部分首先吃整个inputstring。 在这一点上,整体expression不能成功,因为最后三个字母(“f”“o”“o”)已经被消耗( 由谁? )。 所以匹配器每次慢慢退出( 从右到左 )一个字母,直到“foo”的最右边的事件被反刍( 这是什么意思? ),此时匹配成功并且search结束。

然而,第二个例子是不愿意的,所以它首先消耗( 由谁? )“无”。 因为“foo”没有出现在string的开头,所以它被迫吞下( 吞下?)第一个字母(一个“x”),在0和4处触发第一个匹配。我们的testing工具继续这个过程直到inputstring耗尽。 在4日和13日发现另一场比赛。

第三个例子没有find匹配,因为量词是占有的。 在这种情况下,整个inputstring被。* +消耗,( 怎么样? )在expression式的末尾没有留下任何东西来满足“foo”。 如果你想抓住所有的东西而没有退缩,那么使用占有量词来表示后面的意思是什么? 在没有立即find匹配的情况下,它将胜过相当的贪婪量词。

我会给它一个镜头。

一个贪婪的量词首先尽可能匹配。 所以.*匹配整个string。 然后匹配器试图匹配下面的f ,但是没有剩下的字符。 所以它“回溯”,使贪婪的量词匹配一件事(把string末尾的“o”留下来)。 这仍然不匹配正则expression式中的f ,所以它“回溯”了一步,使得贪婪的量词再次匹配一个更less的东西(留下string末尾的“oo”不匹配)。 这仍然不匹配正则expression式中的f ,所以它回溯了一个步骤(在string末尾留下“foo”不匹配)。 现在,匹配器最终匹配正则expression式中的foo也匹配。 成功!

一个不情愿的或“非贪婪的”量词首先尽可能less地匹配。 所以.*不会匹配任何内容,而是让整个string不匹配。 然后匹配器试图匹配下面的f ,但是string的不匹配部分以“x”开始,所以不起作用。 所以匹配器回溯,使非贪婪量词匹配一件事(现在匹配“x”,留下“fooxxxxxxfoo”无可匹敌)。 然后它会尝试匹配成功的f和正则expression式匹配中的oo 。 成功!

在你的例子中,然后按照相同的过程,用剩下的不匹配的string部分开始处理。

占有量词与贪婪量词一样,但不会回溯。 所以它开始与.*匹配整个string,没有什么不匹配的。 那么在正则expression式中就没有什么可以和f匹配的了。 由于所有格量词不回溯,所以匹配在那里失败。

我还没有听说过“反胃”或“退避”的确切术语; 那么将取代这些词组就是“回溯”,但是“反刍”似乎就像“回溯之前暂时被接受的内容再次抛弃”一样。

对于大多数正则expression式引擎来说,重要的是它们是回溯 :他们将暂时接受一个潜在的,部分匹配,同时试图匹配正则expression式的全部内容。 如果正则expression式在第一次尝试时不能完全匹配,则正则expression式引擎将在其中一个匹配上回溯 。 它会尝试匹配*+ ? ,交替或{n,m}重复不同,然后重试。 (是的,这个过程可能需要很长时间。)

第一个例子使用贪婪的量词。*find“任何东西”,零次或多次,后面跟着字母“f”“o”“o”。 由于量词是贪婪的,expression式的。*部分首先吃整个inputstring。 在这一点上,整体expression不能成功,因为最后三个字母(“f”“o”“o”)已经被消耗( 由谁? )。

最后三个字母foo已经被规则的最初部分消耗掉了。 但是,正则expression式f的下一个元素在inputstring中没有任何内容。 引擎将被迫回溯到最初的匹配,并尝试匹配除了最后一个字符以外的所有字符。 (这可能是聪明的 ,回到最后三个,因为它有三个字面的术语,但我不知道这个级别的实现细节。)

所以匹配器每次慢慢退出( 从右到左 )一个字母,直到“foo”的最右边的事件被反刍( 这是什么意思?

这意味着foo在匹配时暂时包含了。 由于该尝试失败,正则expression式引擎尝试在.*接受less一个字符。 如果在这个例子中.* 之前有一个成功的匹配,那么引擎可能会尝试缩短匹配(从右到左,正如你所指出的,因为它是一个贪婪的限定词),如果它无法匹配整个input,那么在我假设的例子中,可能会被迫重新评估它在匹配之前的匹配。

指向匹配成功,search结束。

然而,第二个例子是不情愿的,所以它首先消耗( 由谁? )“无”。 因为“富”

最初的什么都没有被消耗掉.?* ,这会消耗尽可能最less的任何东西,使正则expression式的其余部分能够匹配。

没有出现在string的开头,它被迫吞下( 吞下?)

在第一个字符后面,第一个字符也是消耗的,在最初的失败之后回溯到匹配整个正则expression式和尽可能短的匹配。 (在这种情况下,正则expression式引擎将从左到右扩展匹配.*?因为.*?是不情愿的。)

第一个字母(“x”),它在0和4处触发第一个匹配。我们的testing工具继续这个过程,直到inputstring被耗尽。 在4日和13日发现另一场比赛。

第三个例子没有find匹配,因为量词是占有的。 在这种情况下,整个inputstring被消耗。* +,( 如何?

A .*+将尽可能消耗,并且当整个正则expression式无法find匹配时, 将不会回溯到find新的匹配项。 由于所有格forms不执行回溯,所以您可能不会看到.*+许多用法,而是用字符类或类似的限制: account: [[:digit:]]*+ phone: [[:digit:]]*+

这可以大大加快正则匹配,因为你告诉正则expression式引擎,如果input不匹配,它不应该回溯潜在的匹配。 (如果你必须手工编写所有匹配的代码,这将类似于从不使用putc(3)来“推回”一个input字符,这与第一次尝试写入的朴素代码非常相似。除了正则expression式引擎比推回的单个字符更好,他们可以倒回所有的零,然后再试一次。

但是,除了潜在的加速,还可以让你编写正确匹配你所需要的正则expression式。 我遇到了一个简单的例子:)但用占有者和贪婪的量词写一个正则expression式可以给你不同的匹配,而其中一个可能更合适。

在expression式结尾处留下任何东西来满足“foo”。 如果你想抓住所有的东西而没有退缩,那么使用占有量词来表示后面的意思是什么? 它会跑赢大盘

在这种情况下,“后退”意味着“回溯” – 抛弃一个暂时的部分匹配来尝试另一个部分匹配,这个匹配可能成功也可能不成功。

在没有立即find匹配的情况下等价的贪婪量词。

这只是我的练习输出,

视觉图像

http://swtch.com/~rsc/regexp/regexp1.html

我不确定这是在互联网上的最好的解释,但它的写得很好,适当详细,我不断回来。 你可能想看看。

如果你想要一个更高级的(不太详细的解释),对于简单的正则expression式,比如你正在看的那个,正则expression式引擎通过回溯工作。 本质上,它select(“吃”)string的一部分,并尝试匹配该部分的正则expression式。 如果它匹配,很好。 如果不是,引擎会改变它对string部分的select,并尝试将正则expression式匹配到该部分,依此类推,直到尝试所有可能的select。

这个过程是recursion使用的:在试图将一个string与一个给定的正则expression式匹配时,引擎将正则expression式分割成几部分,并将algorithm分别应用到每个部分。

贪婪,不情愿和占有量词的区别在引擎做出select要匹配的string的哪一部分的时候,以及如果第一次不工作时如何修改这个select。 规则如下:

  • 一个贪婪的量词告诉引擎从整个string开始(或者至less是所有尚未与正则expression式的前面部分匹配的string),并检查它是否与正则expression式匹配。 如果是的话,太好了; 引擎可以继续其余的正则expression式。 如果不是的话,它会再次尝试,但是要修改一个字符(最后一个)离开要检查的string部分。 如果这样做不起作用,则会删除另一个字符等。因此,一个贪婪的量词从最长到最短的顺序检查可能的匹配。

  • 一个不情愿的量词告诉发动机从尽可能短的一段弦开始。 如果匹配,引擎可以继续; 如果不是,则会在要检查的string部分添加一个字符,然后尝试该字符,直到find匹配项或整个string已用完。 所以一个不情愿的量词从最短到最长的顺序检查可能的匹配。

  • 所有格量词在第一次尝试时就像是一个贪婪量词:它通过检查整个string来告诉引擎开始。 不同之处在于,如果不起作用,那么所有格量词就会报告比赛失败。 引擎不会改变正在查看的string部分,也不会再做任何尝试。

这就是为什么在你的例子中所有格量词匹配失败: .*+被整个string匹配,然后引擎继续寻找额外的字符foo后,但当然没有find他们,因为你已经在string的末尾。 如果它是一个贪婪的量词,它会回溯,并尝试使匹配到倒数第二个字符,然后到倒数第三个字符,然后到倒数第四个字符,这是成功的,因为只有那么在.*已经“吃掉”了string的前面部分之后还有剩下的。

以下是我使用单元格和索引位置(请参阅此处的图表来区分单元格和索引)。

贪婪 – 尽可能匹配贪婪的量词和整个正则expression式。 如果没有匹配的话,回溯贪婪的量词。

inputstring: xfooxxxxxxfoo
正则expression式: 。* foo

上面的正则expression式有两个部分:
(我和
(ⅱ)“富”

下面的每个步骤将分析这两个部分。 对于“通过”或“失败”的匹配的附加注释在花括号中进行解释。

步骤1:
(i)。* = xfooxxxxxxfoo – PASS('。*'是一个贪婪的量词,将使用整个inputstring)
(ii)foo =在索引13之后没有字符匹配 – 失败
比赛失败。

第2步:
(i)。* = xfooxxxxxxfo – PASS(对贪婪量词'*'的回溯)
(ii)foo = o – 失败
比赛失败。

第3步:
(i)。* = xfooxxxxxxf – PASS(贪婪量词的回溯。*')
(ii)foo = oo – 失败
比赛失败。

步骤4:
(i)。* = xfooxxxxxx – PASS(贪婪量词的回溯。*')
(ii)foo = foo – PASS
报告MATCH

结果:1场比赛(es)
我发现文本“xfooxxxxxxfoo”从索引0开始到索引13结束。

不情愿 – 尽可能less地匹配不情愿的量词并匹配整个正则expression式。 如果不匹配,则将字符添加到不情愿的量词。

inputstring: xfooxxxxxxfoo
正则expression式: 。*?foo

上面的正则expression式有两个部分:
(一世) '。*?' 和
(ii)'foo'

步骤1:
。*? =''(空格) – PASS(尽可能less地与不情愿的量词匹配。“*?'。具有”'的索引0是匹配的。)
foo = xfo – FAIL(Cell 0,1,2 – 即0到3之间的索引)
比赛失败。

第2步:
。*? = x – PASS(将字符添加到不情愿的量词'*'中。具有'x'的单元0是匹配的。)
foo = foo – PASS
报告MATCH

第3步:
。*? =''(空白) – PASS(尽可能less地与不情愿的量词匹配“*?'。具有”'的索引4是匹配。
foo = xxx – FAIL(单元格4,5,6 – 即4到7之间的索引)
比赛失败。

步骤4:
。*? = x – PASS(将字符添加到不情愿的量词'*?'。单元格4.)
foo = xxx – FAIL(单元格5,6,7 – 即5和8之间的索引)
比赛失败。

第5步:
。*? = xx – PASS(将字符添加到不情愿的量词'。*?'。单元格4到5)
foo = xxx – FAIL(单元格6,7,8 – 即6和9之间的索引)
比赛失败。

第六步:
。*? = xxx – PASS(将字符添加到不情愿的量词'。*?'。单元格4到6)
foo = xxx – FAIL(Cell 7,8,9 – 即7到10之间的索引)
比赛失败。

第七步:
。*? = xxxx – PASS(将字符添加到不情愿的量词'。*?'。单元格4到7)
foo = xxf – FAIL(单元格8,9,10 – 即8和11之间的索引)
比赛失败。

第八步:
。*? = xxxxx – PASS(将字符添加到不情愿的量词'。*?'。单元格4到8)
foo = xfo – FAIL(单元格9,10,11 – 即9和12之间的索引)
比赛失败。

第9步:
。*? = xxxxxx – PASS(将字符添加到不情愿的量词'。*?'。单元格4到9)
foo = foo – PASS(单元格10,11,12 – 即10和13之间的索引)
报告MATCH

第10步:
。*? =''(空白) – PASS(尽可能less地与不情愿的量词匹配“*?'。索引13是空白的。)
foo =没有匹配的字符 – 失败(索引13之后没有任何匹配)
比赛失败。

结果:2场比赛(es)
我发现文本“xfoo”从索引0开始到索引4结束。
我发现文本“xxxxxxfoo”从索引4开始到索引13结束。

所有格 – 尽可能匹配所有格量词并匹配整个正则expression式。 不要回溯。

inputstring: xfooxxxxxxfoo
正则expression式: 。* + foo

上面的正则expression式有两个部分:'。* +'和'foo'。

步骤1:
* + = xfooxxxxxxfoo – PASS(尽可能匹配所有格量词“*”)
foo =没有匹配的字符 – 失败(索引13后没有匹配)
比赛失败。

注意:不允许回溯。

结果: 0比赛(es)

贪婪:“匹配最长的字符序列”

不情愿:“匹配尽可能短的字符序列”

所有者:这是有点奇怪,因为它不(贪婪和不情愿的对比)尝试find整个正则expression式匹配。

顺便说一句:没有正则expression式模式匹配器实现将永远使用回溯。 所有现实模式匹配器都非常快 – 几乎与正则expression式的复杂性无关!

贪婪量化涉及在迭代过程中使用string中所有未经validation的字符进行模式匹配。 未经validation的字符在活动序列中开始。 每当匹配没有发生时,最后的字符被隔离 ,再次执行检查。

当仅有主动序列满足正则expression式模式的主要条件时,将试图validation对照隔离的剩余条件。 如果此validation成功,则隔离区中的匹配字符将得到validation,而残留的不匹配字符将保持未validation状态,并将在下一次迭代中重新开始此过程时使用。

字符stream是从活动序列到隔离区的。 由此产生的行为是尽可能多地将原始序列包含在匹配中。

不情愿的量化与贪婪的资格大致相同,除了字符的stream动是相反的 – 也就是说,他们从隔离区开始stream入活动序列 。 由此产生的行为是尽可能less的原始序列包含在匹配中。

拥有量化不具有隔离,并包含固定活动序列中的所有内容