Perl正则表达式匹配较长句子中的可选短语

时间:2016-04-21 13:16:08

标签: regex perl

我正在尝试匹配句子中的可选(可能存在)短语:

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的零(而不是一个)实例。让它变得非贪婪也无济于事。

所以我的问题是:如何编写我的正则表达式以匹配并捕获句子中的可能短语?我在这里给出的例子很简单;我正在解析的实际句子要长得多,在我匹配的那些之间有很多单词,所以我不能假设任何长度或组成的介入文本。

非常感谢, 斯科特

3 个答案:

答案 0 :(得分:4)

背景:懒惰和贪婪的量化者如何工作

你需要了解贪婪和懒惰的量词是如何工作的。贪婪的人会抓住他们的模式可以立即匹配的文本,然后引擎将回溯,即它会尝试返回到贪婪量化的子模式匹配子字符串的地方,试图检查是否下一个子模式可以匹配。

延迟匹配模式首先匹配最小字符,然后尝试与其他子模式匹配。使用*?,它匹配字符,一个空格,然后继续检查是否可以匹配下一个模式,并且只有在它不能匹配时,才会“扩展”延迟子模式“包括一个角色,等等。

因此,(word1).*(word2)?.*(word3)会将word2与第一个.*匹配(第二个.*会匹配一个空格,因为第一个.*是贪婪的虽然你可以认为(word2)?是贪婪的,因此必须回溯到,但答案是否定的,因为第一个.*抓住了所有的字符串,然后引擎向后寻找匹配。由于(word2)?匹配空字符串,因此它始终匹配,word3首先匹配字符串的结尾。请参阅this demo并检查 regex调试器部分

你想,让我们使用与第一个.\*? 进行懒惰匹配。 (word1).*?(word2)?.*(word3)(将word2与贪婪的第二个.*匹配)的问题略有不同,因为可能与可选组相匹配。怎么样?第一个.*?匹配零个字符,然后尝试匹配所有后续子模式。因此,它找到word1,然后是空字符串,word2 之后找不到word1 word2如果word1.*?之后,则会与第一个(?:(?!word2).)*匹配。请参阅this demo

我现在看到有两个解决方案,它们都包含使第二个可选组对于模式的其余部分“独占”,以便正则表达式引擎在找到时不能跳过它。

  • 上面有一个branch reset solution provided by Casimir。它的缺点是它不能移植到许多其他不支持分支重置的正则表达式。请参阅原始答案中的说明。
  • 使用tempered greedy token(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/

<强> 注意事项

  1. 这可能是一场表演难题。
  2. 请注意,word2必须是文字,因为正则表达式引擎仅支持具有先验已知匹配长度的模式。
  3. 替代解决方案

    鉴于警告,您可能会尝试更改控制逻辑:

    $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\";
        }
    }