文字を数える
以下のような解き方もおもしろいかな。もちろんUTF-8決め打ちならkconv不要。
require'kconv' "日本語".toutf8.unpack('U*').size
Array#injectは凄い便利なので、artonさんのString#char_countも以下のように書き換えられます。
class String def char_count cnt = 0 self.scan(/./).inject(0){|r,i|r+1} cnt end end
もっと力技で数える方法もあります。つまり、バイト列を1バイトずつ見ていって、数えていく方法。毎度毎度書くのは大変ですが、メソッドを定義してしまうなら速い方がいいですもんね。
class String def char_count_jis count = 0 i = 0 while i < self.length if self[i] == 0x1B i += 1 case self[i] when ?$ i += 1 case self[i] when ?@,?B i += 1 while self[i] && self[i] > 0x20 i += 1 if self[i] > 0x20 i += 1 count += 1 else throw 'invalid argument %c/%X' % [self[i],self[i]] end end when ?( i += 1 case self[i] when ?D,?O,?P,?Q,?? i += 1 while self[i] && self[i] > 0x20 i += 1 if self[i] > 0x20 i += 1 count += 1 else throw 'invalid argument %c/%X' % [self[i],self[i]] end end end end when ?( i += 1 case self[i] when ?I,?B,?J,?H i += 1 while self[i] && self[i] > 0x20 i += 1 count += 1 end end end else throw 'invalid argument %c/%X' % [self[i],self[i]] end end count end def char_count_sjis count = 0 trail = false self.each_byte do |c| if trail trail = false elsif c < 0x80 || (c-32)>>6 == 2 count += 1 else trail = true count += 1 end end count end def char_count_euc count = 0 self.each_byte{|c|count += c==0x8F ? 2 : c < 0x80 ? 6 : 3} count / 6 end def char_count_utf8 count = 0 self.each_byte{|c|c>>6==2||count+=1} count end def char_count_utf16 count = 0 self.each_byte{count+=1} count / 2 end def char_count_utf32 count = 0 self.each_byte{count+=1} count / 4 end end
最初の JIS は完璧に力技です。エスケープシーケンスを見て行き、それに対応したバイトごとに文字数を加算していきます。拡張したい方(いるのか?)は ISO-IR を見ながらコードを追加すればISO 2022 系なら何でも対応できます。
次の Shift_JIS になるとぐっと短くなりました。状態を持たないっていいですねぇ。いかに開発当時 Shift_JIS が画期的だったかこれだけでも推察できそうな気がしないでもないです(何 ちなみに、半角カタカナにももちろん対応しています。
EUC-JP ではとうとう実質一行にできました。EUC-JP は Shift_JIS のように トレールバイトが 0x80 以下に入ってくることが無いのでだいぶ楽に書くことができます。途中の count が実際の文字数の 6 倍になっているのは少々トリッキーかもしれません。一応説明すると、EUC-JP は 1 octet, 2 octet, 3 octet から構成されるので、1, 2, 3 の公倍数をとっているわけですね。
UTF-8 ではさらに短くなっています。これは UTF-8 ではリードバイトとトレールバイトが別の空間に存在しているためです。つまり、UTF-8 はトレールバイトが常に 0x80 - 0xBF に位置しているため、それ以外のときだけ数えているわけです。
UTF-16 と UTF-32 もついでに書いておきました。これは言うまでもありませんね。