手抜き日中判別

Twitter 上で、 id:showyou さんがログデータの日中言語判別をしたいという話をしていたので、それについて。

まず前提として、文字だけ見ても日本語と簡体字中国語(繁体字はもっと面倒だけど、ここではいったん棚上げ)を 100%判別することはできない。
というのは、簡体字中国語の文章であっても必ずしも簡体字を含むわけではないので。

“真的?”(本当に?)
“恭喜恭喜!”(おめでとう!)

これらは典型的な例だが、実際はもっと長い簡体字を含まない文章でも実は中国語ということがあり得る。

また、「カナがあれば日本語」という簡単な方法もあるが、全部漢字だから中国語というわけでもない。
「最低!」
「関西電気保安協会」等。

文字ベースで判別できないとなると、精度良く判別するならライブラリを使うのが一番。

Language Detection Library for Javaとか。

ただ、これは Java のライブラリなので、スクリプト言語から利用するのは面倒。

ここで、日本語を中国語に/中国語を日本語に、というエラーのうち、どちらかを許容できるというなら、Perl 等で簡単に処理することもできる。

まず、日本語を中国語に間違えてもいい場合。

これなら、「漢字があって、カナがなければ中国語」が手っ取り早い(超手抜きの場合。上で書いたように、全部漢字だから中国語とは限らない。もうちょっとマシなのは最後に)。

use strict;
use utf8;
my $str = '(判別したいテキスト)';
my $is_zh = 1 if $str =~ tr/\x{4e00}-\x{9fff}// and $str !~ tr/\x{3041}-\x{3093}\x{30a1}-\x{30f6}//;

次は、中国語を日本語に間違えてもいい場合。

この場合、「JISにない日本語の簡体字があれば中国語」がいい。

簡体字かどうかの判別は、http://www.unicode.org/Public/UNIDATA/Unihan.zip:Unihan.zipを落としてきて、kTraditionalVariant があれば簡体字といえる。

ただ、この基準で「簡体字」だから中国語、としてしまうとひどいことになる。

日本の略字体には「国」「体」など、中国の簡体字と同じものがあるので。

だから、「簡体字で、かつ JIS にない文字」というのがいい。

ただ、この処理の精度であれば、Unihan を見るまでもなく「簡体字中国語の文字コードに変換できて、日本語に変換できない文字を含む」なら中国語、とするのが簡単でいい。

簡体字中国語の文字コードというのは、具体的には GB2312(GBK・GB18030等は大きすぎて日本語の文字まで含むのでダメ)。

Perl で書くとこんな感じ。

use strict;
use utf8;
use Encode;
my $enc_gb2312 = find_encoding('gb2312');
my $enc_cp932 = find_encoding('cp932');

my $str = '(判別したいテキスト)';
my $questions_before = ($str =~ tr/?//);
my $questions_gb2312 = ($enc_gb2312->encode($str) =~ tr/?//);
my $questions_cp932 = ($enc_cp932->encode($str) =~ tr/?//);
my $is_zh = 1 if $questions_before == $questions_gb2312 and $questions_cp932 > $questions_before;

ここでは日本語の文字コードは cp932 としたけど、チルダ問題等があるので、そのへんの処理も必要かも。

ちなみに、この処理の gb2312 と cp932 の基準を逆にすると「日本語を中国語に間違えてもいい場合」に使える。その時は、「カナがあれば日本語確定」とした上で(逆にした)この処理を行えばいい。上のより、ちょっとマシな(「関西電気保安協会」が日本語と判別される)処理になる。

use strict;
use utf8;
use Encode;
my $enc_gb2312 = find_encoding('gb2312');
my $enc_cp932 = find_encoding('cp932');

my $str = '(判別したいテキスト)';
my $is_zh = 0;
if ($str !~ tr/\x{3041}-\x{3093}\x{30a1}-\x{30f6}//) {
    my $questions_before = ($str =~ tr/?//);
    my $questions_gb2312 = ($enc_gb2312->encode($str) =~ tr/?//);
    my $questions_cp932 = ($enc_cp932->encode($str) =~ tr/?//);
    $is_zh = 1 unless $questions_before == $questions_cp932 and $questions_gb2312 > $questions_before;
}