20

我无法理解负前瞻正则表达式的细节。在阅读了Regex lookahead、lookbehind 和 atomic groups之后,当我发现这个描述时,我以为我对负前瞻有了一个很好的总结:

(?!REGEX_1)REGEX_2

仅当REGEX_1不匹配时才匹配;检查后REGEX_1,搜索REGEX_2从同一位置开始。

希望我理解算法,我编造了一个两句测试侮辱;我想找到没有某个词的句子。具体来说...

侮辱: 'Yomama很丑。而且,她闻起来像一条湿狗。

要求

  • 测试 1:返回一个没有“丑陋”的句子。
  • 测试 2:返回一个没有“looks”的句子。
  • 测试 3:返回一个没有“气味”的句子。

我将测试词分配给$arg,并用于(?:(?![A-Z].*?$arg.*?\.))([A-Z].*?\.)执行测试。

  • (?![A-Z].*?$arg.*?\.)是否定前瞻来拒绝带有测试词的句子
  • ([A-Z].*?\.)至少匹配一个句子。

关键部分似乎在于理解正则表达式引擎在处理负前瞻后开始匹配的位置。

预期结果

  • 测试 1 ($arg = "ugly"):“而且,她闻起来像一条湿狗。”
  • 测试 2 ($arg = "looks"): "Yomama 很丑。"
  • 测试 3 ($arg = "smells"): "Yomama 很丑。"

实际结果

  • 测试 1 ($arg = "ugly"):“而且,她闻起来像一条湿狗。” (成功)
  • 测试 2 ($arg = "looks"): "Yomama 很丑。" (成功)
  • 测试 3 ($arg = "smells"):失败,不匹配

一开始我以为Test 3失败([A-Z].*?\.)是因为太贪心,两个句子都匹配;但是,(?:(?![A-Z].*?$arg.*?\.))([A-Z][^\.]*?\.)也没有用。接下来我想知道python负前瞻实现是否有问题,但perl给了我完全相同的结果。

最后我找到了解决方案,我不得不.*?通过使用来拒绝我的部分表达式中的句点[^\.]*?;所以这个正则表达式有效:(?:(?![A-Z][^\.]*?$arg[^\.]*?\.))([A-Z][^\.]*?\.)

问题

但是,我还有另一个担心;“Yomama很丑。” 里面没有“气味”。那么,如果.*?应该是非贪婪匹配,为什么我不能用 完成测试 3 (?:(?![A-Z].*?$arg.*?\.))([A-Z].*?\.)

编辑

鉴于@bvr 对使用的极好建议-Mre=debug,我会在下班后考虑更多。看来赛斯的描述在这一点上是准确的。到目前为止,我学到的是负前瞻表达式将尽可能匹配,即使我.*?在 NLA 中放置了非贪婪运算符。


Python 实现

import re

def test_re(arg, INSULTSTR):
    mm = re.search(r'''
        (?:                  # No grouping
        (?![A-Z].*?%s.*?\.)) # Negative zero-width
                             #     assertion: arg, followed by a period
        ([A-Z].*?\.)         # Match a capital letter followed by a period
        ''' % arg, INSULTSTR, re.VERBOSE)
    if mm is not None:
        print "neg-lookahead(%s) MATCHED: '%s'" % (arg, mm.group(1))
    else:
        print "Unable to match: neg-lookahead(%s) in '%s'" % (arg, INSULTSTR)


INSULT = 'Yomama is ugly.  And, she smells like a wet dog.'
test_re('ugly', INSULT)
test_re('looks', INSULT)
test_re('smells', INSULT)

Perl 实现

#!/usr/bin/perl

sub test_re {
    $arg    = $_[0];
    $INSULTSTR = $_[1];
    $INSULTSTR =~ /(?:(?![A-Z].*?$arg.*?\.))([A-Z].*?\.)/;
    if ($1) {
        print "neg-lookahead($arg) MATCHED: '$1'\n";
    } else {
        print "Unable to match: neg-lookahead($arg) in '$INSULTSTR'\n";
    }
}

$INSULT = 'Yomama is ugly.  And, she smells like a wet dog.';
test_re('ugly', $INSULT);
test_re('looks', $INSULT);
test_re('smells', $INSULT);

输出

neg-lookahead(ugly) MATCHED: 'And, she smells like a wet dog.'
neg-lookahead(looks) MATCHED: 'Yomama is ugly.'
Unable to match: neg-lookahead(smells) in 'Yomama is ugly.  And, she smells like a wet dog.'
4

3 回答 3

3
#!/usr/bin/perl

sub test_re {
    $arg    = $_[0];
    $INSULTSTR = $_[1];
    $INSULTSTR =~ /(?:^|\.\s*)(?:(?![^.]*?$arg[^.]*\.))([^.]*\.)/;
    if ($1) {
        print "neg-lookahead($arg) MATCHED: '$1'\n";
    } else {
        print "Unable to match: neg-lookahead($arg) in '$INSULTSTR'\n";
    }
}

$INSULT = 'Yomama is ugly.  And, she smells like an wet dog.';
test_re('Yomama', $INSULT);
test_re('ugly', $INSULT);
test_re('looks', $INSULT);
test_re('And', $INSULT);
test_re('And,', $INSULT);
test_re('smells', $INSULT);
test_re('dog', $INSULT);

结果:

neg-lookahead(Yomama) MATCHED: 'And, she smells like an wet dog.'
neg-lookahead(ugly) MATCHED: 'And, she smells like an wet dog.'
neg-lookahead(looks) MATCHED: 'Yomama is ugly.'
neg-lookahead(And) MATCHED: 'Yomama is ugly.'
neg-lookahead(And,) MATCHED: 'Yomama is ugly.'
neg-lookahead(smells) MATCHED: 'Yomama is ugly.'
neg-lookahead(dog) MATCHED: 'Yomama is ugly.'
于 2011-05-25T03:44:56.160 回答
3

如果您对 Perl 对正则表达式的作用感到好奇,您可以使用正则表达式调试器运行:

perl -Dr -e '"A two. A one." =~ /(?![A-Z][^\.]*(?:two)[^\.]*\.)([A-Z][^\.]+\.)/; print ">$1<\n"'

这将产生很多输出供您思考。您将需要使用 -DDEBUGGING 构建的 Perl。

于 2011-05-25T04:55:50.010 回答
2

您的问题是正则表达式引擎会尽可能地尝试匹配(?![A-Z].*?$arg.*?\.),因此对于“气味”的情况,它最终会匹配整个字符串。(然后中间的句点包含在其中一个.*?结构中。)您应该限制负前瞻情况,使其仅与另一种情况尽可能匹配:

代替:

(?:(?![A-Z].*?$arg.*?\.))([A-Z].*?\.)

采用:

(?:(?![A-Z][^.]*$arg[^.]*\.))([A-Z].*?\.)

现在,负前瞻不能比其他部分匹配更多的字符串,因为它必须在第一个句点处停止。

于 2011-05-25T04:19:49.250 回答