次のように動作する正規表現を探しています。
入力:「HelloWorld」。
出力:he、el、ll、lo、wo、または、rl、ld
私のアイデアは
while($string =~ m/(([a-zA-Z])([a-zA-Z]))/g) {
print "$1-$2 ";
}
しかし、それは少し違うことをします。
次のように動作する正規表現を探しています。
入力:「HelloWorld」。
出力:he、el、ll、lo、wo、または、rl、ld
私のアイデアは
while($string =~ m/(([a-zA-Z])([a-zA-Z]))/g) {
print "$1-$2 ";
}
しかし、それは少し違うことをします。
トリッキーです。それをキャプチャして保存してから、強制的にバックトラックする必要があります。
あなたはこのようにそれを行うことができます:
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);
これにより、以前と同じ出力が生成されます。
しかし、まだ頭痛がするかもしれません。
「バックトラックを強制する」必要はありません!
push @pairs, "$1$2" while /([a-zA-Z])(?=([a-zA-Z]))/g;
指定した限定セットではなく、任意の文字に一致させたい場合があります。
push @pairs, "$1$2" while /(\pL)(?=(\pL))/g;
それを行うためのさらに別の方法。正規表現の魔法を使用せず、ネストされたsを使用しますが、必要に応じてこれをループmap
に簡単に変換できます。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よりも読みやすくなっています。
group
先読みでキャプチャを使用します。
(?=([a-zA-Z]{2}))
------------
|->group 1 captures two English letters
ここで試してみてください
これを行うには、文字を検索し、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
たが、それはうまくいきませんでした。先読みはゼロ幅であり、試合から外れます。他の人の答えは、先読みの中に2番目のキャプチャを入れると、それがちょうど...に崩壊する可能性があることを示しました。
say "$1$2" while $string =~ m{ ($letter_re) (?=($letter_re)) }gx;
特に正規表現マスターでない場合は、TMTOWTDIのすべての回答をここに残します。