LoginSignup
13
10

More than 5 years have passed since last update.

Ruby / Railsでマルチバイト文字の境界を意識しつつ指定のバイト数以下に切り捨てる

Last updated at Posted at 2016-01-29

DBに文字列を格納する時にレコード長の制限があったりして、指定のバイト数以下に切り捨てたいことがあります。

例として8バイト以下に切り捨てたい場合で説明します。説明の便宜上、上限値をMAX_BYTESIZEで定義しておきます。

pry(main)> MAX_BYTESIZE=8
=> 8

ASCII文字だけならこんなかんじで簡単なんですが、

pry(main)> 'abcdefghijklmn'.byteslice(0...MAX_BYTESIZE)
=> "abcdefgh"

マルチバイト文字が入ってくると若干めんどくさいです。
マルチバイト文字の境界を何も考えずにぶった切ると、こんなかんじで末尾にゴミが入ってしまう可能性があります。

pry(main)> 'こんにちは'.byteslice(0...MAX_BYTESIZE)
=> "こん\xE3\x81"

こんなよくありがちな用途の汎用メソッドがRailsにないはずがないと思って調べたら、
ActiveSupport::Multibyte::Chars#limit でできることが分かりました。

pry(main)> 'こんにちは'.bytesize
=> 15
pry(main)> 'こん'.bytesize
=> 6
pry(main)> 'こんに'.bytesize
=> 9
pry(main)> 'こんにちは'.mb_chars.limit(MAX_BYTESIZE)
=> #<ActiveSupport::Multibyte::Chars:0x007fb59e37d420 @wrapped_string="こん">
pry(main)> 'こんにちは'.mb_chars.limit(MAX_BYTESIZE).to_s
=> "こん"
pry(main)> 'こんにちは'.mb_chars.limit(MAX_BYTESIZE).to_s.bytesize
=> 6

素のRubyで使いたい場合はActiveSupportだけ個別にインストールすればよいんじゃないかと。


(2017/04/18追記)
Ruby 2.1.0で追加されたString#scrubという不正なバイト列を除去するメソッドを使った方が素のRubyだけで実装できるしパフォーマンスも速かった。

[12] pry(main)> "寿司\u{1f363}"
=> "寿司🍣"
[13] pry(main)> "寿司\u{1f363}".size
=> 3
[14] pry(main)> "寿司\u{1f363}".bytesize
=> 10
[15] pry(main)> "寿司\u{1f363}".byteslice(0,10)
=> "寿司🍣"
[16] pry(main)> "寿司\u{1f363}".byteslice(0,10).scrub('')
=> "寿司🍣"
[17] pry(main)> "寿司\u{1f363}".byteslice(0,9)
=> "寿司\xF0\x9F\x8D"
[18] pry(main)> "寿司\u{1f363}".byteslice(0,9).scrub('')
=> "寿司"

手元のMacBook Pro (Retina, 13-inch、Early 2015)で軽くベンチーマークとってみた

require 'benchmark'
require 'active_support/multibyte'
MAX_LINE_SIZE = 10000
msg = "あ" * 3333 + "\u{1f363}"

Benchmark.bm 100 do |r|
  r.report "raw" do
    msg
  end
  r.report "byteslice" do
    msg.byteslice(0, MAX_LINE_SIZE)
  end
  r.report "scrub" do
    msg.byteslice(0, MAX_LINE_SIZE).scrub('')
  end
  r.report "mb_chars" do
    msg.mb_chars.limit(MAX_LINE_SIZE).to_s
  end
end
                user     system      total        real
raw           0.000000   0.000000   0.000000 (  0.000005)
byteslice     0.000000   0.000000   0.000000 (  0.000010)
scrub         0.000000   0.000000   0.000000 (  0.000137)
mb_chars      0.000000   0.000000   0.000000 (  0.001624)

String#scrub使うほうがActiveSupport::Multibyte::Chars#limitよりも一桁速いですね。

13
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10