5

我正在寻找一个行为如下的正则表达式:

输入:“你好世界。”

输出:he, el, ll, lo, wo, or, rl, ld

我的想法类似于

    while($string =~ m/(([a-zA-Z])([a-zA-Z]))/g) {
        print "$1-$2 ";
    }

但这有点不同。

4

5 回答 5

10

这很棘手。您必须捕获它,保存它,然后强制回溯。

你可以这样做:

use v5.10;   # first release with backtracking control verbs

my $string = "hello, world!";
my @saved;

my $pat = qr{
    ( \pL {2} )
    (?{ push @saved, $^N })
    (*FAIL)
}x;

@saved = ();
$string =~ $pat;
my $count = @saved;
printf "Found %d matches: %s.\n", $count, join(", " => @saved);

产生这个:

Found 8 matches: he, el, ll, lo, wo, or, rl, ld.

如果你没有 v5.10,或者你头疼,你可以使用这个:

my $string = "hello, world!";
my @pairs = $string =~ m{
  # we can only match at positions where the
  # following sneak-ahead assertion is true:
    (?=                 # zero-width look ahead
        (               # begin stealth capture
            \pL {2}     #       save off two letters
        )               # end stealth capture
    )
  # succeed after matching nothing, force reset
}xg;

my $count = @pairs;
printf "Found %d matches: %s.\n", $count, join(", " => @pairs);

这会产生与以前相同的输出。

但你可能还是会头疼。

于 2013-03-07T18:54:09.617 回答
5

无需“强制回溯”!

push @pairs, "$1$2" while /([a-zA-Z])(?=([a-zA-Z]))/g;

尽管您可能想要匹配任何字母而不是您指定的有限集。

push @pairs, "$1$2" while /(\pL)(?=(\pL))/g;
于 2013-03-07T19:13:26.790 回答
1

还有另一种方法。不使用任何正则表达式魔法,它确实使用了嵌套map的 s 但如果需要,这可以很容易地转换为for循环。

#!/usr/bin/env perl

use strict;
use warnings;

my $in = "hello world.";
my @words = $in =~ /(\b\pL+\b)/g;

my @out = map {
  my @chars = split '';
  map { $chars[$_] . $chars[$_+1] } ( 0 .. $#chars - 1 );
} @words;

print join ',', @out;
print "\n";

同样,对我来说,这比一个奇怪的正则表达式 YMMV 更具可读性。

于 2013-03-07T19:43:46.047 回答
0

我会group在前瞻中使用捕获的..

(?=([a-zA-Z]{2}))
    ------------
         |->group 1 captures two English letters 

在这里试试

于 2013-03-07T18:58:27.127 回答
0

您可以通过查找字母并使用pos函数来利用捕获的位置、\G在另一个正则表达式中引用它以及substr从字符串中读取一些字符来做到这一点。

use v5.10;
use strict;
use warnings;

my $letter_re = qr/[a-zA-Z]/;

my $string = "hello world.";
while( $string =~ m{ ($letter_re) }gx ) {
    # Skip it if the next character isn't a letter
    # \G will match where the last m//g left off.
    # It's pos() in a regex.
    next unless $string =~ /\G $letter_re /x;

    # pos() is still where the last m//g left off.
    # Use substr to print the character before it (the one we matched)
    # and the next one, which we know to be a letter.
    say substr $string, pos($string)-1, 2;
}

您可以使用零宽度肯定断言将“检查下一个字母”逻辑放入原始正则表达式中,(?=pattern). 零宽度意味着它不会被捕获并且不会推进m//g正则表达式的位置。这有点紧凑,但零宽度断言可能会变得棘手。

while( $string =~ m{ ($letter_re) (?=$letter_re) }gx ) {
    # pos() is still where the last m//g left off.
    # Use substr to print the character before it (the one we matched)
    # and the next one, which we know to be a letter.
    say substr $string, pos($string)-1, 2;
}

更新:我最初试图捕捉比赛和前瞻,m{ ($letter_re (?=$letter_re)) }gx但没有奏效。前瞻是零宽度并且滑出匹配。其他人的答案表明,如果您在前瞻中放置第二次捕获,那么它可能会崩溃到...

say "$1$2" while $string =~ m{ ($letter_re) (?=($letter_re)) }gx;

我将所有答案留给 TMTOWTDI,特别是如果您不是正则表达式大师。

于 2013-03-07T19:23:06.633 回答