【Perl】日本語テキスト内での整数表現の認識と正規化
2014-02-16-1
[Programming][NLP]
日本語テキスト中で金額、個数、本数などで使われる数字文字列のマッチングと正規化を行う処理、今までそのつど使い捨てで書いていたんだけど面倒になったので(いまさら!?!?)、エッセンスをまとめた Perl スクリプトを雛形としてここで公開しておく。そういう経緯なのであくまで自分用。モジュール化もしていません。劣化車輪の再発明かもしれないけど気にしない。
■コード(intja.pl):デフォルトでは金額表現の認識・正規化を行う。
■入力データ(test.txt):テストデータだけど網羅してない。
■実行結果:フォーマット「《[認識箇所]》([正規化結果])」。
■コード(intja.pl):デフォルトでは金額表現の認識・正規化を行う。
#!/usr/bin/perl use strict; use warnings; use Encode; use utf8; use open ':utf8'; binmode STDIN, ":utf8"; binmode STDOUT, ":utf8"; my $KETA = ",、,"; my $SUUJI = "一二三四五六七八九"; my $REI = "○〇"; my $DAISU1 = "十百千"; my $DAISU2 = "万億兆京垓"; while (<>) { chomp; my $t = $_; # my $ms_ref = match_intja($t); my $ms_ref = match_kingaku($t); foreach my $m (@$ms_ref) { my $str = $m->{str}; $str =~ s/^[¥\\]\s*//; $str =~ s/\s*円$//; my $rg = reg_intja($str); $m->{info} = $rg; } my $tt = add_tags($t, $ms_ref, "《", "》(%%%)"); print "$tt\n"; } # テキストにタグを埋め込む sub add_tags { my ($t, $tg, $b_str, $a_str) = @_; $b_str = "[" if not $b_str; $a_str = "]" if not $a_str; foreach my $m (sort {$b->{from} <=> $a->{from}} @$tg) { my ($tb_str,$ta_str) = ($b_str, $a_str); $tb_str =~ s{%%%}{$m->{info}}g if $m->{info}; $ta_str =~ s{%%%}{$m->{info}}g if $m->{info}; substr($t, $m->{from}, $m->{len}) = $tb_str.$m->{str}.$ta_str; } return $t; } # 日本語での整数表現にマッチ sub match_intja { my ($t) = @_; my @rv = (); while ($t =~ /[\d一$SUUJI$DAISU1][\d$REI$SUUJI$DAISU1$DAISU2$KETA]*/gx) { push @rv, {str => $&, from => $-[0], len => $+[0]-$-[0]}; } return \@rv; } # 日本語での金額表現にマッチ sub match_kingaku { my ($t) = @_; my @rv = (); while ($t =~ / [\d一$SUUJI$DAISU1][\d$REI$SUUJI$DAISU1$DAISU2$KETA]*\s?円 |[¥\\]\s?[\d一$SUUJI$DAISU1][\d$REI$SUUJI$DAISU1$DAISU2$KETA]* /gx) { push @rv, {str => $&, from => $-[0], len => $+[0]-$-[0]}; } return \@rv; } # 日本語での整数表現の正規化 sub reg_intja { my ($str) = @_; $str =~ s/[$KETA]//g; $str =~ tr/0-9/0-9/; $str =~ tr/○〇一二三四五六七八九/00-9/; return $str if $str !~ /[$DAISU1$DAISU2]/; my $val = 0; my $total = 0; foreach my $n (grep {$_} split(/([0-9]*[$DAISU1$DAISU2])/, $str)) { if ($n =~ /^([0-9]*)([$DAISU1])/) { my ($su, $ds) = ($1, $2); my $k = index($DAISU1, $ds); $val += ($su||1) * 10**($k+1); } elsif ($n =~ /^([0-9]*)([$DAISU2])/) { my ($su, $ds) = ($1, $2); my $k = index($DAISU2, $ds); $total += ($val + $su||0) * 10000**($k+1); $val = 0; } elsif ($n =~ /^[0-9]+$/) { $val += $n; } } $total += $val if $val; return $total; }
■入力データ(test.txt):テストデータだけど網羅してない。
これは100円です。これは100,000円です。 100億円、100万2000円。 これは200円です。2百円も。 200,000円か200,000円です。 これは200、000円か2、000万円で、二千円になります。 これは2,000万円です。2,0000円かも。1,234万5,678円か。 なるなる。\2000とか\2,000とかああ2,000万円ですね。 ふーむ、¥200とか¥1,000とか。 \1¥1 1円、などなど。 200,300,400,500から選んでください。 価格は2千円、2万5千円。値段は二〇〇〇円か二,〇〇〇円。 二百円、五万六千三百二十一円。 五六三二一円。五六万三百二十一円。五十六万千三百二十一円。 五○○万三百一円。三○○万円。五六万百十一円。 五百二十一円。五百二一円。五二十一円。五百○七円。 なんと三億円事件。¥とか円とか。¥20円。 一億円か千円か百円か十円か一円下さい。 十三駅まで行きたいのですが。十三万円です。 一千二百三十四万五千六百七十八円とか二百三十四万五千六百七十八円とか。 ¥ 九千八百七十八億二百三十四万五千六百七十八。 六十二兆九千八百七十八億二百三十四万五千六百七十八円。 それは二万千百円。
■実行結果:フォーマット「《[認識箇所]》([正規化結果])」。
% ./intja.pl test.txt
これは《100円》(100)です。これは《100,000円》(100000)です。 《100億円》(10000000000)、《100万2000円》(1002000)。 これは《200円》(200)です。《2百円》(200)も。 《200,000円》(200000)か《200,000円》(200000)です。 これは《200、000円》(200000)か《2、000万円》(20000000)で、《二千円》(2000)になります。 これは《2,000万円》(20000000)です。《2,0000円》(20000)かも。《1,234万5,678円》(12345678)か。 なるなる。《\2000》(2000)とか《\2,000》(2000)とかああ《2,000万円》(20000000)ですね。 ふーむ、《¥200》(200)とか《¥1,000》(1000)とか。 《\1》(1)《¥1》(1) 《1円》(1)、などなど。 200,300,400,500から選んでください。 価格は《2千円》(2000)、《2万5千円》(25000)。値段は《二〇〇〇円》(2000)か《二,〇〇〇円》(2000)。 《二百円》(200)、《五万六千三百二十一円》(56321)。 《五六三二一円》(56321)。《五六万三百二十一円》(560321)。《五十六万千三百二十一円》(561321)。 《五○○万三百一円》(5000301)。《三○○万円》(3000000)。《五六万百十一円》(560111)。 《五百二十一円》(521)。《五百二一円》(521)。《五二十一円》(521)。《五百○七円》(507)。 なんと《三億円》(300000000)事件。¥とか円とか。《¥20》(20)円。 《一億円》(100000000)か《千円》(1000)か《百円》(100)か《十円》(10)か《一円》(1)下さい。 十三駅まで行きたいのですが。《十三万円》(130000)です。 《一千二百三十四万五千六百七十八円》(12345678)とか《二百三十四万五千六百七十八円》(2345678)とか。 《¥ 九千八百七十八億二百三十四万五千六百七十八》(987802345678)。 《六十二兆九千八百七十八億二百三十四万五千六百七十八円》(62987802345678)。 それは《二万千百円》(21100)。
この記事に言及しているこのブログ内の記事