Perlでマッチした数を数えるにはどうすればいいかという話がある。
“123 51 5 65 25 14 25 12 54 65″の中にどれだけ\d+という正規表現にマッチするのがあるかということ。
今までどうやって調べればいいか知らんかったのよね。
で、どうも複数置換したときの返り値が置換した個数だというから、こうすればいいかなと。
print $s=~s/\d+/$&/g,"\n";
さっきのを$sに入れておくと、10と表示されるから、10個マッチすると言うことだな。
gを付けなければ1つずつしか置換してくれないのよね。
それで\d+にマッチしたものを$&に置換すると書いてあるが、
$&は特殊変数で、マッチした部分全部という意味。
$1,$2…は()でグループ化されたものの1番目、2番目…の意味だったけど、
他にもマッチ関係の特殊変数があるけど、まぁ$&はそれなりに使うのかな。
けどこれって、123にマッチしたら、これを123に置換するという意味だからね。
結局置換してないのよね。なんという効率の悪いプログラム。と思うわな。
それでいろいろ試してたのだが、どうも複数マッチというやつは2つ使い方があるらしい。
my @a=( $s=~m/\d+/g );
print "$_\n" for(@a);
print "$&\n" while( $s=~m/\d+/g );
1つは僕が何度もつかってるマッチした結果を配列にしてくれるやつ。
()でグループ化されてれば、その分だけいれてくれる。
結果をスカラーで受ければ、1つずつマッチして進むことができる。
それが3行目のwhileを使ったやつ。今まで知らんかったのよね。
これは、まず123にマッチして、次に51にマッチして…最後にマッチしなくなれば偽を返すと。
まぁPerlというやつはスカラーで受けるか配列で受けるかで動作が変わってしまうことが多いからね。
@aをスカラーとして取り扱えば@aの要素数だし、<>をスカラーとして取り扱えば一行読みこみだし…
まぁそういうことが複数マッチにもあったということです。
しかしこれを使えば個数を数えることをこうかける。
my $n=0;
$n++ while( $s=~m/\d+/g );
print "$n\n";
こっちの方が性能がいいに違いない。
なら確かめてみよう。
というわけでBenchmarkモジュールで試してみました。
use strict;
use warnings;
use Benchmark;
my $s='123 51 5 65 25 14 25 12 54 65 'x1000;
timethese(500,{
'replace'=>sub{ $s=~s/\d+/$&/g; },
'match' =>sub{ my $n=0; $n++ while( $s=~m/\d+/g ); }
} );
こんな便利なものがあるんですね。
timethese関数に試験する回数と試験名とサブルーチンリファレンスのハッシュリファレンスを渡すと。
今回ターゲットとした文字列は’123 51 5 65 25 14 25 12 54 65 ‘を1000回繰り返したもの。
この試験を500回やってもらったと。
本当は1000回ぐらいやればよかったんだが、実は結構時間がかかる。
徐々に増やしながらやったのだが、まぁ500回ぐらいやればいいかって。
結果ですが…
Benchmark: timing 500 iterations of match, replace...
match: 35 wallclock secs (33.88 usr + 0.00 sys = 33.88 CPU) @ 14.76/s (n=500)
replace: 6 wallclock secs ( 6.11 usr + 0.00 sys = 6.11 CPU) @ 81.85/s (n=500)
実はreplaceの方が圧倒的に速いんですね。
何でなんだろうね。けど不思議な話ですよね。
置換の方は繰り返しに特化されてるからかな。
それならばとこの2つのコードを比較してみることにした。
my @a1=();
while($s =~ m/^(.+?)\s+(.+)$/mg){
push @a1,[$1,$2];
}
my @a2=();
$s =~ s/^(.+?)\s+(.+)$/
push @a2,[$1,$2]; $&;
/emg;
mを付けると複数行の文字列の改行にも^と$が使えるようになりますからね。
行ごとに、test 100という行なら[‘test’,’100′]という配列リファレンスを作って配列に投げ込むと。
1つ目は複数マッチの方法。まぁそのままですね。
2つ目は置換にeオプションをつけたもの。
eオプションというのは、置換先をPerlのプログラムとして評価するもの。
$s =~ s/0x([0-9a-fA-F]+)/hex($1)/eg;
これは0x5Aとかにマッチして、これをhex(‘5A’)の結果でもって、90に置換すると。
それでこれはマッチしたものをpush @a2,[$1,$2]; $&;の結果に置換するというものだが、
結局これの結果って$&なんだよね。だから何も置換しない置換ということです。
さて、比較してみようか。
my $s="test 100\n"x10000;
timethese(100,{
'match' =>sub{ my @a1=(); while($s =~ m/^(.+?)\s+(.+)$/mg){ push @a1,[$1,$2]; } @a1; },
'replace'=>sub{ my @a2=(); $s =~ s/^(.+?)\s+(.+)$/ push @a2,[$1,$2]; $&; /emg; @a2; }
} );
test 100と10000行繰り返したものに処理することを100回するベンチマークね。
結果だが、こんな風になった。
Benchmark: timing 100 iterations of match, replace...
match: 39 wallclock secs (37.77 usr + 0.03 sys = 37.80 CPU) @ 2.65/s (n=100)
replace: 9 wallclock secs ( 8.19 usr + 0.11 sys = 8.30 CPU) @ 12.05/s (n=100)
と、eオプション付きの置換の方が高速でした。
しかし1000行なら少し速いかなという程度、100行だとマッチの方が速いぐらい。
普段1000行も処理しないだろうし、好きな方で書けばよさそうですね。
そしてとんでもない回数の繰り返しには複数置換が向いていることがよくわかりましたね。
まぁPerlというのはやり方はいろいろあるわけで、好きな方法で書けばいいのは確かですね。
けど性能がいいとか欠きやすいとかあるから、これがいいですねというのもあると。
僕の感想では個数数えるのは置換の方がいいですね、
けど別にそれ以外はわかりにくい書き方してやる必要もないだろうと。
明らかに見にくいしねぇ。何してるんだ?と思われること間違えなし。
ただ知っておくと面白いとは思うけどね。