これは何?
Ruby で "shuffle"
を 7文字と数える方法を思いつた。
"shuffle"
ってのはこういうやつ。
"shuffle".then{ [_1.chars, _1.size ] } #=> [["s", "h", "u", "ffl", "e"], 5]
それだけだとしょぼいので、文字の数え方という一般的な話題を装った記事にしてみた。
そもそも文字とはなにかという問題もあったりはするけれど、そのあたりは
に書いた。
数える方法
バイト数
バイト数は文字数じゃないわけだけど、バイト数を数えるメソッド String#bytesize
がある。
ruby の場合、UTF-16 にしても バイト列であることには変わらない
"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"
は、ffl
(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
を使うと、だいぶ文字数っぽくなる
"👨👩👦".grapheme_clusters.size #=> 1
"ぬ\u{3099}".grapheme_clusters.size #=> 1
"shuffle".grapheme_clusters.size #=> 5
が、それでも "shuffle"
は 5文字のまま。
突然の capitalize
ところで、String#capitalize
というメソッドがある。
こういうやつ。
%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コードポイント目の ffl
を 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 のバイト数の半分が正解と思う。
入力フォームの制限なら、フォームに入れた文字列が到達する先のデータベースの都合に合わせるのが正解。
デザインの観点でパット見で文字多すぎにならないようにということなら、文字数ではなく、その場所で使われるフォントでピクセル数を数えるのが正解かもしれない。
というわけで
というわけで。
文字数の数え方にも色々あるよ。という話でした。