« 手に余る | main | pixivから特定ユーザのイラストを全て取ってくる »

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会議で外人さんが喋っていた気がするが、当時興味が無かったので聞き飛ばしてしまった。回り道をしない為にもその内ちゃんと勉強しようと思う。

(追記)
更に漁ってたらこんなん見つけた。
perl, python & ruby - chr() vs. Unicode
わお。なるほど。.chr試してみたけどやっぱりダメだったので、Dan the Godに乗っかってみる。
generate2.rb

#!/opt/local/bin/ruby -Ku
require 'jcode'

class Fixnum
def utf8chr
if self < 0x80
chars = [ self ]
elsif self < 0x800
chars = [
(self >> 6) | 0b1100_0000,
(self & 0b0011_1111) | 0b1000_0000
]
elsif self < 0x10000
chars = [
(self >> 12) | 0b1110_0000,
((self >> 6) & 0b0011_1111) | 0b1000_0000,
(self & 0b0011_1111) | 0b1000_0000
]
elsif self < 0x110000
chars = [
(self >> 18) | 0b1111_0000,
((self >> 12) & 0b0011_1111) | 0b1000_0000,
((self >> 6) & 0b0011_1111) | 0b1000_0000,
(self & 0b0011_1111) | 0b1000_0000
]
else
raise RangeError, "#{self} is out of utf8 range";
end
chars.map{|c| c.chr}.join('')
end
end

puts (0x4e00..0x9fa5).map{|i| i.utf8chr}


文字コードはここを参考。
わーい。
% ruby generate2.rb | wc  
20902 21522 83608
%

あ、あれ?増えてるよ?
% ruby generate2.rb | tail -1

%

test.rb
#!/opt/local/bin/ruby -Ku
require 'jcode'

puts 'aA0&!漢字です龥'.gsub(/[^一-龠]/, '')


% ruby test.rb
漢字
%

あww

と言う訳で、漢字マッチやるなら[一-龥]になるのかな。

(もっかい追記)
漢字正規表現の大元の記事
なるほど。

トラックバック

このエントリーのトラックバックURL:
http://polog.org/mt-tb.cgi/398

この一覧は、次のエントリーを参照しています: Rubyで全ての漢字を列挙する:

» 誤字ェネレータを作った 送信元 polog
誤字ェネレータ Rubyで全ての漢字を列挙する 漢字を類似度検索可能にする この... [詳しくはこちら]

コメント (2)

そういえば。
先日伝え忘れたけど、我が家では夫婦でクックパッドを愛用するようになりました。
頑張ってねー。

おお、ご利用ありがとう:D
君らがより楽しく料理できるように、がんばるよ。

コメントを投稿

Powered by
Movable Type 3.34