テーブルから正規表現を生成する
前節では三項演算子を用いたテーブル(っぽくみえる表記)で、正規表現のパターンマッチを行ったが、テーブルは正規表現を生成するためにも使える。あるテーブルを正規表現を使って検索する場合、テーブルから正規表現を生成する。
#変則的な複数形のテーブル
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 件のコメント:
コメントを投稿