sang.pl --- Suffix Array を用いて N-gram 統計をとるプログラム USAGE sang.pl -n NUM -t NUM FILENAME OPTION -n NUM : NUM で n-gram の n を指定する。 -t NUM : threshold: NUM以下の頻度のものは表示しない n-gram には改行は含まれない。 [実行例] % cat test ABCBACABBAACABCABCACABACABBACBACACAAABACCAB % makeary -q test ● arrayファイルの作成 % sang -n 6 -t 1 test ● 6-gram で頻度が 1 より大きいものを表示 2 ACABBA 2 BACABB % ./sang.pl -n 3 -t 4 test ● trigram で頻度が 4 より大きいものを表示 6 ACA 5 BAC 6 CAB
#!/usr/bin/perl use strict; use warnings; use SUFARY; use Encode; use Getopt::Std; use utf8; use open ':utf8'; binmode STDOUT, ":utf8"; my %opts = (); getopts("n:t:", \%opts); my $ng = $opts{n} || 4; my $threshold = $opts{t} || 3; my $fn = shift @ARGV; my $sa = SUFARY->new($fn); my $n_ctr = 0; my @ktoks = (); for (my $i = 0; $i < $sa->{arraysize}; $i++) { my $pos = $sa->get_position($i); my ($from, $len) = $sa->get_line_info($pos); my $s = $sa->get_string($pos, $from + $len - $pos); $s =~ s/^([^\n]+)\n.*$/$1/; my @ttoks = map {Encode::decode_utf8($_)} ($s =~ m{([\x00-\x7f]|[\xC0-\xDF][\x80-\xBF]| [\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})}gsx); if (cmp_tok(\@ktoks, \@ttoks) >= $ng) { $n_ctr++; } else { output(); $n_ctr = 1; } @ktoks = @ttoks; } output() if $n_ctr >= 1; sub output { return if $n_ctr <= $threshold; print "$n_ctr ".join("", @ktoks[0..($ng-1)])."\n"; } sub cmp_tok { my ($k_ref, $t_ref) = @_; my $i; for ($i = 0; $i < @$k_ref and $i < @$t_ref; $i++) { last if $k_ref->[$i] ne $t_ref->[$i]; } return $i; }
N-gram カウント。% ./mkipu8.pl a.txt > a.txt.ary % mkary -so a.txt
% ./sang.pl -n 4 -t 3 a.txt 4 100円 5 「ぬんな 10 ぬんなり 4 ました。 4 まったり 5 んなり」