機略戦記

Maneuver warfare

あるUnicodeの文字列が中国語かどうかを判定したい

このエントリを書いた人間はUnicodeや中国語について素人です。
このエントリに載っている情報は誤っている可能性があります。

結論

厳密では無いが、Unicode Han Database(Unihan)を参照して広東語または北京語の発音を持ちかつ音読みまたは訓読みの発音を持たない文字が含まれているかどうかで判定できそうだ。

概要

  • 最初に試みた方法:
    • /[ぁ-ん]/にマッチする文字列を取り出すことでひらがなのみが取り出せるように、中国語のみを正規表現で取り出せないか」。
    • Unicode上で漢字は、中国語・日本語・朝鮮語で使われている漢字をひとまとめにしたCJK統合漢字という概念で扱われているので無理だった。
    • https://ja.wikipedia.org/wiki/CJK%E7%B5%B1%E5%90%88%E6%BC%A2%E5%AD%97
  • Unihanには、ある漢字の読みの情報が含まれている。
    • 「中国語読みがあって、かつ、日本語読みが無い漢字」が含まれていればたぶん中国語であろう、という事が言える。
    • そのように実装してみたら、期待したように動いた。

実装

1. Unihanから漢字とその読みの対応表を取ってくる。

ここにあるUnihan.zipUnihan_Readings.txtを取ってくる。
http://www.unicode.org/Public/UNIDATA/

2. ファイルを分割する

以下のように分割する。

  • 訓読みがあるcodepointだけを集めたファイル
  • 音読みがあるcodepointだけを集めたファイル
  • 北京語読みがあるcodepointだけを集めたファイル
  • 広東語みがあるcodepointだけを集めたファイル

具体的にはこうする。

cat Unihan_Readings.txt | awk '$2 ~ /kJapaneseKun/ {print $1}' > japanese_kun
cat Unihan_Readings.txt | awk '$2 ~ /kJapaneseOn/ {print $1}'  > japanese_on
cat Unihan_Readings.txt | awk '$2 ~ /kMandarin/ {print $1}'    > mandarin
cat Unihan_Readings.txt | awk '$2 ~ /kCantonese/ {print $1}'   > cantonese

3. ある文字が、2.で作ったどのファイルに含まれるかで判定する

広東語または北京語の発音を持ちかつ音読みまたは訓読みの発音を持たない文字が含まれている文字だけを取り出すプログラムを書く。

具体的にはこうする。

補足:

  • ruby
  • 一番下に実行例が書いてある
# maybe_chinese.rb
CODEPOINTS =
  {
    japanese_kun: File.read('japanese_kun'),
    japanese_on:  File.read('japanese_on'),
    cantonese:    File.read('cantonese'),
    mandarin:     File.read('mandarin'),
  }.freeze

# size == 1のStringをcodepointに変換する
def to_codepoint(character)
  #raise "size == 1のStringを与える必要がある" unless character.size == 1
  character.codepoints.map{|cp| sprintf("%04X", cp)}.first
end

# 以下のように書きたいところだが重いので正規表現でやる
# CODEPOINTS[:japanese_kun].include?(codepoint) # (CODEPOINTS[:japanese_kun]を配列にしておいた上で)
def japanese_kun?(codepoint)
  !!(CODEPOINTS[:japanese_kun] =~ /\nU\+#{codepoint}\n/)
end

def japanese_on?(codepoint)
  !!(CODEPOINTS[:japanese_on] =~ /\nU\+#{codepoint}\n/)
end

def cantonese?(codepoint)
  !!(CODEPOINTS[:cantonese] =~ /\nU\+#{codepoint}\n/)
end

def mandarin?(codepoint)
  !!(CODEPOINTS[:mandarin] =~ /\nU\+#{codepoint}\n/)
end

# 漢字か判定
def han?(character)
  character =~ /\p{Han}/
end

# 文字列から中国語らしき文字をselectする。
# なぜか漢字以外のcodepointを与えると期待した動作をしないので、漢字のみで判定する。
def select_chinese_and_not_japanese(survey_target_string)
  survey_target_string.split('').select do |character|
    next if !han?(character)

    codepoint = to_codepoint(character)
    next if japanese_kun?(codepoint) || japanese_on?(codepoint)
    next if !(cantonese?(codepoint) || mandarin?(codepoint))

    true
  end
end

実行例

p select_chinese_and_not_japanese("我开动了, 多谢款待")
#=> ["开", "动", "谢"]
# 日本語としての読みを持つ字は引っかからない

p select_chinese_and_not_japanese("简体字")
#=> ["简"]
# 日本語としての読みを持つ字は引っかからない

p select_chinese_and_not_japanese("日本語")
#=> []

p select_chinese_and_not_japanese("ひらがなabc,.")
#=> []

課題

  • この実装だと「たまたま音読みor訓読みを持つ中国語の漢字のみで構成された文字列」は中国語だと判定されなくなってしまう。
  • 今回の(私が必要としていた)ユースケースだと別に問題なかったので無視した。