Frage

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

Zum Beispiel, wenn ich eine Zeichenfolge habe "hello" Die Methode sollte zurückkehren 5.das habe ich gesehen len(str) gibt die Anzahl der Bytes zurück und nicht die Anzahl der Zeichen also len("£") gibt 2 statt 1 zurück, da £ in UTF-8 mit zwei Bytes codiert ist.

War es hilfreich?

Lösung

Du kannst es versuchen RuneCountInString aus dem utf8-Paket.

gibt die Anzahl der Runen in p zurück

das, wie in dargestellt dieses Skript:die Länge von „World“ könnte 6 betragen (auf Chinesisch geschrieben:„世界“), aber seine Runenzahl beträgt 2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen fügt hinzu in den Kommentaren:

Eigentlich kannst du es tun len() über Runen durch einfaches Typ-Casting.
len([]rune("世界")) wird gedruckt 2.Zumindest in Go 1.3.


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

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

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

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 im erwähnt wurde Streicher-Blogbeitrag, Zeichen können mehrere Runen umfassen.
Zum Beispiel ein 'e' und '◌́◌́' (akut „\u0301“) können zu „é“ kombiniert werden („e\u0301" in NFD). Zusammen bilden diese beiden Runen ein Zeichen.

Die Definition eines Zeichens kann je nach Anwendung variieren.
Für Normalisierung Wir werden es wie folgt definieren:

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

Der Normalisierungsalgorithmus verarbeitet jeweils ein Zeichen.

Verwenden dieses Pakets und seiner Iter Typ, die tatsächliche Anzahl der „Zeichen“ wäre:

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 verwendet Unicode-Normalisierungsformular NFKD „Kompatibilitätszerlegung“


Oliver'S Antwort verweist auf UNICODE-TEXTSEGMENTIERUNG als einzige Möglichkeit, Standardgrenzen zwischen bestimmten wichtigen Textelementen zuverlässig zu bestimmen:Vom Benutzer wahrgenommene Zeichen, Wörter und Sätze.

Dafür benötigen Sie eine externe Bibliothek wie rivo/uniseg, was der Fall ist Unicode-Textsegmentierung.

Das wird tatsächlich zählen“Graphem Cluster", wobei mehrere Codepunkte zu einem vom Benutzer wahrgenommenen Zeichen kombiniert werden können.

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

Zwei Grapheme, obwohl es drei Runen gibt (Unicode-Codepunkte).

Andere Tipps

Es gibt eine Möglichkeit, die Anzahl der Runen ohne Pakete zu ermitteln, indem man einen String in []rune as konvertiert 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)))

}

Anzahl der Bytes 30 16

Anzahl der Runen 16 16

Hängt stark von Ihrer Definition ab, was ein „Charakter“ ist.Wenn „Rune entspricht einem Zeichen“ für Ihre Aufgabe in Ordnung ist (im Allgemeinen nicht), dann ist die Antwort von VonC perfekt für Sie.Ansonsten sollte wohl beachtet werden, dass es nur wenige Situationen gibt, in denen die Anzahl der Runen in einer Unicode-Zeichenfolge ein interessanter Wert ist.Und selbst in solchen Situationen ist es, wenn möglich, besser, die Anzahl abzuleiten, während die Zeichenfolge bei der Verarbeitung der Runen „durchlaufen“ wird, um eine Verdoppelung des UTF-8-Dekodierungsaufwands zu vermeiden.

Wenn Sie Graphemcluster berücksichtigen müssen, verwenden Sie Regexp oder Unicode-Modul.Zur Validierung ist auch das Zählen der Anzahl der Codepunkte (Runen) oder Bytes erforderlich, da die Länge des Graphemclusters unbegrenzt ist.Wenn Sie extrem lange Sequenzen eliminieren möchten, prüfen Sie, ob die Sequenzen konform sind Stream-sicheres Textformat.

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

Ich möchte darauf hinweisen, dass keine der bisher bereitgestellten Antworten die erwartete Zeichenanzahl liefert, insbesondere wenn es um Emojis geht (aber auch um einige Sprachen wie Thailändisch, Koreanisch oder Arabisch). VonCs Vorschläge wird Folgendes ausgeben:

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.

Das Gleiche gilt für die Verwendung von 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 wirklich dasselbe wie das Zählen von Zeichen und viele Zeichen können nicht in ein Ein-Codepunkt-Äquivalent normalisiert werden.

Masakielastics Antwort kommt dem nahe, verarbeitet aber nur Modifikatoren (die Regenbogenflagge enthält einen Modifikator, der daher nicht als eigener Codepunkt gezählt wird):

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

Der richtige Weg, Unicode-Zeichenfolgen in (vom Benutzer wahrgenommene) Zeichen aufzuteilen, d. h.Graphem-Cluster, ist in der definiert Unicode-Standardanhang Nr. 29.Die Regeln finden Sie in Abschnitt 3.1.1.Der github.com/rivo/uniseg Das Paket implementiert diese Regeln, damit Sie die richtige Anzahl von Zeichen in einer Zeichenfolge bestimmen können:

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

Es gibt mehrere Möglichkeiten, eine Stringlä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)

}

Ich habe versucht, die Normalisierung etwas schneller durchzuführen:

    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
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top