我正在尝试匹配句子中的可选(可能存在)短语:
perl -e '$_="word1 word2 word3"; print "1:$1 2:$2 3:$3\n" if m/(word1).*(word2)?.*(word3)/'
输出:
1:word1 2: 3:word3
我知道第一个'。*'是贪婪的,并且匹配'word3'的所有内容。让它不贪婪无济于事:
perl -e '$_="word1 word2 word3"; print "1:$1 2:$2 3:$3\n" if m/(word1).*?(word2)?.*(word3)/'
输出:
1:word1 2: 3:word3
这里似乎存在利益冲突。我以为Perl会匹配(word2)?如果可能的话,仍然满足于非贪婪。*?至少那是我对'?'的理解。 Perl正则表达式页面上写着'?'做1次或0次所以不应该更喜欢一个匹配而不是零?
如果我抓住。*?:
,更令人困惑的是perl -e '$_="word1 word2 word3"; print "1:$1 2:$2 3:$3 4:$4\n" if m/(word1)(.*?)(word2)?.*(word3)/'
输出:
1:word1 2: 3: 4:word3
这里的所有群组都是捕捉群体,因此我不知道他们为什么是空的。
只是为了确保没有捕获字间空间:
perl -e '$_="word1_word2_word3"; print "1:$1 2:$2 3:$3 4:$4\n" if m/(word1)(.*?)(word2)?.*(word3)/'
输出:
1:word1 2: 3: 4:word3
鉴于唯一不匹配的匹配是word2和word3之间的匹配,我只能假设它是进行匹配的那个。 果然:
perl -e '$_="word1_word2_word3"; print "1:$1 2:$2 3:$3 4:$4 5:$5\n" if m/(word1)(.*?)(word2)?(.*)(word3)/'
输出:
1:word1 2: 3: 4:_word2_ 5:word3
所以贪婪匹配正在向后工作,Perl很乐意匹配word2的零(而不是一个)实例。让它变得非贪婪也无济于事。
所以我的问题是:如何编写我的正则表达式以匹配并捕获句子中的可能短语?我在这里给出的例子很简单;我正在解析的实际句子要长得多,在我匹配的那些之间有很多单词,所以我不能假设任何长度或组成的介入文本。
非常感谢, 斯科特
答案 0 :(得分:4)
你需要了解贪婪和懒惰的量词是如何工作的。贪婪的人会抓住他们的模式可以立即匹配的文本,然后引擎将回溯,即它会尝试返回到贪婪量化的子模式匹配子字符串的地方,试图检查是否下一个子模式可以匹配。
延迟匹配模式首先匹配最小字符,然后尝试与其他子模式匹配。使用*?
,它匹配零字符,一个空格,然后继续检查是否可以匹配下一个模式,并且只有在它不能匹配时,才会“扩展”延迟子模式“包括一个角色,等等。
因此,(word1).*(word2)?.*(word3)
会将word2
与第一个.*
匹配(第二个.*
会匹配一个空格,因为第一个.*
是贪婪的虽然你可以认为(word2)?
是贪婪的,因此必须回溯到,但答案是否定的,因为第一个.*
抓住了所有的字符串,然后引擎向后寻找匹配。由于(word2)?
匹配空字符串,因此它始终匹配,word3
首先匹配字符串的结尾。请参阅this demo并检查 regex调试器部分
你想,让我们使用与第一个.\*?
进行懒惰匹配。 (word1).*?(word2)?.*(word3)
(将word2
与贪婪的第二个.*
匹配)的问题略有不同,因为可能与可选组相匹配。怎么样?第一个.*?
匹配零个字符,然后尝试匹配所有后续子模式。因此,它找到word1
,然后是空字符串,在word2
之后找不到word1
。 word2
如果word1
在.*?
之后,则会与第一个(?:(?!word2).)*
匹配。请参阅this demo。
我现在看到有两个解决方案,它们都包含使第二个可选组对于模式的其余部分“独占”,以便正则表达式引擎在找到时不能跳过它。
(word1)(?:(?!word2).)*(word2)?.*?(word3)
。它效率低于分支重置解决方案,但可以移植到JS,Python和支持前瞻的大多数其他正则表达式。这是如何运作的? /s
匹配除了换行符(除了word2
之外的任何字符的0次出现,甚至包括换行符),该换行符不会启动文字字符序列w
。如果ord2
匹配,则word2
不能跟随构造匹配。因此,当它到达(word2)?
时,它会停止并让后续子模式 - word2
- 匹配并捕获以下$("body").on("click","#nice2Know", function(){
window.location ="URLString";
return false;
});
。 *要提高此方法效率**,请使用unroll the loop technique:(word1)[^w]*(?:w(?!ord2)[^w]*)*(word2)?.*?(word3)
。答案 1 :(得分:1)
您可以使用分支重置构造作为解决方法:
(word1)(?|.*?(word2).*?(word3)|().*?(word3))
#^ ^ ^ ^ ^---- group 3
#| | | '--------- group 2
#| | '----------------- group 3
#| '--------------------------- group 2
#'---------------------------------------- group 1
分支重置组(?|...()...()|...()...())
的主要兴趣是捕获组在每个分支中具有相同的编号。您可以使用第一个分支,其中该组是必需的,而第二个分支是空的(或者您可以使用始终失败的模式填充它并在之后添加?
,而不是使第2组成为可选的它)
答案 2 :(得分:0)
为了解决您的问题,您必须观察到正则表达式中的catch-all子表达式与您不希望它们匹配的材料:
(word1).*(word2)?.*(word3)
--
^--- this subexpression matches _all_ material between `word1` and `word3` in the test string, in particular `word2` if it is present
(word1).*? (word2)? .*(word3)
---+--------+--
^ ^ ^-- this subexpression matches _all_ material between `word1` and `word3` in the test string, in particular `word2` if it is present
| |
| +------ this subexpression is empty, even if `word2` is present:
| - the preceding subexpression `.*?` matches minimally (ie. the empty string)
| - `(word2)?` cannot match for the preceding blank.
| - the following subexpression `.*` matches everything up to `word3`, including `word2`.
|
| -> the pattern matches _as desired_ for test strings
| where `word2` immediately follows `word1` without
|
+-------------- this subexpression will always be empty
您需要的是一种构造,可以防止catch-all匹配包含word2
的字符串。幸运的是,perl的正则表达式语法体现了用于此目的的负面观察:对于catch-all子表达式匹配中的每个字符,请确保它不在word2
之前。
在perl:
/(word1).*(word2).*(word3)|word1((?<!word2).)*word3/
<强> 注意事项 强>
word2
必须是文字,因为正则表达式引擎仅支持具有先验已知匹配长度的模式。替代解决方案
鉴于警告,您可能会尝试更改控制逻辑:
$teststring = $_;
if ($teststring =~ m/(word1).*(word2).*(word3)/) {
print \"1:$1 2:$2 3:$3\n\";
}
else {
# You know by now that there is no word2 between any word1, word3 occurrences
if ($teststring =~ m/(word1).*(word3)/) {
print \"1:$1 2:- 3:$2\n\";
}
}