Rubyで全ての漢字を列挙する
正しくは"UTF-8で表現できる"が接頭辞につきます。また、前提として僕はエンコードやらバイナリやらに関してはドがつく素人です。
すべての漢字を取り出す正規表現を以前読んでいて、ちょっと作りたいものがあって全ての漢字の列挙を行いたかったのでこれを参考にやってみた。
多分だけど、Rangeで範囲内の全ての漢字を取り出せるかなあと思って、まずは単純にirbで
("一".."龠").step(1){|s| puts s}ってやった所、数百文字で止まり、しかも途中からは漢字ではない??みたいなのが出力されている。おかしい。どれくらいの範囲が確保されていて、どれくらい漢字でないものが含まれているんだろう。
unpackしてバイト列に直したものを10進数に変換してみる。
>> "一".unpack('C*').map{|i| i.to_s(16)}.join
=> "e4b880"
>> 0xe4b880
=> 14989440
>> "龠".unpack('C*').map{|i| i.to_s(16)}.join
=> "e9bea0"
>> 0xe9bea0
=> 15318688ワオ、1文字3byteで、30万種類以上の漢字が詰め込めることになってる。多過ぎだろ漢字。ひょっとしたら詰め込み方にパターンがあるかもしれないので、とりあえず該当する文字と各バイトコードを全て取り出して、傾向を見てみる。今度は最初のコードみたく止まらずに出力された。
(14989440..15318688).step(1) do |i|
chr_a = i.to_s(16).scan(/../).map{|s| eval("0x#{s}")}
puts "#{chr_a.map{|i| i.chr}.join}\t#{chr_a.join(',')}"
end
出力ファイル (5MBあるから開かない方が無難)
首尾よくパターンを見つけた。第2・第3バイトは128から191までしか使われていない。
(14989440..15318688).step(1) do |i|
chr_a = i.to_s(16).scan(/../).map{|s| eval("0x#{s}")}
next unless(
chr_a[1] >= 128 &&
chr_a[1] <= 191 &&
chr_a[2] >= 128 &&
chr_a[2] <= 191
)
puts "#{chr_a.map{|i| i.chr}.join}\t#{chr_a.join(',')}"
end
出力ファイル
20897文字。とりあえず目的のものが得られた。
隙間があるのは将来増える文字達の為なのかなとか思いつつ、128とか191とかをフックにして調べてみたら何となく分かって来た。UTF-8はもともとUnicodeのサブセットなので、Unicodeと同等の情報量を3バイトで表現する為に第1バイトの頭4ビット、第2、第3バイトの頭2ビットが固定されているって感じかな。
この辺のエンコードのストラテジな話はRuby会議で外人さんが喋っていた気がするが、当時興味が無かったので聞き飛ばしてしまった。回り道をしない為にもその内ちゃんと勉強しようと思う。