LoginSignup
4
2

More than 1 year has passed since last update.

Ruby で文字数を数える

Last updated at Posted at 2023-05-16

これは何?

Ruby で "shuffle" を 7文字と数える方法を思いつた。
"shuffle" ってのはこういうやつ。

ruby
"shuffle".then{ [_1.chars, _1.size ] } #=> [["s", "h", "u", "ffl", "e"], 5]

それだけだとしょぼいので、文字の数え方という一般的な話題を装った記事にしてみた。

そもそも文字とはなにかという問題もあったりはするけれど、そのあたりは

に書いた。

数える方法

バイト数

バイト数は文字数じゃないわけだけど、バイト数を数えるメソッド String#bytesize がある。

ruby の場合、UTF-16 にしても バイト列であることには変わらない

ruby
"hoge".bytesize #=> 4
"hoge".encode("utf-16le").bytesize #=> 8
"shuffle".encode("utf-16le").bytesize #=> 14
"shuffle".encode("utf-16le").bytesize #=> 10
"𩸽".encode("utf-16le").bytesize #=> 4

"shuffle" は、 (U+fb04) という ユニコードキャラクタを含んでいる。
"𩸽" は、いわゆるサロゲートペア文字。ほっけ。Java の char や Windows の wchar_t だと 二個になる。

コードポイント数

コードポイントの数を数えるともうちょっと文字数らしくなる。
String#size または String#length を使う。

"hoge".size #=> 4
"hoge".encode("utf-16le").size #=> 4
"shuffle".size #=> 7
"shuffle".size #=> 5
"𩸽".encode("utf-16le").size #=> 1
"👨‍👩‍👦".size #=> 5
"た\u{3099}んこ\u{3099}".tap{ p [_1, _1.size] } #=> ["だんご", 5]
"だんご".tap{ p [_1, _1.size] } #=> ["だんご", 3]

とはいえ。unicode 結合文字列がある場合など、コードポイント数とパット見の文字数が合わなくなる。

正規化してコードポイント数

String#unicode_normalize をすると「た」+ U+3099 が「だ」になる

"だんご".unicode_normalize.size #=> 3
"た\u{3099}んこ\u{3099}".unicode_normalize.size #=> 3
"ぬ\u{3099}".unicode_normalize.size #=> => 2
"👨‍👩‍👦".unicode_normalize.size #=> 5
"shuffle".unicode_normalize.size #=> 5

しかし「ぬ」のような濁点を付けた字にコードポイントがない場合はコードポイント数が減らないし、"shuffle" が 7文字になったりもしない。

Grapheme cluster の数

String#grapheme_clusters を使うと、だいぶ文字数っぽくなる

ruby
"👨‍👩‍👦".grapheme_clusters.size #=> 1
"ぬ\u{3099}".grapheme_clusters.size #=> 1
"shuffle".grapheme_clusters.size #=> 5

が、それでも "shuffle" は 5文字のまま。

突然の capitalize

ところで、String#capitalize というメソッドがある。

こういうやつ。

ruby
%w(jazz JAZZ dzsessz DZSESSZ).map(&:capitalize)
#=> ["Jazz", "Jazz", "Dzsessz", "Dzsessz"]

最初の文字を大文字にする。
と思うとちょっと正しくなくて、最初の文字を文頭にふさわしい文字にして、残りを文頭以外にふさわしい文字にする。

ハンガリー語などで使われるらしい dz (U+01F3) という文字は、大文字 DZ (U+01F1) の他に、大文字でも小文字でもない「title case」の Dz (U+01F2) がある。ruby の String#capitalize はこれに対応していて、dz (U+01F3) で始まる語を capitalize すると Dz (U+01F2) で始まるようになる。

で。

"shuffle" の 4コードポイント目の capitalize すると

"ffl".capitalize #=> "Ffl"

となる。「最初の f だけが大文字」という合字は、少なくともユニコード内にはないのでしかたなくばらばらにする。という意外な実装になっている。

というわけで、これを使うと "shuffle" を 7文字だと数えることができる。

"shuffle".chars.map{ _1.capitalize.size }.sum #=> 7

まあ capitalize じゃなくて upcase でもいいんだけどね。

それでも ㌠ は 1文字

ここまで色々やってきたけど

  • "くま㌠" # 3文字? 7文字?
  • "2㎧" # 2文字? 4文字?
  • "≠″ャ」レ" # 「ギャル」と読む。5文字? 3文字?
  • "㍻元年" # 3文字? 4文字?

と、色々ある。

そもそも

そもそも、文字数が知りたいというとき。
その文字数がなんのために必要なのかがわからないとどの数え方が正解なのかはわからない。

Windows のファイル名なら、およそ文字数らしくないけど、 UTF-16LE のバイト数の半分が正解と思う。

入力フォームの制限なら、フォームに入れた文字列が到達する先のデータベースの都合に合わせるのが正解。

デザインの観点でパット見で文字多すぎにならないようにということなら、文字数ではなく、その場所で使われるフォントでピクセル数を数えるのが正解かもしれない。

というわけで

というわけで。
文字数の数え方にも色々あるよ。という話でした。

4
2
0

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
4
2