it-swarm.com.de

Wie bekomme ich die Anzahl der Zeichen in einem String?

Wie kann ich die Anzahl der Zeichen einer Zeichenfolge in Go ermitteln?

Wenn ich beispielsweise einen String "hello" habe, sollte die Methode 5 zurückgeben. Ich habe gesehen, dass len(str) die Anzahl der Bytes und nicht die Anzahl der Zeichen zurückgibt, also len("£") gibt 2 statt 1 zurück, da £ in UTF-8 mit zwei Bytes codiert ist.

104
Ammar

Sie können RuneCountInString aus dem utf8-Paket versuchen.

gibt die Anzahl der Runen in p zurück

dass, wie in dieses Skript : die Länge von "World" 6 ist (wenn auf Chinesisch geschrieben: "世界"), aber die Runenanzahl 2 ist:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen fügt in den Kommentaren hinzu :

Tatsächlich können Sie len() über Runen ausführen, indem Sie einfach Casting ausführen.
len([]rune("世界")) druckt 2. Etwas in Gange 1.3.


Und mit CL 108985 (Mai 2018, für Go 1.11) ist len([]rune(string)) jetzt optimiert. (Fixes Ausgabe 24923 )

Der Compiler erkennt len([]rune(string))-Muster automatisch und ersetzt es durch den Aufruf von r: = range.

Fügt eine neue Laufzeitfunktion hinzu, um Runen in einer Zeichenfolge zu zählen. Ändert den Compiler, um das Muster len([]rune(string)).__ zu erkennen. und ersetzt es durch die neue Laufzeitfunktion der Runenzählung.

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)

Stefan Steiger verweist auf den Blogbeitrag " Textnormalisierung in Go "

Was ist ein Charakter?

Wie bereits im strings-Blogbeitrag erwähnt, können Zeichen mehrere Runen umfassen.
Beispielsweise können 'e' und '◌́◌́' (akut "\ u0301") zu "é" ("e\u0301" in NFD) zusammengefasst werden. Zusammen bilden diese beiden Runen ein Zeichen

Die Definition eines Zeichens kann je nach Anwendung variieren.
Für Normalisierung definieren wir es als:

  • eine Runenfolge, die mit einem Starter beginnt, 
  • eine Rune, die nicht mit einer anderen Rune rückwärts modifiziert oder kombiniert wird, 
  • gefolgt von einer möglicherweise leeren Sequenz von Nichtstartern, d. h. Runen, die dies tun (normalerweise Akzente). 

Der Normalisierungsalgorithmus verarbeitet jeweils ein Zeichen. 

Unter Verwendung dieses Pakets und seines Iter-Typs würde die tatsächliche Anzahl von "Zeichen" lauten:

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)
}

Hier wird die Unicode-Normalisierungsform verwendet NFKD "Compatibility Decomposition"

128
VonC

Es gibt eine Möglichkeit, Runen ohne Pakete abzurufen, indem Sie string in [] rune als len([]rune(YOUR_STRING)) konvertieren:

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)))

}

anzahl der Bytes 30 16

anzahl der Runen 16 16

32

Hängt viel von Ihrer Definition ab, was ein "Charakter" ist. Wenn "Rune gleich einem Charakter" für Ihre Aufgabe in Ordnung ist (im Allgemeinen nicht), ist die Antwort von VonC perfekt für Sie. Ansonsten ist wahrscheinlich anzumerken, dass es wenige Situationen gibt, in denen die Anzahl der Runen in einer Unicode-Zeichenfolge ein interessanter Wert ist. Und selbst in diesen Situationen ist es besser, wenn möglich, die Anzahl zu ermitteln, während der String "durchquert" wird, während die Runen verarbeitet werden, um eine Verdoppelung der UTF-8-Dekodierleistung zu vermeiden.

5
zzzz

Wenn Sie Graphem-Cluster berücksichtigen müssen, verwenden Sie das Regex- oder Unicode-Modul. Das Zählen der Anzahl der Codepunkte (Runen) oder Bytes ist auch für die Validierung erforderlich, da die Länge des Graphemclusters unbegrenzt ist. Wenn Sie extrem lange Sequenzen entfernen möchten, prüfen Sie, ob die Sequenzen dem Stream-sicheren Textformat entsprechen.

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) 
}
4
masakielastic

Ich möchte darauf hinweisen, dass keine der bisher gegebenen Antworten die erwartete Anzahl von Zeichen enthält, insbesondere wenn es sich um Emojis handelt (aber auch einige Sprachen wie Thai, Koreanisch oder Arabisch). VonCs Vorschläge geben Folgendes aus:

fmt.Println(utf8.RuneCountInString("????️‍????????????")) // Outputs "6".
fmt.Println(len([]rune("????️‍????????????"))) // Outputs "6".

Das liegt daran, dass diese Methoden nur Unicode-Codepunkte zählen. Es gibt viele Zeichen, die aus mehreren Codepunkten bestehen können.

Gleiches gilt für das Normalisierungspaket :

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

Normalisierung ist nicht dasselbe wie das Zählen von Zeichen, und viele Zeichen können nicht zu einem Ein-Code-Punkt-Äquivalent normalisiert werden.

masakielastics Antwort kommt nahe, behandelt aber nur Modifikatoren (das Rainbow-Flag enthält einen Modifikator, der daher nicht als eigener Codepunkt gezählt wird):

fmt.Println(GraphemeCountInString("????️‍????????????"))  // Outputs "5".
fmt.Println(GraphemeCountInString2("????️‍????????????")) // Outputs "5".

Die korrekte Aufteilung von Unicode-Zeichenfolgen in (vom Benutzer wahrgenommene) Zeichen, d. H. Graphem-Cluster, ist im Unicode Standard Annex # 29 definiert. Die Regeln finden Sie in Abschnitt 3.1.1 . Das Paket github.com/rivo/uniseg implementiert diese Regeln, damit Sie die richtige Anzahl von Zeichen in einer Zeichenfolge bestimmen können:

fmt.Println(uniseg.GraphemeClusterCount("????️‍????????????")) // Outputs "2".
3
Oliver

Es gibt verschiedene Möglichkeiten, eine Zeichenfolgenlänge zu ermitteln:

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)

}

1
pigletfly

Ich habe versucht, die Normalisierung etwas schneller zu machen:

    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
    }
0
Marcelloh