我在正则表达式上发现了这个excellent tutorial,虽然我直观地理解了“贪婪”,“不情愿”和“占有欲”量词所做的事情,但我的理解似乎存在严重漏洞。
具体来说,在以下示例中:
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.
说明提到吃整个输入字符串,字母消耗,匹配退出,最右边出现的“foo”已经反刍等。
不幸的是,尽管有很好的比喻,我仍然不明白被谁吃掉了...你知道另一个教程(简明地)解释 正则表达式引擎是如何工作的吗?
或者,如果某人能够在不同的语言中解释以下段落,那将非常感激:
第一个例子使用贪心 量词。*找到“任何东西”,零 或更多次,然后是字母 “f”“o”“o”。因为量词是 贪婪的。*部分 表达首先吃掉整个输入 串。此时,整体而言 表达不能成功,因为 最后三个字母(“f”“o”“o”)有 已被消费(由谁?)。所以匹配器 慢慢退出(从右到左?)一次一个字母 直到最右边的发生 “foo”已被反刍(这意味着什么?),在此 指向匹配成功和 搜索结束。
然而,第二个例子是 不情愿,所以它首先开始 消费(由谁?)“没什么”。因为“foo” 没有出现在开头 字符串,它被迫吞下(谁吞下?) 第一个字母(“x”),触发 在0和4的第一场比赛。我们的测试 线束继续这个过程,直到 输入字符串已用尽。它 在4和13找到另一场比赛。
第三个例子找不到 匹配,因为量词是 所有格。在这种情况下,整个 输入字符串由。* +,(如何?)消耗 什么都不留下来满足 最后的“foo” 表达。使用占有欲 对于你的情况量词 想要抓住所有的东西 永远退缩(退回意味着什么?);它会超越 等价的贪心量词 不匹配的情况 马上找到了。
答案 0 :(得分:444)
我会试一试。
贪心量词首先尽可能匹配。所以.*
匹配整个字符串。然后匹配器尝试匹配f
跟随,但没有剩下的字符。因此它“回溯”,使得贪婪的量词匹配少一点(在字符串末尾留下“o”不匹配)。这仍然与正则表达式中的f
不匹配,因此它“再回溯”了一步,使得贪婪量词再次匹配一件事(在字符串末尾留下“oo”不匹配)。那个仍然与正则表达式中的f
不匹配,因此它会再拖回一步(在字符串末尾留下“foo”不匹配)。现在,匹配器最终匹配正则表达式中的f
,o
和下一个o
也匹配。成功了!
不情愿或“非贪婪”量词首先尽可能少地匹配。所以.*
最初没有匹配任何内容,使整个字符串无法匹配。然后匹配器尝试匹配f
跟随,但字符串的不匹配部分以“x”开头,因此不起作用。因此匹配器回溯,使非贪婪量词匹配更多的东西(现在它匹配“x”,留下“fooxxxxxxfoo”无法比拟)。然后,它会尝试匹配成功的f
,以及正则表达式匹配中的o
和下一个o
。成功了!
在您的示例中,它然后按照相同的过程开始处理字符串的剩余不匹配部分。
占有量词就像贪婪的量词,但它没有回溯。所以它始于.*
匹配整个字符串,没有任何不匹配的东西。然后没有什么可以与正则表达式中的f
匹配。由于占有量词没有回溯,因此匹配失败。
答案 1 :(得分:37)
答案 2 :(得分:24)
答案 3 :(得分:19)
http://swtch.com/~rsc/regexp/regexp1.html
我不确定这是互联网上最好的解释,但它的编写得相当好,而且相当详细,我会不断回过头来。你可能想看一下。
如果你想要更高级别(不太详细的解释),对于简单的正则表达式,例如你正在查看的正则表达式,正则表达式引擎通过回溯来工作。从本质上讲,它选择(“吃掉”)字符串的一部分,并尝试将正则表达式与该部分进行匹配。如果它匹配,那很好。如果没有,引擎会改变它对字符串部分的选择,并尝试将regexp与该部分匹配,依此类推,直到尝试了所有可能的选择。
此过程以递归方式使用:在尝试将字符串与给定正则表达式匹配时,引擎会将正则表达式拆分为多个部分并将算法单独应用于每个部分。
当引擎选择要尝试匹配的字符串的哪个部分时,贪婪,不情愿和占有量词之间的区别进入,以及如果第一次不起作用,如何修改该选项。规则如下:
贪婪的量词告诉引擎以整个字符串开头(至少是所有尚未与正则表达式前面部分匹配的字符串)并检查是否与正则表达式匹配。如果是这样,很好;引擎可以继续使用regexp的其余部分。如果没有,它会再次尝试,但会修剪要检查的字符串部分的一个字符(最后一个字符)。如果这不起作用,它会削减另一个角色,等等。因此,贪婪的量词会按照从最长到最短的顺序检查可能的匹配。
一个不情愿的量词告诉引擎以最短的字符串开始。如果匹配,引擎可以继续;如果没有,它将一个字符添加到被检查字符串的部分并尝试该字符,依此类推,直到找到匹配或整个字符串已用完为止。因此,一个不情愿的量词从最短到最长的顺序检查可能的匹配。
占有量词在第一次尝试时就像一个贪婪的量词:它通过检查整个字符串告诉引擎启动。不同之处在于,如果它不起作用,占有量词会报告当时和那里的匹配失败。引擎不会更改正在查看的字符串部分,并且不再进行任何尝试。
这就是为什么占有量词匹配在您的示例中失败的原因:.*+
会针对匹配的整个字符串进行检查,但之后引擎继续寻找其他字符foo
之后 - 但当然它找不到它们,因为你已经在字符串的末尾了。如果它是一个贪婪的量词,它会回溯并尝试使.*
仅匹配倒数第二个字符,然后是第三个到最后一个字符,然后是第四个到最后一个字符,成功,因为只有在foo
“吃掉”字符串的前一部分之后才剩下.*
。
答案 4 :(得分:12)
以下是我使用细胞和指数位置的看法(参见diagram here区分细胞和指数)。
贪婪 - 尽可能地与贪婪量词和整个正则表达式匹配。如果没有匹配,请回顾贪心量词。
输入字符串: xfooxxxxxxfoo
正则表达式:。* foo
以上 Regex 有两部分:
(ⅰ)' *'和
(ⅱ)'富'
以下每个步骤都将分析这两个部分。对于通过'匹配的其他评论或者'失败'在括号内解释。
步骤1:
(i)。* = xfooxxxxxxfoo - PASS('。*'是一个贪婪的量词,将使用整个输入字符串)
(ii)foo =指数13后没有任何字符匹配 - 失败
比赛失败。
步骤2:
(i)。* = xfooxxxxxxfo - PASS(回溯贪婪的量词'。*')
(ii)foo = o - FAIL
比赛失败。
步骤3:
(i)。* = xfooxxxxxxf - PASS(回溯贪婪的量词'。*')
(ii)foo = oo - FAIL
比赛失败。
步骤4:
(i)。* = xfooxxxxxx - PASS(回溯贪婪的量词'。*')
(ii)foo = foo - 通过
报告MATCH
结果:1场比赛
我找到了文字" xfooxxxxxxfoo"从索引0开始到索引13结束。
不情愿 - 尽可能少地与不情愿的量词匹配并匹配整个正则表达式。如果没有匹配项,请将字符添加到不情愿的量词中。
输入字符串: xfooxxxxxxfoo
正则表达式:。*?foo
上述正则表达式有两部分:
(i)'。*?'和
(ii)' foo'
步骤1:
。*? ='' (空白) - 通过(尽可能少地与不情愿的量词匹配'。*?'。索引0有''是匹配。)
foo = xfo - FAIL(单元格0,1,2-即0到3之间的索引)
比赛失败。
步骤2:
。*? = x - PASS(将字符添加到不情愿的量词'。*?'。单元格0具有' x'是匹配。)
foo = foo - 通过
报告MATCH
步骤3:
。*? ='' (空白) - 通过(尽可能少地与不情愿的量词匹配'。*?'。索引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之间)
比赛失败。
步骤6:
。*? = xxx - PASS(将字符添加到不情愿的量词'。*?'。单元格4到6.)
foo = xxx - FAIL(单元格7,8,9-即7和10之间的索引)
比赛失败。
步骤7:
。*? = xxxx - PASS(将字符添加到不情愿的量词'。*?'。Cell 4到7.)
foo = xxf - FAIL(单元格8,9,10-即索引在8和11之间)
比赛失败。
步骤8:
。*? = 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:
。*? ='' (空白) - 通过(尽可能少地与不情愿的量词匹配'。*?'。索引13为空白。)
foo =没有匹配的字符 - 失败(索引13之后没有任何内容匹配)
比赛失败。
结果:2场比赛
我找到了文字" xfoo"从索引0开始到索引4结束
我找到了文字" xxxxxxfoo"从索引4开始,到索引13结束。
占有 - 尽可能匹配占有量,并匹配整个正则表达式。不要回溯。
输入字符串: xfooxxxxxxfoo
正则表达式:。* + foo
以上正则表达式分为两部分:'。* +'和' foo'。
步骤1:
。* + = xfooxxxxxxfoo - PASS(尽可能与占有量词匹配'。*')
foo =没有匹配的字符 - FAIL(索引13之后无法匹配)
比赛失败。
注意:不允许回溯。
结果: 0匹配
答案 5 :(得分:0)
贪心:“匹配最长的字符序列”
不情愿:“匹配最短的字符序列”
占有:这有点奇怪,因为它没有(与贪婪和不情愿相反)试图找到整个正则表达式的匹配。
顺便说一句:没有正则表达式模式匹配器实现将使用回溯。所有真实模式匹配器都非常快 - 几乎与正则表达式的复杂性无关!
答案 6 :(得分:0)
贪婪量化涉及在迭代期间使用字符串的所有剩余未经验证的字符进行模式匹配。未经验证的字符从活动序列开始。每次匹配都没有发生时,最后的字符被隔离并再次执行检查。
当活动序列仅满足正则表达式的前导条件时,尝试根据隔离区验证剩余条件。如果此验证成功,则隔离区中的匹配字符将得到验证,剩余的不匹配字符将保持未经验证,并将在下一次迭代中重新开始该过程时使用。
字符流是从活动序列进入隔离区。由此产生的行为是尽可能多地将原始序列包含在匹配中。
不情愿的量化与贪婪的资格大致相同,除了字符流相反 - 也就是说,它们从隔离区开始并流入主动序列。由此产生的行为是尽可能少的原始序列包含在匹配中。
占有量化没有隔离,并且包含固定活动序列中的所有内容。