至少与Perl中的正则表达式匹配

时间:2012-03-17 21:33:21

标签: regex perl

我正在尝试将字符串(称为$word)与至少5个a和最多15个b匹配。我们如何使用正则表达式?

$word =~ /xxxxx/

是什么而不是xxxxx

2 个答案:

答案 0 :(得分:3)

以您的方式呈现,您不能轻易使用单个正则表达式。您可以使用tr///计算字符:

use v5.10;

my $word = 'aabaabaa';

my $a = $word =~ tr/a//;
my $b = $word =~ tr/b//;

say do {
    if( $a >= 5 and $b <= 15 ) { "Matched with $a a's and $b b's" }
    else                       { "Missed with $a a's and $b b's" }
    };

您可以在标量上下文中使用匹配运算符来沿着字符串移动并计算您找到的内容,但这并不令人满意:

use v5.10;

my $word = 'aabaabaa';
my %seen;

while( $word =~ m/(a|b)/g ) {
    $seen{$1}++;
    }

say do {
    my( $a, $b ) = @seen{ qw( a b ) };
    if( $a >= 5 and $b <= 15 ) { "Matched with $a a's and $b b's" }
    else                       { "Missed with $a a's and $b b's" }
    };

如果您不喜欢while,可以这样做:

my @matches = $word =~ m/(a|b)/g;
$seen{$_}++ for @matches;

使用这样的正则表达式可以让你找到tr///没有的多个字符或模式(虽然这不能处理模式重叠):

my @matches = $word =~ m/(cat|dog)/g;
$seen{$_}++ for @matches;

但是,我经常看到这个问题表现为寻找字符的运行,因此连续 a 连续不超过5次, b 连续不到15次秒。既然我认为你在做作业,我会告诉你这个问题,但我没有给你一个完整的解决方案。

那里有很多技巧,因为它很容易匹配最大数量的字符,但你需要确保周围的文字不会破坏问题。我将从 b 开始,但最多寻找5个,所以我不必输入那么多 b 。但这个数字并不重要。如果你有字符串bbbbbbb,你可以用三种方式匹配不超过五个 b bbbbb bb,b bbbbb b,和bb bbbbb 。你必须确保你的比赛周围的文字不是它不应该的。

您可能认为这很简单:

my $b_regex = qr/
    (?:
        (?<!b)
        (?:b{0,5})
        (?!b)
    )
    /;

然而,Perl使用NFA正则表达式引擎,这意味着它找到最左边,最长的匹配。最左边的部分是问题,因为这比最长的更重要。考虑字符串bbb......bbbbbb。 Perl将在开头匹配bbb部分,因为它是满足正则表达式的最左边部分。您可以使用可变宽度前瞻来修复它,该前瞻扫描字符串的其余部分,查找六个 b 的运行:

my $b_regex = qr/
    (?:
        (?<!b)
        (?:b{0,5})
        (?!b)
    )
    (?!.*b{5,})
    /sx;

有效!嗯,不,它没有。这只是展望未来。 Perl的NFA将沿着字符串浮动,寻找另一个匹配的地方。这个正则表达式失败了bbbbbb...bbb...bbbb,其中 b 的长期位于该正则表达式匹配的位置之前。

所以,你改变了计划。如果任何数量的 b 包含0(棘手!),您可以使用负向前瞻不匹配6个连续的 b 。这是锚定到字符串的开头,所以我们可以扫描整个字符串而不改变匹配位置(只需暂停一下):

my $b_regex = qr/
    \A
    (?!.*b{6})
    /sx;

如果你必须匹配至少一个 b ,我们可以对此有一个积极的前瞻。现在这个锚是有意义的。你扫描字符串寻找不合格的 b 运行,不要改变匹配位置,然后寻找至少一个 b

my $b_regex = qr/
    \A
    (?!.*b{6})
    (?=.*b)
    /sx;

现在你必须考虑 a 。这几乎很容易。诀窍是 a 的正确数量可以在 b 的匹配之前或之后出现。同样,您可以使用前瞻扫描字符串。你不需要做那么多的工作,因为你不是这样做的,如果有超过5个,那么任何5个都会这样做:

my $ab_regex = qr/
    \A
    (?!.*b{6})
    (?=.*b)
    (?=.*a{5})
    /sx;

现在,我已经给你留下了半个解决方案,因为我特别省略了一些部分,这些部分可以让你捕捉匹配的部分并知道它们在字符串中的位置。这只会告诉您模式匹配。

这些前瞻者还有另一个好处。您可以找到重叠匹配,因为您永远不会推进匹配位置,并且每个新前瞻都会扫描整个字符串。

答案 1 :(得分:2)

这是我使用的正则表达式:

if ($word =~ m/  # Match word having 5 A's min and 15 B's max.
            ^                       # Anchor to start of string.
            (?=(?:[^Aa]*[Aa]){5})   # Assert 5 A's minimum.
            (?!(?:[^Bb]*[Bb]){16})  # Assert 15 B's maximum.
            .*                      # Safe to match whole string.
    /sx) {
    # Successful match
} else {
    # Match attempt failed
}