複数の置換をたくさん実行する

あるテキストファイルに対して、複数個の置換を一気に行いたい。例えば、こういう置換候補のファイルを用意してテキストファイルに適用するとか。

before1 after1
before2 after2
before3 after3

普通に考えるとこういうコードになるでしょう。

use strict;
use warnings;

my $txtfile = shift;
my $repfile = shift;
my %replace;

open my $fh1, "<", $repfile;
while (<$fh1>){
  tr/\x0D\x0A//d;
  my ($k, $v)  = split(/\s/);
  $replace{$k} = $v;
}
close $fh1;

open my $fh2, "<",  $txtfile;
while(my $line = <$fh2>){
  foreach my $k (keys %replace){
    $line =~ s/$k/$replace{$k}/g;
  }
  print $line;
}
close $fh2;

ただ、これは遅い・・・。入力ファイルの各行ごとに、置換対象の正規表現をひとつずつコンパイルして適用してるんだから、遅いのも当然でしょう。複数の正規表現を最適化するにはRegexp::Assembleという手もあるみたいだけど、オーバースペックだよなあ。ということで、とりあえずはevalを使ってお手軽に解決することにしました。$repfileの読み込みが終わった後の処理を

my $re = eval 'qr/(' . join('|', keys %replace) . ')/';

open my $fh2, "<",  $txtfile;
while(my $line = <$fh2>){
  $line =~ s/$re/$replace{$1}/go;
  print $line;
}
close $fh2;

こんな感じにする。

テキストファイルが1500行ほど、置換候補が20個ほどの入力で試してみる。

$ time perl rep.pl txtfile repfile > /dev/null
real    0m0.484s
user    0m0.452s
sys     0m0.046s

$ time perl rep2.pl txtfile repfile > /dev/null
real    0m0.078s
user    0m0.046s
sys     0m0.046s

6倍強早くなる。この程度のinputでこれだけ目に見えて早くなるもんですなあ。正規表現のオーバーヘッドは侮れない。