Wie erhalte ich die Anzahl der Zeichen in einem String?

145

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

Wenn ich beispielsweise eine Zeichenfolge habe "hello", sollte die Methode zurückgegeben werden 5. Ich habe gesehen, dass len(str)die Anzahl der Bytes und nicht die Anzahl der Zeichen zurückgegeben wird. len("£")Daher wird 2 anstelle von 1 zurückgegeben, da £ in UTF-8 mit zwei Bytes codiert ist.

Ammar
quelle
2
Es gibt 5 zurück . Möglicherweise nicht, wenn die Dateicodierung UTF-8 ist.
Moshe Revah
7
Ja, dies ist in diesem Fall der Fall, aber ich möchte es allgemein für andere UTF-8-Zeichen wie Arabisch machen, die nicht in 1 Byte übersetzt werden.
Ammar

Antworten:

177

Sie können es RuneCountInStringaus dem utf8-Paket versuchen .

gibt die Anzahl der Runen in p zurück

das, wie in diesem Skript dargestellt : Die Länge von "Welt" könnte 6 sein (wenn auf Chinesisch geschrieben: "世界"), aber seine Runenzahl ist 2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen fügt in den Kommentaren hinzu :

Eigentlich kannst du len()Runen übergehen, indem du einfach Casting tippst.
len([]rune("世界"))wird gedruckt 2. Bei Leats in Go 1.3.


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

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

Fügt eine neue Laufzeitfunktion hinzu, um Runen in einer Zeichenfolge zu zählen. Ändert den Compiler, um das Muster zu erkennen, len([]rune(string)) und ersetzt es durch die neue Laufzeitfunktion zur 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 im Blog-Beitrag zu Strings erwähnt , können Charaktere mehrere Runen umfassen .
Zum Beispiel können ein ' e' und '◌́◌́' (akut "\ u0301") kombiniert werden, um 'é' (" e\u0301" in NFD) zu bilden. Zusammen bilden diese beiden Runen einen Charakter .

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

  • eine Folge von Runen, die mit einem Starter beginnt,
  • eine Rune, die sich mit keiner anderen Rune ändert oder rückwärts kombiniert,
  • gefolgt von einer möglicherweise leeren Folge von Nicht-Startern, dh Runen, die dies tun (normalerweise Akzente).

Der Normalisierungsalgorithmus verarbeitet jeweils ein Zeichen.

Unter Verwendung dieses Pakets und seines IterTyps wäre die tatsächliche Anzahl von "Zeichen":

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 das Unicode-Normalisierungsformular NFKD "Compatibility Decomposition" verwendet.


Oliver ‚s Antwort verweist auf UNICODE TEXT SEGMENTATION als der einzige Weg , um zuverlässig Standard Grenzen zwischen bestimmten wesentlichen Textelemente zu bestimmen: Benutzer wahrgenommenen Zeichen, Wörter und Sätze.

Dafür benötigen Sie eine externe Bibliothek wie rivo / uniseg , die die Unicode-Textsegmentierung durchführt .

Das wird zählen eigentlich „ Graphem - Cluster “, in denen mehrere Codepunkte können in einem benutzer wahrgenommen Charakter kombiniert werden.

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

Weitere Beispiele finden Sie unter " Wie manipuliere ich Zeichenfolgen in GO, um sie umzukehren? "

👩🏾‍🦰 allein ist ein Graphem, aber vom Unicode- zum Codepunktkonverter 4 Runen:

VonC
quelle
4
Sie können es in Aktion in dieser String-Umkehrfunktion unter stackoverflow.com/a/1758098/6309
VonC
5
Hier erfahren Sie nur die Anzahl der Runen, nicht die Anzahl der Glyphen. Viele Glyphen bestehen aus mehreren Runen.
Stephen Weinberg
5
Tatsächlich können Sie len () über Runen ausführen, indem Sie einfach casting eingeben ... len ([] rune ("世界")) gibt 2 aus. Bei Leats in Go 1.3 wissen Sie nicht, wie lange es gedauert hat.
Phrozen
3
@VonC: Tatsächlich kann ein Zeichen (umgangssprachliche Sprachbezeichnung für Glyphe) - gelegentlich - mehrere Runen umfassen, daher lautet diese Antwort, um den genauen Fachbegriff zu verwenden, FALSCH. Was Sie brauchen, ist die Anzahl der Grapheme / GraphemeCluster, nicht die Anzahl der Runen. Zum Beispiel können ein 'e' und '◌́' (akut "\ u0301") kombiniert werden, um 'é' ("e \ u0301" in NFD) zu bilden. Aber ein Mensch würde & korrekt (eacute; als EIN Charakter .. Anscheinend macht es einen Unterschied in Telugu. Aber wahrscheinlich auch Französisch, abhängig von der verwendeten Tastatur / dem verwendeten Gebietsschema. blog.golang.org/normalization
Stefan Steiger
1
@ JustinJohnson Einverstanden. Ich habe die Antwort bearbeitet, um besser auf Olivers zu verweisen, die ich zuvor positiv bewertet habe.
VonC
42

Es gibt eine Möglichkeit, die Anzahl der Runen ohne Pakete zu ermitteln, indem der String in die Rune [] konvertiert wird als 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

Denis Kreshikhin
quelle
5

Hängt sehr 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. Andernfalls sollte wahrscheinlich 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 besser, wenn möglich, die Anzahl abzuleiten, während die Zeichenfolge "durchlaufen" wird, während die Runen verarbeitet werden, um eine Verdoppelung des UTF-8-Dekodierungsaufwands zu vermeiden.

zzzz
quelle
Wann möchten Sie nicht sehen eine Rune als Charakter? Die Go-Spezifikation definiert eine Rune als Unicode-Codepunkt: golang.org/ref/spec#Rune_literals .
Thomas Kappler
Um zu vermeiden, dass sich der Dekodierungsaufwand verdoppelt, mache ich einfach eine [] Rune (str), arbeite daran und konvertiere dann wieder in einen String, wenn ich fertig bin. Ich denke, das ist einfacher, als Codepunkte beim Durchlaufen einer Zeichenfolge zu verfolgen.
Thomas Kappler
4
@ ThomasKappler: Wann? Nun, wenn Rune kein Charakter ist, was es im Allgemeinen nicht ist. Nur einige Runen sind gleich Charakteren, nicht alle. Angenommen, "rune == Zeichen" gilt nur für eine Teilmenge von Unicode-Zeichen. Beispiel: en.wikipedia.org/wiki/…
zzzz
@ThomasKappler: aber wenn man es so aussehen, dann zB Java String‚s .length()Methode entweder die Anzahl der Zeichen nicht zurück. Genauso wenig wie Cocoa NSString‚s - -lengthMethode. Diese geben einfach die Anzahl der UTF-16-Entitäten zurück. Die wahre Anzahl von Codepunkten wird jedoch selten verwendet, da das Zählen linear dauert.
Newacct
5

Wenn Sie Graphemcluster berücksichtigen müssen, verwenden Sie das Regexp- oder Unicode-Modul. Das Zählen der Anzahl von Codepunkten (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, überprü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) 
}
masakielastisch
quelle
Danke dafür. Ich habe Ihren Code ausprobiert und er funktioniert bei einigen Emoji-Graphemen wie diesen nicht: 🖖🏿🇸🇴. Irgendwelche Gedanken darüber, wie man diese genau zählt?
Björn Roche
Der kompilierte reguläre Ausdruck sollte varaußerhalb der Funktionen extrahiert werden.
Dolmen
5

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

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)

}
Ferkelfliege
quelle
3

Ich möchte darauf hinweisen, dass keine der bisher gegebenen Antworten die Anzahl der Zeichen angibt, die Sie erwarten würden, insbesondere wenn Sie mit Emojis zu tun haben (aber auch mit einigen Sprachen wie Thai, Koreanisch oder Arabisch). VonCs Vorschläge geben Folgendes aus:

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

Dies 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 die Verwendung des Normalisierungspakets :

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 Zählen von Zeichen, und viele Zeichen können nicht zu einem Ein-Code-Punkt-Äquivalent normalisiert werden.

Die Antwort von masakielastic kommt nahe, behandelt 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".

Die korrekte Aufteilung von Unicode-Zeichenfolgen in (vom Benutzer wahrgenommene) Zeichen, dh Graphemcluster, ist im Unicode-Standardanhang Nr. 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".
Oliver
quelle
0

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