文字エンコーディングに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 というメソッドがあるのを教えてもらいました。考え方は同じですが、こっちの方がすっきり書けますね。
@tmtms `str.byteslice(0, 10).scrub(‘’)` でしょうか。
— Akira Matsuda (@a_matsuda) 2016年7月27日
StringIO#gets を使う
str = "本日は晴天なり" n = 10 require 'stringio' StringIO.new(str).gets(nil, 10) #=> "本日は晴" s.chop! if s.bytesize > n s #=> "本日は"
gets は指定バイト数が文字境界にない場合は文字境界まで読むので1文字多くなることがあるので、最後に調整してます。
なんかどれもいまいちな気がします。もっといい方法ありますかねー。