古い記事
ランダムジャンプ
新しい記事
今日のタスクは、入力テキストに対して検索式(論理式)をラベルとした辞書エントリをマッチさせる処理。手作りルールによるスパムフィルターなんかに使える。

昨日公開した「ANDやORを用いた検索式でテキスト走査する(perl)」[2012-01-26-1]と一昨日公開した「入力テキストに辞書の語が含まれているかどうか正規表現でチェック」[2012-01-25-1]を合体させた処理である。

具体例で説明する。

まずマッチさせたい辞書の例。ラベルとコンテントからなるTSV。ラベルは論理式。使う演算子は AND→"&", OR→"|", NOT→"!" と括弧"()"。

正月|賀正|元旦|迎春	NY
(今年|本年)&よろしく	KTYR
麦酒|ビール&!モビール	BEER
エロ&!(ピエロ|ヒエロ)	ERO
(スペース連続はタブ)

そして、入力テキストとマッチする辞書エントリ(のコンテント部分)。

- 今年もどうぞよろしくお願いします → KTYR
- 元旦からビール飲みたい → NY,BEER
- ヒエログラフ読みたい → no match
- 教えてエロい人! → ERO

検索式によるクエリが複数(辞書エントリ分)あり、入力テキストに対してそれらをマッチさせるというイメージ。

■サンプルコード(andor.pl):
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use open ':utf8';
binmode STDIN, ":utf8";
binmode STDOUT, ":utf8";

my $dic_fn = shift @ARGV;

my @db;
my %token;
open(my $fh, "<:utf8", $dic_fn) or die;
while (<$fh>) {
    chomp;
    next if /^\s*$/;
    my ($pat, $rest)  = split(/\t/, $_, 2);
    push @db, {pat => $pat, line => $_};
    foreach my $i (grep {not /^\s*$/} split(/[\(\)\&\|\!]+/, $pat)) {
	$token{length($i)}{$i} = 1;
    }
}
close($fh);

my %pat;
foreach my $i (keys %token) {
    $pat{$i} = join("|", keys %{$token{$i}});
}

while (<>) {
    print "> $_";
    chomp;
    next if /^\s*$/;
    my $s_ref = get_matched_strings(\%pat, $_);
    my $r_ref = get_matched_entries(\@db, $s_ref);
    print join("\n", map {$_->{line}} @$r_ref)."\n" if @$r_ref;
}

sub get_matched_strings {
    my ($pat_ref, $text) = @_;
    my %match;
    for (my $i = 0; $i < length($text); $i++) {
	foreach my $len (keys %{$pat_ref}) {
	    $match{$1}++ if $text =~ /^.{$i}($pat_ref->{$len})/;
	}
    }
    return \%match;
};

sub get_matched_entries {
    my ($db_ref, $match_ref) = @_;
    my @rv;
    foreach my $i (@$db_ref) {
	my $pat = $i->{pat};
	$pat =~ s/([^\(\)\&\|\!]+)/$match_ref->{$1}?1:0/ge;
	push @rv, $i if eval "$pat";
    }
    return \@rv;
}

■辞書(test.dic):スペース連続はタブ。
正月|賀正|元旦|迎春	NY
(明けまして|あけまして)&おめでとう	AHNY
(今年|本年)&よろしく	KTYR
麦酒|ビール&!モビール	BEER
ワイン|葡萄酒		WINE
エロ&!(ピエロ|ヒエロ)	ERO

■実行例:
% cat test.txt
【賀正】あけましておめでとうございます【迎春】
今年もどうぞよろしくね☆
元旦からビール飲みたい
天井からモビールをぶら下げた
ワインも飲める麦酒会開催
ヒエログラフ読みたい
駅前広場にピエロがいた
教えてエロい人!

% andor.pl test.dic test.txt
> 【賀正】あけましておめでとうございます【迎春】
正月|賀正|元旦|迎春     NY
(明けまして|あけまして)&おめでとう      AHNY
> 今年もどうぞよろしくね☆
(今年|本年)&よろしく    KTYR
> 元旦からビール飲みたい
正月|賀正|元旦|迎春     NY
麦酒|ビール&!モビール   BEER
> 天井からモビールをぶら下げた
> ワインも飲める麦酒会開催
麦酒|ビール&!モビール   BEER
ワイン|葡萄酒           WINE
> ヒエログラフ読みたい
> 駅前広場にピエロがいた
> 教えてエロい人!
エロ&!(ピエロ|ヒエロ)   ERO

実際にとある実験システムで似たような処理を書いているのだけど、そっちはもちろんインデックス張ったりして高速化してる。まあそこは仕事だから当然だけど。

ref.
- [を] ANDやORを用いた検索式でテキスト走査する(perl)[2012-01-26-1]
- [を] 入力テキストに辞書の語が含まれているかどうか正規表現でチェック[2012-01-25-1]
この記事に言及しているこのブログ内の記事