ペペロンチーノ街道

無知を晒す

Go言語 絵文字を含んだ文字列の長さを獲得する

概要

Goで絵文字とかを扱ったことがなかったのでメモ

内容

Go言語での文字列はUTF-8で扱われる。UTF-8は1文字を1~4byteで表現する。 例えば、 "a"61

"あ"e3 81 82

"😀"f0 9f 98 80

go.dev

そのため

s := "aあ😀"
fmt.Println(len(s)) // 8

lenでbyte列の長さが得られる。

一方でbyte列の長さではなく、人間の目で認識している文字列の長さ、 すなわち、len(s)=3が欲しい場合、 rune型にキャストしてカウントしてあげると良さそう。 ここで、rune型はunicodeコードポイントを表す型(実態はint32)(よく知らんかった)

s := "aあ😀"
fmt.Println(len([]rune(s))) // 3

または、utf8.RuneCountInStringruneスライスの長さをカウントする事もできる

s := "aあ😀"
fmt.Println(utf8.RuneCountInString(s)) // 3

しかし、これでは4byte以上で表現される絵文字のカウントがうまく行かない。例えば👨‍👩‍👧‍👦は他の絵文字の組み合わせで表現されている(多分)

s := "👨‍👩‍👧‍👦"
for i := 0; i < len(s); i++ {
    fmt.Printf("%x ", s[i]) // f0 9f 91 a8 e2 80 8d f0 9f 91 a9 e2 80 8d f0 9f 91 a7 e2 80 8d f0 9f 91 a6
}
fmt.Println([]rune(s)) // [97 12354 128104 8205 128105 8205 128103 8205 128102]

このような絵文字も「1文字」としてカウントしたいとき、どうすればいいか調べていたら、ライブラリを発見。

github.com

runeスライスの長さが2以上の文字をも1文字としてカウントしてくれる。 grapheme clusterとやらをカウントしているらしい。

func main() {
    s := ("aあ👨‍👩‍👧‍👦")
    fmt.Println(uniseg.GraphemeClusterCount(s)) // 3
}

おわりに

調べるうちに、Unicodeとかの理解が捗った。 todo: ライブラリの中身をもうちょい読む

参考

UTF-8 - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN The Go Programming Language Specification - The Go Programming Language

github.com