5

次のように動作する正規表現を探しています。

入力:「HelloWorld」。

出力:he、el、ll、lo、wo、または、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

それを行うためのさらに別の方法。正規表現の魔法を使用せず、ネストされた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よりも読みやすくなっています。

于 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たが、それはうまくいきませんでした。先読みはゼロ幅であり、試合から外れます。他の人の答えは、先読みの中に2番目のキャプチャを入れると、それがちょうど...に崩壊する可能性があることを示しました。

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

特に正規表現マスターでない場合は、TMTOWTDIのすべての回答をここに残します。

于 2013-03-07T19:23:06.633 に答える