読者です 読者をやめる 読者になる 読者になる

文字を数える

Ruby Char

artonさんの出題解答例とか。

以下のような解き方もおもしろいかな。もちろん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-16UTF-32 もついでに書いておきました。これは言うまでもありませんね。