Rubyで指定バイト数を超えない文字列の取得

文字エンコーディングにUTF-8を使用した場合、1文字は1バイト〜4バイトです。

ある文字列の先頭からn文字の文字列を取り出すには次のようにできます。

str = "本日は晴天なり"
n = 3
str[0, n]    #=> "本日は"

先頭からnバイトを超えない最大の文字列を取り出す方法を考えてみました。

愚直に数える

str = "本日は晴天なり"
n = 10
out = ""
str.each_char do |c|
  break if out.bytesize + c.bytesize > n
  out.concat c
end
out   #=> "本日は"

inject を使うと少しかっこ良くなるかもしれない

str = "本日は晴天なり"
n = 10
str.each_char.inject(''){|a,b| break a if (a+b).bytesize > n; a+b}  #=> "本日は"

バイナリデータとしてnバイト抜き出した後に不正なバイト列を消す

str = "本日は晴天なり"
n = 10
str.b[0, n].force_encoding("utf-8").scrub("")   #=> "本日は"

ちなみに最後の scrub がないと "本日は\xE6" になってしまいます。 最後だけでなく途中に文字として不正なバイトがあった場合はそれも消えます。

[追記]

Twitter で byteslice というメソッドがあるのを教えてもらいました。考え方は同じですが、こっちの方がすっきり書けますね。

StringIO#gets を使う

str = "本日は晴天なり"
n = 10

require 'stringio'
StringIO.new(str).gets(nil, 10)  #=> "本日は晴"
s.chop! if s.bytesize > n
s                                #=> "本日は"

gets は指定バイト数が文字境界にない場合は文字境界まで読むので1文字多くなることがあるので、最後に調整してます。


なんかどれもいまいちな気がします。もっといい方法ありますかねー。