正则表达式益智游戏

Regular expression puzzler

我做正则表达式已经超过 25 年了,但我不明白为什么这个正则表达式不匹配(使用 Perl 语法):

"unify" =~ /[iny]{3}/
# as in
perl -e 'print "Match\n" if "unify" =~ /[iny]{3}/'

谁能帮忙解开这个谜语?

字母 niy 并不都是相邻的。它们之间有一个f

/[iny]{3}/ 匹配任何包含从集合 {i, n, y} 中提取的三个字母的子字符串的字符串。这些字母可以按任何顺序排列;它们甚至可以重复。

三次选择三个字符,加上替换,意味着有33 = 27个匹配的子串:

  • iiiiiniiyiniinninyiyiiyn, iyy
  • niininniynninnnnnynyinyn, nyy
  • yiiyinyiyyniynnynyyyiyyn, yyy

要匹配不相邻的字母,您可以使用其中一种:

  • [iny].*[iny].*[iny]
    
  • [iny](.*[iny]){2}
    
  • ([iny].*){3}
    

(最后一个选项本身可以正常工作,因为您的搜索未锚定,但可能不适合作为更大的正则表达式的一部分。最后的 .* 可能比您预期的匹配更多。)

该模式查找字母 iny 的三个 连续 次出现。您没有 连续 次出现。

也许您打算使用 [inf][ify]

{3} 原子表示“前一个元素的三个连续匹配项”。虽然字符 class 中的所有字母都出现在字符串中,但它们不是连续的,因为它们被字符串中的其他字符分隔开。

问题不在于字符 class 中项目的顺序。事实上,您无法匹配字符 class 中三个字母的任意组合,其中恰好三个字母在您的示例字符串中彼此直接相邻。

看起来您正在寻找 3 个连续的字母,因此您的不匹配

[iny]{3} //no match
[unf]{3} //no match
[nif]{3} //matches nif
[nify]{3} //matches nif
[ify]{3} //matches ify
[uni]{3} //matches uni

希望有所帮助:)

模式[iny]{3}中的quantifier{3}表示用该模式匹配一​​个字符(iny),然后是另一个具有相同模式的字符,然后是另一个。三——一个接着一个。所以你的字符串 unify 没有那个,但最多可以召集两个 ni.

这已经在其他答案中解释过了。我想添加的是对评论中澄清的回答:如何检查这些字符在字符串中出现 3 次,随意散布。除了匹配整个子字符串,如前所示,我们还可以使用 lookahead:

(?=[iny].*[iny].*[iny])

这不会“消耗”任何字符,而是“寻找”模式的前方,而不是从当前位置推进引擎。因此,它作为子模式非常有用,可以与更大的正则表达式中的其他模式结合使用。

Perl 示例,复制粘贴到命令行:

perl -wE'say "Match" if "unify" =~ /(?=[iny].*[iny].*[iny])/'

这样做的缺点以及消耗整个此类子字符串的缺点是所有三个子模式的字面拼写;什么时候需要动态决定数量?或者十二点的时候?当然,该模式可以在运行时构建。在 Perl 中,一种方式

my $pattern = '(?=' . join('.*', ('[iny]')x3) . ')';

然后在正则表达式中使用它。


为了性能,对于长字符串和多次重复,使.*非贪婪

(?=[iny].*?[iny].*?[iny])

(形成模式时用.*?动态连接)

用于说明的简单基准(在 Perl 中)

use warnings;
use strict;
use feature 'say';

use Getopt::Long;
use List::Util qw(shuffle);
use Benchmark qw( cmpthese );

# For how many seconds to run each option (-r N, default 3), 
# how many times to repeat for the test string (-n N, default 2)
my ($runfor, $n) = (3, 2);
GetOptions('r:i' => $runfor, 'n:i' => $n);

my $str = 'aa'
    . join('', map { (shuffle 'b'..'t')x$n, 'a' } 1..$n)
    . 'a'x($n+1) 
    . 'zzz'; 
    
my $pat_greedy     = '(?=' . join('.*',  ('a')x$n) . ')';
my $pat_non_greedy = '(?=' . join('.*?', ('a')x$n) . ')';
#my $pat_greedy     = join('.*',  ('a')x$n);  # test straight match,
#my $pat_non_greedy = join('.*?', ('a')x$n);  # not lookahead

sub match_repeated {
    my ($s, $pla) = @_;
    return ( $s =~ /$pla(.*z)/ ) ? "match" : "no match";
}   

cmpthese(-$runfor, {
    greedy     => sub { match_repeated($str, $pat_greedy) },
    non_greedy => sub { match_repeated($str, $pat_non_greedy) },
}); 

(可能不需要改组该字符串,但我担心优化会干扰。)

当用因子 20 (program.pl -n 20) 构成字符串时,输出为

              Rate     greedy non_greedy
greedy      56.3/s         --      -100%
non_greedy 90169/s    159926%         --

所以...一些 1600 次 更好的非贪婪。该测试字符串长 7646 个字符,要匹配的模式有 20 个子模式 (a),它们之间有 .*(在贪婪的情况下);所以那里发生了很多事情。默认为 2,因此对于短字符串和更简单的模式,差异为 10%.

顺便说一句,要测试直接匹配(不使用前瞻),只需将那些注释符号移动到模式变量周围,它的糟糕程度几乎是原来的两倍:

               Rate     greedy non_greedy
greedy       56.5/s         --      -100%
non_greedy 171949/s    304117%         --