负面前瞻正则表达式贪婪(为什么。*?太贪心)

时间:2011-05-25 03:11:44

标签: python regex perl regex-lookarounds

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

  

(?!REGEX_1)REGEX_2

     

仅在REGEX_1不匹配时匹配;检查REGEX_1后,搜索REGEX_2的位置相同。

希望我理解算法,我做了两句话侮辱;我想找一个没有一个字的句子。具体地说...

  

侮辱:'Yomama很难看。而且,她闻起来像一只湿狗。“

     

要求

     
      
  • 测试1:没有“丑陋”的句子。
  •   
  • 测试2:返回没有“看起来”的句子。
  •   
  • 测试3:返回没有'闻起来'的句子。
  •   

我将测试字分配给$arg,并使用(?:(?![A-Z].*?$arg.*?\.))([A-Z].*?\.)来实施测试。

  • (?![A-Z].*?$arg.*?\.)是拒绝使用测试词
  • 的句子的否定先行
  • ([A-Z].*?\.)匹配至少一句话。

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

预期结果

  
      
  • 测试1($ arg =“丑陋”):“而且,她闻起来像一只湿狗。”
  •   
  • 测试2($ arg =“看起来”):“Yomama很难看。”
  •   
  • 测试3($ arg =“闻起来”):“Yomama很难看。”
  •   

实际结果

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

起初我认为测试3失败了,因为([A-Z].*?\.)过于贪婪并且匹配两个句子;但是,(?:(?![A-Z].*?$arg.*?\.))([A-Z][^\.]*?\.)也没有用。接下来我想知道python否定前瞻实现是否存在问题,但perl给了我完全相同的结果。

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

问题

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

修改

根据@ bvr关于使用-Mre=debug的优秀建议,我会在下班后再考虑这个问题。看起来Seth的描述在这一点上看起来很准确。到目前为止我学到的是,即使我把非贪婪的.*?运算符放在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.'

3 个答案:

答案 0 :(得分: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.'

答案 1 :(得分:3)

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

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

这会产生很多输出供你思考。你需要一个用-DDEBUGGING构建的Perl。

答案 2 :(得分:2)

你的问题是正则表达式引擎会尽可能地努力匹配(?![A-Z].*?$arg.*?\.),所以对于“气味”情况,它最终匹配整个字符串。 (然后将中间的句点包含在.*?构造之一中。)您应该将负前瞻案例限制为仅与其他案例相同:

而不是:

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

使用:

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

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