UTF-8 文字列を省略して一定の表示幅に収める
つまり Go の go/runewidth の runewidth.Truncate みたいなことをしたい。
GitHub - mattn/go-runewidth
https://github.com/mattn/go-runewidth
で、どうやら unicode-display_width という gem があるらしい。
GitHub - janlelis/unicode-display_width: Monospace Unicode character width in Ruby
https://github.com/janlelis/unicode-display_width
道具は揃ったので、順当に実装してみる。
require 'unicode/display_width' def truncate_string(str, len, omission) return str.dup unless Unicode::DisplayWidth.of(str) > len lim = len - Unicode::DisplayWidth.of(omission) n = 0 chars = str.each_char.take_while{|c| (n += Unicode::DisplayWidth.of(c)) <= lim } chars.join << omission end
ここでちょっと欲が出た。
Unicode::DisplayWidth.of(str) == str.each_char.inject(0){|r, c| r += Unicode::DisplayWidth.of(c) }
のはずである。
すると、実質的に str に対して二回 Unicode::DisplayWidth.of が呼ばれているってことになるな、と考える。
これ省けばそっちのが速いんじゃね?
けど、文字ごとにメソッドを呼ぶオーバーヘッドもあるし?
というわけで試してみる。 2 回呼んでるならメモ化すればいいじゃないの精神。
def truncate_string2(str, len, omission) widths = Hash.new{|h, k| h[k] = Unicode::DisplayWidth.of(k) } return str.dup unless str.each_char.inject(0){|r, c| r + widths[c] } > len lim = len - widths[omission] n = 0 chars = str.each_char.take_while{|c| (n += widths[c]) <= lim } chars.join << omission end
ベンチ。
user system total real ascii: no memo 2.309000 0.000000 2.309000 ( 2.298512) ascii: memo 0.562000 0.000000 0.562000 ( 0.566717) mb: no memo 0.999000 0.000000 0.999000 ( 1.001561) mb: memo 0.733000 0.000000 0.733000 ( 0.725408)
効いてるっぽい。
さらによく考えると、 str を終わりまで舐めて全長を出すのは無駄である。 len を超えた時点で省略するのは確定するんだから。
というわけでほい。
def truncate_string3(str, len, omission) widths = Hash.new{|h, k| h[k] = Unicode::DisplayWidth.of(k) } n = 0 over_len_p = false str.each_char do |c| if (n += widths[c]) > len over_len_p = true break end end return str.dup unless over_len_p lim = len - widths[omission] n = 0 chars = str.each_char.take_while{|c| (n += widths[c]) <= lim } chars.join << omission end
ベンチ。
user system total real ascii: all len 0.577000 0.000000 0.577000 ( 0.569295) ascii: break when over len 0.374000 0.000000 0.374000 ( 0.370433) mb: all len 0.734000 0.000000 0.734000 ( 0.731004) mb: break when over len 0.421000 0.000000 0.421000 ( 0.416125)
これもまた効いてるかな。
とりあえず自分が使うのに問題はなさそうだから一旦満足。