Top > MyPage
 

Perl TIPS1

変数関係

$_は注意を要する(エイリアス)

まずは次のソースを見てください。

@array = qw(sakai toda shibata iwane sakamizu sasaki);

foreach (@array) {
  print $_, "\n";
  $_ =~ s{k}{b};
  $_ .= "";
}

foreach $name (@array) {
  print $name, "\n";
}


  $_ .= "";

という行がないと、何故かPerlが文字列を数字と考えてしまうことはさておき、 この$_はエイリアスであるので、変更の対象となっている、 @arrayの中身を変えてしまいます。なので、実際上記は

sabai
toda
shibata
iwane
sabamizu
sasabi

と表示されます。

このエイリアスというのはリファレンスと似ているようで、デリファレンスし ないでも使え、つまり全く同じものをさすようです。本やWEBなどを検索して もあまり詳しく出ていませんが、

%hash = (name => 'sakai', age => 42, sex => 'make');

foreach (values %hash) {
  $_ =~ s{[sm]}{b}g;
}

foreach $v (values %hash) {
  print $v, "\n";
}

というように、valuesもエイリアスを$_に入れていくようなので、上記 のコードは

bakai
bake
42

と出力されます。

つまり、$_を変更するとしたら、もとの値も変わると言うことを積極的に意識しておく必要があるということでしょうか。

型グロブ

ファイルグロブと型グロブとは、ほとんど関係ないという話なので、今回は型グロブに限定。上記のエイリアスにたぶん関係がありそう。ただ、相当奥深そうな話なので、触りだけ調べてみました。

パッケージの内容をひとまとめにしてシンボルテーブルと呼ぶそうです。もし明確にパッケージを指定していないとmainパッケージになります。そのシンボルテーブルはパッケージ名の後ろに、コロンを2つ::をつけた名前のハッシュに格納されています。つまり、mainパッケージだったら%main::ですし、DUM::CVSだったら%DUM::CSV::ということになります。

ただし、レキシカル変数は(myのついたもの)、パッケージとは関係ないので、見ることはできません。上記のコードの例に#####で囲んだところを追加してみました。

@array = qw(sakai toda shibata iwane sakamizu sasaki);
foreach (@array) {
  print $_, "\n";
  $_ =~ s{k}{b};
  $_ .= "";
}
foreach $name (@array) {
  print $name, "\n";
}

%hash = (name => 'sakai', age => 42, sex => 'make');
foreach (values %hash) {
  $_ =~ s{[sm]}{b}g;
}
foreach $v (values %hash) {
  print $v, "\n";
}
########################################
foreach $symname (sort keys %main::) {
  local *sym = $main::{$symname};
  print "\$$symname is defined\n"  if defined($sym);
  print "\@$symname is not null\n" if @sym;
  print "\%$symname is not null\n" if %sym;
}
########################################

です。これを実行すると、

$" is defined
$$ is defined
$- is defined
$/ is defined
$0 is defined
$@ is defined
%CORE:: is not null
%Cwd:: is not null
%Cygwin:: is not null
%DynaLoader:: is not null
%ENV is not null
@INC is not null
%IO:: is not null
%Internals:: is not null
%PerlIO:: is not null
%Regexp:: is not null
%UNIVERSAL:: is not null
$_<cygwin.c is defined
$_<perlio.c is defined
$_<perlmain.c is defined
$_<universal.c is defined
$_<xsutils.c is defined
@array is not null
%attributes:: is not null
%hash is not null
%main:: is not null
$symname is defined
%utf8:: is not null

などが出力されます。

@array is not null
%hash is not null

などは、コードの中で使っているので、それが入っているわけですね。それら以外は、デフォルトで作成されているのだと思います。

さて、

$name = 'chiku';

@name = qw(sakai toda iwane);

*namae = *name;

print $namae, "\n";

foreach $n (@namae) {
  print $n, "\n";
}

*onamae = \$name;

print "$onamae\n";

print @onamae, "/may be nothing\n"

#
*namae = *name;

で、エイリアス処理が出来ます。つまり、上記の$name@nameの代わりに、$namae@namaeでアクセスできることを意味します。スカラー値だけエイリアス処理を行いたかったら、

#
*onamae = \$name;

というように、リファレンスを与えるとOKのようです。

しか〜し、何の役に立つの?と言われると、ちょっとつらいですが、例えば、Exporterモジュールはこの方法を使って、あるパッケージから別のパッケージにシンボルをインポートするようで、

#
*SomePackage::method = \&OtherPackage::otherMethod;

で、OtherPackageのotherMethod関数をSomePackageのmethod関数として使えるようにするわけです。

リファレンスを関数に渡して、それをグロブとして受け取ると、デリファレンスしなくて良いらしいですが、レキシカル変数では使えないという意味では、あまり実用的じゃないかもしれません。

$name = "sakai";

&change(\$name);

print $name, "\n";

sub change {
  local *n = shift;
  $n .= " is good guy!";
}

もう1つ。constantプラグマで、定数の定義は

use constant PI => 3.14;

のように行うが、これは

#
*PI = sub () {3.14};

のようなことをしているようです。「のような」とは、

#
*pi = sub () { 3.14 };
print pi . "/pi\n";

ではダメで、

#
*pi = sub () { 3.14 };

print pi() . "/pi\n";

ではOKになるあたりは、よくわかりません。constant.pmを見ると、

my $scalar = $_[0];
*$full_name = sub () { $scalar };

とかやっているので、同じような気がするのですが、謎でした(謎だったのですが、同僚から指摘を受けてわかりました)。これを

BEGIN {
  my $pi = 3.14;
  {
    *main::PI = sub () {$pi};
  }
}
print PI . "\n";

とやればOKでした。つまり(自信はないのですが)

BEGIN {
  #なんらかの処理
}

のBEGINブロックは、コンパイラがこれを見つけるや否や、即座にコンパイルフェーズで実行してしまいます。つまり今回の場合はプログラムが実行フェーズになる前にPIの定義をすませるということになります。何よりも早く実行したい場合に有効です。

たぶん、そうしておかなければ、実行フェーズでPIが呼ばれたときに、ベアワードなのかサブルーチンなのかを判断するときに、その選択をPerlが判断を間違えてしまう(というか、判断できない)のでしょう。

スライス

通常配列に値を代入するには

@array = (1 10 100);

$array2[0] = 1;
$array2[1] = 10;
$array2[2] = 100;

$array3    = qw(1 10 100);

等の方法で値を代入します。

配列スライスとは「配列の要素を指定するための省略構文」で、@array[1, 2, 7]($array[1], $array[2], $array[7])を表すことになるで、

@array = qw(sakai toda sasaki iwane shibata kurosawa ogasawara sakamizu);

@another[ 0, 1, 2 ] = @array[ 1, 3, 7 ];

foreach $name (@another) {
  print $name, "\n";
}

というようなことがでます。注意すべきは@another[ 0, 1, 2]のように@を使ってアクセスすることです。

これはhashでも同様に出来ます。

@array = qw(sakai toda sasaki iwane shibata kurosawa ogasawara sakamizu);

%hash = (age => 42, name => "", friend => "", sex => 'mail');

@hash{ 'name', 'friend' } = @array[ 1, 3 ];

foreach my $key (keys %hash) {
  print "$key = $hash{$key}\n";
}

のように、相変わらず@を付けますが、次の括弧{}は注意しないといけません。

たぶん、時々、楽が出来るような気がします。

制御関係

正規表現

数字へコンマ挿入

1234567などの数字に1,234,567というようにコンマを挿入する、正規表現についてです。つまり

$num = "1223322";
$num =~ s/../.../g;
print "The population of $num is growing.\n";

の...のところを考えてみようと、という話です。

これを考える上で、先読み戻り読みという考え方が重要です。

例えば、

Jeffs brothers are Jeffship and Jeffrey.

という(ちょっと無理な)文字列があったとして、この文字列の中でJeffsをJeff'sにしたいと思います。

$text = "Jeffs brothers are Jeffship and Jeffrey.";

$text =~ s/Jeffs/Jeff's/g;

print $text,"\n";

はダメですね。後半のJeffshipも変換されてしまいます。

Jeff's brothers are Jeff'ship and Jeffrey.

もちろん、今回は最初の1つだけ変換すればよいので、gオプションを取れば、最初のJeffだけが変換されますが、他にもある場合にはその方法は使えません。

そこで、

$text = "Jeffs brothers are Jeffship and Jeffrey.";

$text =~ s/Jeffs\b/Jeff's/g;
                ^^  
print $text,"\n";

のように\bを追加すれば、うまくいきます。

Jeff's brothers are Jeffship and Jeffrey.

この\bは単語の区切り文字とマッチするものですが、

$text = "Jeffs brothers are Jeffship and Jeffrey.";

$text =~ s/Jeffs /Jeff's/g;
                ^  
print $text,"\n";

のように、スペースにすると

Jeff'sbrothers are Jeffship and Jeffrey.
^^^^^^^

となり、Jeff'sbrothersというように、単語と単語がくっついてしまいます。これは\bが単語の区切り文字という言い方が正確でないことを意味しています。つまり、マッチングの対象になっておらず、1つ前の正規表現では置換の対象に\bが含まれていません。これらを一般的にはアンカーと呼ばれているもので、よく使われる^(文字列の先頭)$(文字列の最後)と同じです。

なかなかうまく表現できませんが、文字に一致するものではなく位置にマッチするものということで、0幅などと表現する場合もあります。別な言い方をすると文字と文字との間ににマッチするということでしょうか。

これと、先読みというものは似ています。先ほどの例を少し変えてみましょう。

$text = "Jeffs brothers are Jeffship and Jeffrey.";

$text =~ s/Jeff(?=s\b)/Jeff'/g;
               ^^^^^^      ^  
print $text,"\n";

です。この表現でも

Jeff's brothers are Jeff'ship and Jeffrey.

と成功します。この(?=s\b)が先読みの表現です。この

Jeff(?=s\b)

でマッチするのは

Jeffs brothers
^^^^##

なので、(?=s\b)の部分は、文字列としてはマッチしません。先ほどの\bはもとより表記中のsもマッチせず、あくまでもsの直後に単語の区切り文字がある直前の位置にマッチしています。あくまでも位置です(文頭とか文末というのと同じです)。これを利用すると、

$text = "Jeffs brothers are Jeffship and Jeffrey.";

$text =~ s/(?<=Jeff)(?=s\b)/'/g;
            ^^^^^^^^^^^^^^  ^  
print $text,"\n";

でも成功します。この(?<=Jeff)戻り読みというもので、先読みとは反対に文字列の左方向にその文字列が存在する直後の位置にマッチします。そうなると、この

(?<=Jeff)(?=s\b)

を言葉にすると、Jeffという文字が左に存在する位置にマッチし、次にsと単語区切り文字が右に存在する位置にマッチするということにまります。

Jeff s brother
    ^

上記はスペースがありますが、このスペース部分は幅のない文字と文字との間(^を書けないので敢えてスペースを入れている)を意味しています。つまりその位置に'を挿入していることになっているわけです(正確には文字と文字との間の空文字を'で変換している)。

これらを利用すると、

$num = "1223322";
$num =~ s/(?<=\d)(?=(\d\d\d)+$)/,/g;
print "The population of $num is growing.\n";

で、3桁ごとのコンマを挿入することが出来ます。

(?<=\d)(?=(\d\d\d)+$)

を言葉にすると、数字が1つ左にある位置にマッチし、続いて数字が3つずつ群れをなして、最後まで続く位置にマッチする、ということになります。つまり

1 234567
#^######

にマッチするわけです。そこに,を挿入し、gオプションで次を

1,234 567
    #^###

の^にマッチし、やはりコンマを挿入します。

これを次のような別のやりかたで変換するものを時々見かけますが、

s/(\d{1,3})(?=(?:\d\d\d)+(?!\d))/$1,/g;

もほぼ同じ表現です。これも言葉にすれば、数字が1つ以上3つ以下続き(これは戻り読みを使っていないので、文字に一致します)、次に数字が3つずつかたまって1つ以上続き、次に数字以外が来る位置にマッチする、ということになります。ただし、前半が戻り読みではないので、$1,で置換する必要があります。

全角・半角大文字のtrim

半角文字のtrim(文字列の前後の空白を削除)は簡単ですが、どうやら全角スペースは結構面倒です。

詳しくは<URL:http://www.din.or.jp/~ohzaki/perl.htm>あたりをお読み下さい。

まずは前提として、文字コードはEUC_JPである必要があるので、テストをする際には必ず、EUC_JPでコーディングしてください。

$str     = 'これはテストです';
$pattern = '好';

if ($str =~ /$pattern/) {
  print "match!\n";
}

は、何故かマッチしてしまいます。何故でしょうか?

それは『EUC-JP の「ス」の文字コードは 0xA5 0xB9 ,「ト」は 0xA5 0xC8,「好」は 0xB9 0xA5 であり,ちょうど「スト」の真ん中の部分が「好」と同じになるのでマッチしてしまうのです』。

$str     = 'これはテストです';
$pattern = '好';
$ascii = '[\x00-\x7F]';                     #ASCII
$twoBytes = '[\x8E\xA1-\xFE][\xA1-\xFE]';   #半角カタカナと全角文字
$threeBytes = '\x8F[\xA1-\xFE][\xA1-\xFE]'; #補助漢字

if ($str =~ /^(?:$ascii|$twoBytes|$threeBytes)*?(?:$pattern)/) {
  print "match\n";
}

とするとマッチしません。どうしてかというと、EUC-JP での 1文字というのは 1バイト文字である ASCII, 2バイト文字である JIS X 0201片仮名(半角カタカナ)と JIS X 0208(全角文字),3バイト文字である JIS X 0212(補助漢字)のことなので、その3種類の文字が0以上続いた後のマッチさせたいパターンというように指定したからです。つまり、文字の途中でマッチしてしまうような場合を避けたわけです。

これを利用すると、全角文字のtrimは次のように書けます。

sub trim {
    my $text        = shift;
    my $ascii       = '[\x00-\x7F]';
    my $two_bytes   = '[\x8E\xA1-\xFE][\xA1-\xFE]';
    my $three_bytes = '\x8F[\xA1-\xFE][\xA1-\xFE]';
$text =~ s/\G((?:$ascii|$two_bytes|$three_bytes)*?)(?:\xA1\xA1)/$1/g;
   return $text;
}

最後の

$text =~ s/\G((?:$ascii|$two_bytes|$three_bytes)*?)(?:\xA1\xA1)/$1/g;

を簡単に見ておくと、まずはわかりやすいようにベストプラクティスが推奨している方法に書き直して、

$text ="  サカイは馬鹿じゃありませ んよ  ";
$text =~ s{\G                                     #最初は行頭、マッチ後はマッチした最後
           (                                      #グルーピング開始
            (?:$ascii|$two_bytes|$three_bytes)*?  #アスキー・2バイト・3バイト文字が0以上
           )                                      #グルーピング終了
            (?:\xA1\xA1)}                         #全角スペース
          {$1}gmx;                                #上でマッチした文字列だけに全部置換

という感じでしょうか。順に追って考えると、

  1. \G は、マッチングが行われる前(つまり最初)は先頭に一致します。
  2. 上の例では、 $text の最初は全角スペースですが、「アスキー・2バイト・3バイト文字が0個以上で非欲張りマッチング」なので何もなくてもOKだから、次の正規表現((?:\xA1\xA1))で全角スペースで一致します。
  3. そして、 $1 にはグルーピングしたところにはマッチしていないので、何も入っていません。その全角は何もない $1 で置換されます(つまり消える)。
  4. \G はマッチした場合にはマッチした最後を意味するので、 \G・ は先ほどの消されたスペースがあった次を意味します。今回だと再度全角スペースに一致し、3が起こります。
  5. 次は、途中に全角スペースなどもありますが、全角スペースなども全て「アスキー・2バイト・3バイト文字」に含まれるので、グルーピング部分は最後の全角スペースの直前までマッチし、さらに次の全角スペースでマッチ全体が終わります。そして、その「アスキー・2バイト・3バイト文字」+「全角スペース」が「アスキー・2バイト・3バイト文字」で置き換わるわけです。
  6. まだ最後の全角スペースが残っています。5の後、最後の全角スペースの直前に \G の位置がありますので、3と同様に全角スペースは消えてしまいます。

とうわけで、先頭・末尾の全角スペースを消すには、EUCに変換し、上記の置換を行いましょう。

atomic

現時点ですべて説明するほどわかっていませんが、この辺がわかると「正規表現」は、たぶん、だいぶわかってくるかもしれません。

「^.*([0-9]+)」という正規表現で「Copyright 2005」をマッチさせたら、$1(括弧でグルーピングしたところ)には何が入るでしょうか。

まず

.*

によって「何でもOKな文字が0個以上」続くということですが、この*や+は貪欲な奴で、可能な限りマッチさせていきます(これは後から出てくる、非欲張りの場合と違い、テストするかスキップするか、という選択肢のうち、欲張り量指定子の場合は---つまり*は---テストを優先させる)。

つまり、

Copyright 2005
^

がOK。

Copyright 2005
^^

がOKと続いていき、

Copyright 2005
^^^^^^^^^^^^^^

まで、マッチさせます。正規表現エンジンは、その後、([0-9]+)に気づきます。ここがポイントで、人間と違い、次のマッチングのことは頭にないようです。つまり行くとこまでいって、その次を考えるということのようです(コンピュータは回転が速いけれど、ちょっと馬鹿なのかもしれませんね)。

「おや、次は数字が1個以上か」ということで、

Copyright 2005
^^^^^^^^^^^^^^

は、.*としてOKだとしても、次の([0-9]+)に合致しないので、マッチとしては失敗なので、1個、もどります(これはバックトラックという)。

Copyright 2005
^^^^^^^^^^^^^

そこで、「おお、これは([0-9]+としてOKだ」と、ここでマッチしたというこ とで終了します。

この辺から若干自信はないのですが(説明が)、もし

^.*?([0-9])+だったら、どうなるかというと、

正規表現エンジンは、欲張りな*でなく、非欲張りな*?では、とりあえず自分自身のテストをしないで(スキップして)、次をテストするそうです。

つまり

Copyright 2005
^

([0-9])+じゃない。そこで、失敗したので、前に戻って、さっきスキップした.*?をテストする。

Copyright 2005
^

でOK。

次に、たぶん、また.*?はスキップして、([0-9])+をテストする。

Copyright 2005
^^

でも失敗。そしてまたバックトラックして、.*?をテストして、

Copyright 2005
^^

はOK。そのまま失敗と成功を繰り返して、

Copyright 2005
^^^^^^^^^^

まで来るわけですが、今度も.*?をスキップして、([0-9])+をテストする。

Copyright 2005
^^^^^^^^^^^

でOKとなります。そこで.*?は終了し、([0-9])+のマッチングが始まり、

Copyright 2005
^^^^^^^^^^^^

でもOKと続いていき最後までたどり着いて、マッチング成功となるわけです。

以下のperlのスクリプトを実行すると

$test = "Copyright 2005";

if($test =~ /^.*([0-9]+)/){
    print $1."\n";
}

if($test =~ /^.*?([0-9]+)/){
    print $1."\n";
}

それぞれ

5
2005

と表示されます。後半の説明は若干自信がありませんが、一応予想通りの結果になります。

ちなみに、

if($test =~ /^.+([0-9]+)/){
    print $1."\n";
}


if($test =~ /^.+?([0-9]+)/){
    print $1."\n";
}

のように*+に変えても結果は同じです。

また、

if($test =~ /^.*([0-9]*)/){
    print $1."\n";
}

if($test =~ /^.*?([0-9]*)/){
    print $1."\n";
}

では、どちらも()にはマッチせず$1には何も入りません。最初のは最後まで行ったあとに、([0-9]*)が次にあるのかどうかを判断する時、もう後には文字がありません。だけど、「0個以上ということはなくてもOKなんだ」と正規表現エンジンが判断し、それでマッチング成功となるからです(欲張りにしても、非欲張りにしても成功を優先するらしい)。

じゃあ、何で2つめのの()内ともマッチングしないのかは、これまた自信がありませんが、たぶん、次のような解釈じゃないでしょうか。

Copyright 2005
^^^^^^^^^^

までは、以前のものと同様に行きます。次の2を.*?はまずはスキップするので、[0-9]*をテストしようとするのですが、+と違い、数字が絶対必要な訳じゃないので、この[0-9]*も保留し、バックトラックし、.*?でテスト、そしてOKを出すのだと思います。それが続くので、結局()内にはマッチしないわけです。

さてさて、じゃあatomic(原子)とはいったい何かというと、書き方は(?> .. )というような表記ですが、上記のような*+では、欲張りだろうが、非欲張りだろうが、いったん先に進めながら(実はその都度、ステータスというものをどこかにLIFOで保存しておき、いつでも戻れるようにしているらしい)、不一致が起こった後にバックトラックするということを繰り返していますが、それをしないのがatomicです。つまり

if($test =~ /^(?>\w*)t ([0-9]+)/){
    print "come\n";
    print $1."\n";
    print $2."\n";
}

のように(?>\w*)というのがあった場合---(?>がatomicの表現---これはマッチングさえ起こりません。なぜなら(?>\w*)で、\wがいけるだけ先に進みます。つまり

Copyright 2005
^^^^^^^^^

までポインターを進めます。しかし、そこで終わるので、次のtに正規表現エンジンは気づくわけですが、atomicは(?>\w*)という()内を抜け出ると、ステートすべて捨ててしまうので、バックトラックしないわけです。したがって、

Copyright 2005
^^^^^^^^^^

最後の^がtじゃないので、マッチングに失敗します。

したがって

(?>.*?)

というのは、全く意味のない表現で、なぜなら

.*?は、非欲張りなので、まずはスキップして、次の正規表現で正規表現エンジンはテストしようとします。が、その際、()を抜けるので、ステートを捨てるので、バックトラック出来ない状況になります。結果的に、何もなかったのと同じ意味になるからです。

じゃあ、このatomicが便利なのはどういうときでしょうか。たぶん、バックトラックが多く起こると思われる正規表現で、バックトラックが必要ではない場合は、効率を上げることが出来そうです。

以下のように、失敗しないマッチングでは

use Benchmark;

$m  = "1 2 3aaaabbbbccccaaaabbb123 2";
$t0 = new Benchmark;
foreach (1 .. 5000000){
    if($m =~ /^[\d\s]+[a-zA-Z]+[\d\s]+$/){
    }
}
$t1 = new Benchmark;
$td = timediff($t1, $t0);
print "nomarl regex took:", timestr($td), "\n";

foreach (1 .. 5000000){
    if($m =~ /^(?>[\d\s]+)(?>[a-zA-Z]+)(?>[\d\s]+)$/) {
    }
}
$t2 = new Benchmark;
$td = timediff($t2, $t1);


print "atomic regex took:", timestr($td), "\n";

はほぼ同じような結果です。

nomarl regex took:11 wallclock secs (10.62 usr +  0.00 sys = 10.62 CPU)
atomic regex took:11 wallclock secs (11.00 usr +  0.00 sys = 11.00 CPU)

それに比べて、

$m  = "1 2 3aaaabbbbccccaaaabbb123 2a";

として、マッチングに失敗する場合は

nomarl regex took:18 wallclock secs (18.02 usr +  0.00 sys = 18.02 CPU)
atomic regex took: 6 wallclock secs ( 5.91 usr +  0.00 sys =  5.91 CPU)

とうように、3倍近い差が出ます。ケースバイケースで使い分けると良いかもしれません。

どのように無駄を回避するのかをもう少し、つっこんでみましょう。

本(O'REILLYの「正規表現」)に書いてある例で言うと、

1.6250000345
1.62050000345

の小数点以下の数字を小数点第3位が0でない場合は、その数字を残し、それ以降は切り捨て、そして、小数点第3位が0の場合は0も含めて切り捨てる正規表現を考えます。

1.6250000345→1.625
1.62050000345→1.62

それを実現するのは以下のようなスクリプトになります。

$price = 1.6250000345;
$price =~ s/(\.\d\d[1-9]?)\d*/$1/;
print $price."\n";

$price = 1.62050000345;
$price =~ s/(\.\d\d[1-9]?)\d*/$1/;
print $price."\n";

つまり

s/(\.\d\d[1-9]?)\d*/$1/;

がその正規表現です。

これを言葉にすると

小数点があり、数字が2桁続き、次が0を含めない数字が0個以上あり、その後数字が1つ以上合った場合には、最初のグループ(\.\d\d[1-9]?)でマッチした値($1)で変換しています。

1.62500

の場合

1.62500
 ^^^

で「\.\d\d」が、

1.62500
    ^

で「[1-9]?」がひっかかり、その後の

1.62500
     ^^

は「\d*」となります。これを$1で変換しているので、1.625になるわけです。

ここまでは当たり前ですね。

さて、じゃあ1.625(最後の00がない)をこれに当てはめたらどうなるでしょう。

1.625
  ^^^

までは、\.\d\d[1-9]?で一致し、\d*は0個以上なので、結局マッチし、

「1.625を1.625で変換して」おしまいになります。

これは結果は望むとおりなのですが、どこかすっきりしません。つまり無駄なことをやっているというわけです。

可能なら、必要な場合だけに変換したいというのが人情です(人にもよりますが)。

そこで、

s/(\.\d\d[1-9]?)\d*/$1/;

s/(\.\d\d[1-9]?)\d+/$1/;
                  ^

とすると、どうなるかというと、やはり

1.62500

などはうまくいきます。

が、先ほどの1.625ではどうなるかというと

1.625
  ^^

までは全く同じで、

次の

1.625

^

で、「[1-9]?」が引っかかるのも同じです。

しかしここからが違います。正規表現エンジンは、次の\d+を見て、次は数字が1個以上なので、マッチングに一時的に失敗するので、バックトラックして、[1-9]?を放棄させます(0個でもOK だから)。そして最後の5と\d+がマッチするので、

(\.\d\d[1-9]?)は1.62とマッチしてしまい、変換は

1.62

となり、思った結果になりません。

非欲張りの

(\.\d\d[1-9]??)
          ^^

としても同様です。

ここで、やっとこさatomicの登場です。前回に述べたように、一度atomicの()の中を通り過ぎたら、けっして譲りません。つまり

s/(\.\d\d(?>[1-9]?))\d+/$1/

です。

これだと

1.625
    ^

で、「[1-9]?」が引っかかって、その後バックトラックしないので、次の\d+ があると、マッチングに失敗し、置換が行われません。

つまり

1.625

のままになるというわけです。

このように、atomicは無駄なことをさせないで、マッチングに失敗したいときに使うと便利なようです。

もう1つ例を挙げます。これも正規表現エンジンの特徴をつかむのにも有用だと思います。

Subjectという文字列に、正規表現「\w+:」でマッチングさせると、どういう動きなるかというと、

Subject
^
OK.

Subject
^^
OK.

Subject
^^^
OK.

Subject
^^^^
OK.

Subject
^^^^^
OK.

Subject
^^^^^^
OK.

Subject
^^^^^^^
OK.

とここまで来て、次で文字列は終わりますが、:がないのでマッチングに失敗しますが、正規表現エンジンはまだ最終的な判断を下しません。以前若干触れたステートというものを残しているので、今度はバックトラックしていき、

Subject
^^^^^^
Failure.

Subject
^^^^^
Failure.

Subject
^^^^
Failure.

Subject
^^^
Failure.

Subject
^^
Failure.

Subject
^
Failure.

と最初まで戻って、やっとこさ失敗したことに気づき「こりゃ駄目だ(マッチングに失敗)」と初めて正規表現エンジンは結論に達します。

これは相当無駄な話なので、「\w+:」を「(?>\w+):」だとどうなるかというと、

Subject
^^^^^^^
OK.

でバックトラックしないので、そこで失敗がわかるというわけです。

ちなみに、

(?>\w+)

\w++

は同じ意味だそうです。

その他

まだなし。

[2007-02-18 11:12]