Overview
Go 言語で文字数をカウントする方法について記載します。
そもそも文字数を数えるとは?
LINE Engineering のこのブログが詳しくてわかりやすいです。
ある文字列の文字数を計算するときに、コンピューター上で「何文字」として扱われるかは、文字の定義によって異なります。
Go における文字数のカウント方法
utf8.RuneCountInString を使う
マルチバイトの文字(漢字など)を使ったテキストの総文字数を Go でシンプルに計算するときは https://golang.org/pkg/unicode/utf8/#RuneCountInString を使います。
例えば以下のように使用します。
package main import ( "fmt" "unicode/utf8" ) func main() { s := "こんにちは" ss := utf8.RuneCountInString(s) fmt.Println(ss) }
この実装の出力結果は 5
です。
しかしこの文字数の数え方には 絵文字が入った文字列では正確に文字数をカウントできない
という漏れがあります。例えば以下のようなケースでは✌️ は Rune Slice の長さ 2 のマルチバイト文字列であることがわかります。
package main import ( "fmt" "unicode/utf8" ) func main() { s := "✌️" ss := utf8.RuneCountInString(s) fmt.Println(ss) }
Grapheme Cluster を使う
ユーザーが認識する文字。文字体系で表現できる文字の最小単位。1 Graphemeは、N code pointで構成されています。
ref: https://engineering.linecorp.com/ja/blog/the-7-ways-of-counting-characters/
Go で Grapheme を使う
OSS として出してくれてるライブラリがあります。
実装方法は上記の Readme に書いてる通りです。
このライブラリ (というか Grapheme) は Rune Slice が 2 以上あるマルチバイと文字列も「1文字としてカウント」してくれます。
具体的に文字数を数えるときは以下のように実装します。
func main() { var sum int gr := uniseg.NewGraphemes("✌️!") for gr.Next() { sum++ } fmt.Printf("%d", sum) }
この出力は2です。
✌️ + ! の文字列しかないので 2 になります。 utf8. RuneCountInString を使用した場合、上記の文字列は 3 と表示されます。つまりマルチバイトの文字まで数えてしまいます
余談: 同じ絵文字でも文字数が違う場合がある
👉 という絵文字にも以下のような種類が存在します。
この別の肌の色をどう表現してるのかを調べました。
package main import ( "fmt" "github.com/rivo/uniseg" ) func main() { s := "👉" gr := uniseg.NewGraphemes(s) for gr.Next() { fmt.Printf("%x", gr.Runes()) } } // Output: [1f449]
別の肌の色では以下でした。
package main import ( "fmt" "github.com/rivo/uniseg" ) func main() { s := "👉🏻" gr := uniseg.NewGraphemes(s) for gr.Next() { fmt.Printf("%x", gr.Runes()) } } // Output: [1f449 1f3fb]
👉 に色の文字コードが追加された出力されました。なお utf8.RuneCountInString
を使用した場合、👉 は1文字ですが、👉🏻 は2文字で計算されます。
同じ絵文字でも文字数が違うことがわかりました。
ちなみに Code Point を調べるとこのことは一発でわかります。
👉 U+1F449
🏻 U+1F3FB
と別のデータとして扱われてますね。
まとめ
目で見てる絵文字と、コンピュータ上でどう扱われるかには差があるので「文字数をカウントする」という実装はかなり奥が深い(というかややこしい)ことがわかりました。
Grapheme cluster は覚えておこうと思います。