Goで文字列の文字数を取得するにはどうすればよいですか?
たとえば、文字列がある場合"hello"
、メソッドは を返す必要があり5
ます。£ は UTF-8 で 2 バイトでエンコードされているため、文字数ではなくlen(str)
バイト数を返すので、1 ではなく 2 を返します。len("£")
Goで文字列の文字数を取得するにはどうすればよいですか?
たとえば、文字列がある場合"hello"
、メソッドは を返す必要があり5
ます。£ は UTF-8 で 2 バイトでエンコードされているため、文字数ではなくlen(str)
バイト数を返すので、1 ではなく 2 を返します。len("£")
RuneCountInString
utf8 パッケージから試すことができます。
p のルーンの数を返します
このスクリプトに示されているように、「世界」の長さは 6 (中国語で書かれた場合: 「世界」) かもしれませんが、そのルーン数は 2 です。
package main
import "fmt"
import "unicode/utf8"
func main() {
fmt.Println("Hello, 世界", len("世界"), utf8.RuneCountInString("世界"))
}
フローズンはコメントに次のように追加します。
len()
実際には、タイプキャストするだけでルーン文字を上書きできます。
len([]rune("世界"))
印刷されます2
。少なくとも Go 1.3 では。
また、CL 108985 (2018 年 5 月、Go 1.11 用) によりlen([]rune(string))
最適化されました。(問題 24923 を修正)
コンパイラはlen([]rune(string))
パターンを自動的に検出し、 for r := range s 呼び出しに置き換えます。
文字列内のルーンをカウントする新しいランタイム関数を追加します。パターンを検出するようにコンパイラを変更し、
len([]rune(string))
それを新しいルーン カウント ランタイム関数に置き換えます。RuneCount/lenruneslice/ASCII 27.8ns ± 2% 14.5ns ± 3% -47.70% RuneCount/lenruneslice/Japanese 126ns ± 2% 60 ns ± 2% -52.03% RuneCount/lenruneslice/MixedLength 104ns ± 2% 50 ns ± 1% -51.71%
Stefan Steigerは、ブログ投稿 " Text normalization in Go "を指摘しています。
キャラクターとは?
文字列のブログ投稿で述べたように、文字は複数のルーンにまたがることができます。
たとえば、'e
' と '◌́◌́' (急性の "\u0301") を組み合わせて 'é' (e\u0301
NFD では " ") を形成できます。これら 2 つのルーンを合わせて 1 つのキャラクターです。文字の定義は、アプリケーションによって異なる場合があります。正規化
のために、次のように定義します。
- スターターで始まる一連のルーン文字、
- 他のルーンと逆方向に変更または結合しないルーン、
- その後に、スターター以外の空のシーケンス、つまりルーン (通常はアクセント) が続く可能性があります。
正規化アルゴリズムは、一度に 1 文字ずつ処理します。
そのパッケージとそのIter
タイプを使用すると、「文字」の実際の数は次のようになります。
package main
import "fmt"
import "golang.org/x/text/unicode/norm"
func main() {
var ia norm.Iter
ia.InitString(norm.NFKD, "école")
nc := 0
for !ia.Done() {
nc = nc + 1
ia.Next()
}
fmt.Printf("Number of chars: %d\n", nc)
}
ここでは、Unicode 正規化フォームNFKD「互換性分解」を使用します。
Oliverの回答は、UNICODE TEXT SEGMENTATIONが、特定の重要なテキスト要素 (ユーザーが認識する文字、単語、文) 間のデフォルトの境界を確実に決定する唯一の方法であることを示しています。
そのためには、 rivo/unisegのようなUnicode Text Segmentationを行う外部ライブラリが必要です。
これは実際には「書記素クラスター」をカウントします。このクラスターでは、複数のコード ポイントが 1 つのユーザー認識文字に結合される場合があります。
package uniseg
import (
"fmt"
"github.com/rivo/uniseg"
)
func main() {
gr := uniseg.NewGraphemes("!")
for gr.Next() {
fmt.Printf("%x ", gr.Runes())
}
// Output: [1f44d 1f3fc] [21]
}
3 つのルーン (Unicode コード ポイント) があるにもかかわらず、2 つの書記素。
「 GO で文字列を操作してそれらを逆にする方法」で他の例を見ることができます。
だけでも書記素は 1 つですが、Unicode からコード ポイントへのコンバーター、4 つのルーン:
次のように文字列を []rune に変換することにより、パッケージなしで rune の数を取得する方法がありますlen([]rune(YOUR_STRING))
。
package main
import "fmt"
func main() {
russian := "Спутник и погром"
english := "Sputnik & pogrom"
fmt.Println("count of bytes:",
len(russian),
len(english))
fmt.Println("count of runes:",
len([]rune(russian)),
len([]rune(english)))
}
バイト数 30 16
ルーンの数 16 16
「キャラクター」とは何かの定義に大きく依存します。「rune equals a character」がタスクに適している場合 (通常はそうではありません)、VonC による回答が最適です。それ以外の場合は、Unicode 文字列内のルーンの数が興味深い値になる状況はほとんどないことに注意してください。そして、そのような状況でも、可能な場合は、UTF-8 デコードの手間が 2 倍になるのを避けるために、ルーン文字が処理されるときに文字列を「トラバース」しながらカウントを推測することをお勧めします。
書記素クラスターを考慮する必要がある場合は、regexp または unicode モジュールを使用してください。書記素クラスタの長さは無制限であるため、検証にはコードポイント(ルーン)またはバイト数のカウントも必要です。非常に長いシーケンスを削除する場合は、シーケンスがストリーム セーフ テキスト形式に準拠しているかどうかを確認してください。
package main
import (
"regexp"
"unicode"
"strings"
)
func main() {
str := "\u0308" + "a\u0308" + "o\u0308" + "u\u0308"
str2 := "a" + strings.Repeat("\u0308", 1000)
println(4 == GraphemeCountInString(str))
println(4 == GraphemeCountInString2(str))
println(1 == GraphemeCountInString(str2))
println(1 == GraphemeCountInString2(str2))
println(true == IsStreamSafeString(str))
println(false == IsStreamSafeString(str2))
}
func GraphemeCountInString(str string) int {
re := regexp.MustCompile("\\PM\\pM*|.")
return len(re.FindAllString(str, -1))
}
func GraphemeCountInString2(str string) int {
length := 0
checked := false
index := 0
for _, c := range str {
if !unicode.Is(unicode.M, c) {
length++
if checked == false {
checked = true
}
} else if checked == false {
length++
}
index++
}
return length
}
func IsStreamSafeString(str string) bool {
re := regexp.MustCompile("\\PM\\pM{30,}")
return !re.MatchString(str)
}