2010年9月13日月曜日

Perl best practices[Perlベストプラクティス] 12章 正規表現 12.18~12.22


このエントリーをはてなブックマークに追加


テーブルから正規表現を生成する


 前節では三項演算子を用いたテーブル(っぽくみえる表記)で、正規表現のパターンマッチを行ったが、テーブルは正規表現を生成するためにも使える。あるテーブルを正規表現を使って検索する場合、テーブルから正規表現を生成する。

#変則的な複数形のテーブル
my %irregular_plural_of = {
    'child'             => 'children',
    'brother'        => 'brethren',
    'money'          => 'monies',
    'mongoose'    => 'mongooses',
    'ox'                => 'oxen',
    'cow'              => 'kine',
    'prima donna' => 'prime donne',
    'tooth'            => 'teeth',
    'toothfish'      => 'toothfish',
};

#正規表現の生成
my $has_irregular_plural = join '|', map {quotemeta $_} reverse sort keys %irregular_plural_of;

while (my $word = <>) {
    chomp $word;
    if ($word =~ m/\A ($has_irregular_plural) \z/xms) {
        print $irregular_plural_of{$word}, "\n";
    }
    else {
        print form_regular_plural_of($word), "\n";
    }
}

 ここでは2つの作業が必要になる。
 1つ目は、reverseを用いていること。|でつないだ正規表現は、左から順に評価されるので、作成された正規表現が....| tooth | toothfish | ... の順番だと、パターンマッチの部分で、ハッシュテーブルのtoothfishは正規表現のtoothとマッチしてしまう。このため、生成される正規表現は、toothfishがtoothより先に評価されるようにreverseを使用する。
 2つ目は、quotemetaを使用することで、メタ文字をエスケープすること。'prima donna'も'prima\ donna'になり、/xフラグがセットされていても問題なく処理できるようになる。
 このテーブルを用いた正規表現の生成は、テーブルと正規表現の一貫性を保証できるし、テーブルにキーと値のペアを追加するだけで、正規表現も拡張される点でメリットがある。
 決して、以下のように、元のテーブルのキーをコピーしただけの正規表現を作ってはいけない。これは作業が面倒だし、タイプミスも混じり込みやすい。


my $has_irregular_plural = qr{
    child | brother  | mongoose
  | ox    | cow      | money
  | tooth(?:fith)?
}xms;

複雑な正規表現は単純なパーツに分ける


#数字を補足する正規表現$NUMBERを作成する
Readonly my $DIGITS   => qr{ \d+ (?: [.] \d*)? | [.] \d+         }xms;
Readonly my $SIGN     => qr{ [+-]                                }xms;
Readonly my $EXPONENT => qr{ [Ee] $SIGN? \d+                     }xms;
Readonly my $NUMBER   => qr{ ( ($SIGN?) ($DIGITS) ($EXPONENT?) ) }xms;

 ここで、一つqrを使った際のデメリットを説明する。それはqrが適用された正規表現内に、qrが適用された正規表現があると、パフォーマンスが低下するかもしれないこと。$EXPONENTや$NUMBERが当てはまる。理由は、要素である正規表現を展開する際に、文字列に逆コンパイルされてから展開され、最後に再コンパイルされるが、文字列に逆コンパイルする際に、最適化されず効率の悪いパターンを生成する可能性があるため。
 代わりにq{ }またはqq{ }を使用すると、単純な文字列として、製作者が作成した正規表現がそのまま展開される。ただし、q{ }、qq{ }を使うと、エスケープ文字や、部分パターン全体を一つの項目として扱ったりするので、複雑な場合は、qr{ }を使うことが推奨される。
 あまりにも複雑な場合は、Regexp::Assembleを使用してみる。

既存の正規表現をまとめたモジュールを使用する


 正規表現の車輪の再発明はとても多い。これはRegexp::Commonモジュールを使用することで楽になる。
前節の、数字にマッチする正規表現は以下のように書ける。


use Regexp::Common;

Readonly my $NUMBER => $RE{num}{real}{-keep};#いつも$REというハッシュを基に作成する。

単一文字を|でつなぐのではなく文字クラスを使用する

以下の1番目のように書くとパフォーマンスの問題が生じる。2番目のようにすること。また、複数文字の共通点を文字クラスに統一することも推奨される。

if($cmd !~ m{\A (?: a | d | i | q | r | w | x ) \x}xms)

if($cmd !~ m{\A [adiqrwx] \z}xms)


0 件のコメント:

コメントを投稿