« PATH_INFOと相対パス(リンク) | メイン | Tokyo Bossa Nova~Outono~ »

Regexp::Trieにさわってみた

にわかににぎわっているはてなキーワードを高速に付与なのですが、dankogaiさんのRegexp::Trieをちょっとさわってみた。

Trieを利用したRegexpのオプティマイズという理解で間違っていないですよね。

#キーワードリスト
my @src = qw(1U 2ch amazon apache apple atom blog cdbi CentOS cgiapp colinux cpan csrf css dashboard db firefox flash foaf ftp google hacker hard httpd intrablog ipod);
my $rt = Regexp::Trie->new();
map{$rt->add($_)} @src;
my $regexp = $rt->as_regexp;
print $regexp,"?n";

とすると、

(?-xism:(?:1U|2ch|CentOS|a(?:mazon|p(?:ache|ple)|tom)|blog|c(?:dbi|giapp|olinux|pan|s(?:rf|s))|d(?:ashboard|b)|f(?:irefox|lash|oaf|tp)|google|h(?:a(?:cker|rd)|ttpd)|i(?:ntrablog|pod)))  

こういう正規表現が得られる。使う時は、

#キーワードの前後に「"」をつける
$text =~ s/$regexp/"$&"/go;


これのソースをみていて、やっぱりすげぇなぁと思った。

sub add{
    my $self = shift;
    my $str  = shift;
    my $ref  = $self;
    for my $char (split //, $str){
        $ref->{$char} ||= {};
        $ref = $ref->{$char};
    }
    $ref->{''} = 1; # { '' => 1 } as terminator
    $self;
}

なにげないように見える上のコードなんだけど。
この部分だけで動くようにしてみると、


$ perl -e '
> use Data::Dumper;
> my $self = {};
> my $ref = $self;
> foreach my $char (split //,"perl"){
> $ref->{$char} ||= {};
> $ref=$ref->{$char};
> }
> $ref->{""}=1;
> print Dumper($self);
> '
$VAR1 = {
          'p' => {
                   'e' => {
                            'r' => {
                                     'l' => {
                                              '' => 1
                                            }
                                   }
                          }
                 }
        }; 

こうなる。
これがなぜこうなるのかがすぐに分からなくて悩んだ。$refだけを見ているとわからない。リファレンスと$selfの動きをちゃんと追って行けばよかった。

こんな作り方(コード)ができるようになりたい。