文字列の文字数を取得するにはどうすればよいですか?


145

Goで文字列の文字数を取得するにはどうすればよいですか?

たとえば、文字列がある場合"hello"、メソッドはを返し5ます。私はそれを見たlen(str)リターンをバイト数としないので、文字の数をlen("£")£は、UTF-8で2バイトで符号化されるので、リターン2の代わりに、1。


2
5を返します。ファイルのエンコーディングがUTF-8の場合はそうではないかもしれません。
Moshe Revah、2012年

7
はい、この場合は対応していますが、1バイトに変換されないアラビア語などの他のUTF-8文字については一般的なものにしたいと考えています。
Ammar 2012年

回答:


177

RuneCountInStringutf8パッケージから試すことができます。

ルーンの数をpで返します

このスクリプトに示されているように、「ワールド」の長さは6(中国語で書かれた場合は「世界」)かもしれませんが、ルーンカウントは2です。

package main

import "fmt"
import "unicode/utf8"

func main() {
    fmt.Println("Hello, 世界", len("世界"), utf8.RuneCountInString("世界"))
}

Phrozenコメントに追加します

実際には、len()型キャストだけでルーン文字を上書きできます。
len([]rune("世界"))印刷されます2。Go 1.3で少なくとも。


そして、CL 108985(2018年5月、Go 1.11用)でlen([]rune(string))最適化されました。(問題24923を修正)

コンパイラはlen([]rune(string))パターンを自動的に検出し、それをr:= range s呼び出しに置き換えます。

文字列内のルーンをカウントする新しいランタイム関数を追加します。コンパイラを変更してパターンを検出len([]rune(string)) し、新しいルーンカウントランタイム関数に置き換えます。

RuneCount/lenruneslice/ASCII                  27.8ns ± 2%  14.5ns ± 3%  -47.70%  (p=0.000 n=10+10)
RuneCount/lenruneslice/Japanese                126ns ± 2%    60ns ± 2%  -52.03%  (p=0.000 n=10+10)
RuneCount/lenruneslice/MixedLength             104ns ± 2%    50ns ± 1%  -51.71%  (p=0.000 n=10+9)

ステファン・スタイガーのブログ記事へのポイント「ゴーでのテキストの正規化

キャラクターとは?

文字列のブログ投稿で述べたように、文字は複数のルーンにまたがることができます
たとえば、 ' e'と '◌́◌́'(acute "\ u0301")を組み合わせて 'é'(e\u0301NFD では" ")を作成できます。これらの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を指しています。

そのためには、Unicodeテキストのセグメンテーションを行うrivo / unisegのような外部ライブラリが必要です。

これは、実際には「書記素クラスタ」を数えます。この場合、複数のコードポイントが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つのルーン文字:



5
これはルーンの数だけを伝え、グリフの数は伝えません。多くのグリフは複数のルーン文字でできています。
Stephen Weinberg

5
実際には、型キャストだけでルーンに対してlen()を実行できます... len([] rune( "世界"))は2を出力します。
冷凍、2014

3
@VonC:実際には、文字(Glyphの口語言語の用語)は-時々-複数のルーンにまたがることができるので、この答えは、正確な技術用語WRONGを使用することです。必要なのは、ルーン数ではなく、Grapheme / GraphemeCluster数です。たとえば、 'e'と '◌́'(急性 "\ u0301")を組み合わせて 'é'(NFDでは "e \ u0301")を形成できます。しかし、人間は(正しく)éを考慮します。一つの文字として..どうやらそれはテルグ語で違いを生みます。ただし、使用するキーボード/ロケールに応じて、おそらくフランス語も使用できます。 blog.golang.org/normalization
Stefan Steiger

1
@JustinJohnson同意する。私は以前に賛成したオリバーをよりよく参照するために答えを編集しました。
VonC、

42

文字列を[] rune asに変換することにより、パッケージなしでルーンの数を取得する方法があります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


5

「キャラクター」が何であるかのあなたの定義に大きく依存します。「ルーンが文字に等しい」があなたのタスクに適している場合(通常はそうではありません)、VonCによる回答が最適です。それ以外の場合、Unicode文字列のルーンの数が興味深い値である状況はほとんどないことに注意してください。そして、これらの状況でも、可能であれば、ルーンが処理されるときに文字列を「トラバース」しながらカウントを推測して、UTF-8デコードの作業が2倍になるのを回避することをお勧めします。


とき、あなたは考えていない文字としてルーンを参照してください?Go仕様では、ルーン文字をUnicodeコードポイントとして定義しています:golang.org/ref/spec#Rune_literals
Thomas Kappler 2012年

また、デコードの作業が2倍になるのを避けるために、[] rune(str)を実行し、それを処理して、完了したら文字列に変換し直します。文字列をトラバースするときにコードポイントを追跡するよりも簡単だと思います。
Thomas Kappler 2012年

4
@ThomasKappler:いつ?まあ、ルーンはキャラクターではないとき、それは一般的にそうではありません。一部のルーンだけが文字と同等であり、すべてではありません。「rune == character」は、Unicode文字のサブセットにのみ有効であると想定しています。例:en.wikipedia.org/wiki/...
ZZZZ

@ThomasKappler:しかし、そのように見ると、たとえばJavaのString.length()メソッドは文字数も返しません。どちらもCocoaの行いませんNSString-length方法。それらは単にUTF-16エンティティの数を返します。ただし、コードポイントのカウントに線形時間がかかるため、コードポイントの実際の数はほとんど使用されません。
newacct 2012年

5

書記素クラスタを考慮する必要がある場合は、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) 
}

これをありがとう。私はあなたのコードを試してみましたが、次のようないくつかの絵文字の書記素では機能しません:🖖🏿🇸🇴。それらを正確に数える方法について何か考えはありますか?
ビョルンロシュ

コンパイルされた正規表現はvar、関数の外部として抽出する必要があります。
ドルメン

5

文字列の長さを取得するには、いくつかの方法があります。

package main

import (
    "bytes"
    "fmt"
    "strings"
    "unicode/utf8"
)

func main() {
    b := "这是个测试"
    len1 := len([]rune(b))
    len2 := bytes.Count([]byte(b), nil) -1
    len3 := strings.Count(b, "") - 1
    len4 := utf8.RuneCountInString(b)
    fmt.Println(len1)
    fmt.Println(len2)
    fmt.Println(len3)
    fmt.Println(len4)

}

3

特に絵文字(ただし、タイ語、韓国語、アラビア語などの一部の言語)を扱っている場合は、これまでに提供されたどの回答も期待どおりの文字数を提供しないことに注意してください。VonCの提案は以下を出力します:

fmt.Println(utf8.RuneCountInString("🏳️‍🌈🇩🇪")) // Outputs "6".
fmt.Println(len([]rune("🏳️‍🌈🇩🇪"))) // Outputs "6".

これは、これらのメソッドがUnicodeコードポイントのみをカウントするためです。複数のコードポイントで構成できる多くの文字があります。

正規化パッケージを使用する場合も同様です。

var ia norm.Iter
ia.InitString(norm.NFKD, "🏳️‍🌈🇩🇪")
nc := 0
for !ia.Done() {
    nc = nc + 1
    ia.Next()
}
fmt.Println(nc) // Outputs "6".

正規化は実際には文字を数えることと同じではなく、多くの文字を1つのコードポイントに相当するものに正規化することはできません。

masakielasticの答えは近づきますが、修飾子のみを処理します(虹色の旗には修飾子が含まれているため、独自のコードポイントとしてはカウントされません)。

fmt.Println(GraphemeCountInString("🏳️‍🌈🇩🇪"))  // Outputs "5".
fmt.Println(GraphemeCountInString2("🏳️‍🌈🇩🇪")) // Outputs "5".

Unicode文字列を(ユーザーが認識する)文字、つまり書記素クラスタに分割する正しい方法は、Unicode Standard Annex#29で定義されています。ルールはセクション3.1.1にあります。github.com/rivo/unisegのあなたは、文字列内の文字の正しい数を決定することができますので、パッケージには、これらの規則を実装します。

fmt.Println(uniseg.GraphemeClusterCount("🏳️‍🌈🇩🇪")) // Outputs "2".

0

私は正規化を少し速くすることを試みました:

    en, _ = glyphSmart(data)

    func glyphSmart(text string) (int, int) {
        gc := 0
        dummy := 0
        for ind, _ := range text {
            gc++
            dummy = ind
        }
        dummy = 0
        return gc, dummy
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.