Domanda

Come posso ottenere il numero di caratteri di una stringa in Go?

Ad esempio, se ho un file string "hello" il metodo dovrebbe restituire 5.l'ho visto len(str) restituisce il numero di byte e non il numero di caratteri così len("£") restituisce 2 invece di 1 perché £ è codificato con due byte in UTF-8.

È stato utile?

Soluzione

Puoi provare RuneCountInString dal pacchetto utf8.

restituisce il numero di rune in p

che, come illustrato in questa sceneggiatura:la lunghezza di "World" potrebbe essere 6 (se scritto in cinese:"世界"), ma il numero delle sue rune è 2:

package main

import "fmt"
import "unicode/utf8"

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

congelato aggiunge nei commenti:

In realtà puoi farlo len() sulle rune digitando semplicemente casting.
len([]rune("世界")) stamperà 2.Almeno in Go 1.3.


E con CL108985 (Maggio 2018, per Go 1.11), len([]rune(string)) è ora ottimizzato.(Correzioni numero 24923)

Il compilatore rileva len([]rune(string)) pattern automaticamente e lo sostituisce con la chiamata for r := range s.

Aggiunge una nuova funzione runtime per contare le rune in una stringa.Modifica il compilatore per rilevare il modello len([]rune(string))e lo sostituisce con la nuova funzione runtime di conteggio delle rune.

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 indica il post del blog "Normalizzazione del testo in Go"

Cos'è un personaggio?

Come è stato accennato nel post sul blog sulle stringhe, i personaggi possono estendersi su più rune.
Ad esempio, un 'e' e '◌́◌́' (acuto "\u0301") possono combinarsi per formare 'é' ("e\u0301"nella NFD). Insieme, queste due rune formano un carattere.

La definizione di un carattere può variare a seconda dell'applicazione.
Per normalizzazione lo definiremo come:

  • una sequenza di rune che inizia con un antipasto,
  • una runa che non si modifica o si combina all'indietro con qualsiasi altra runa,
  • seguito da una sequenza possibilmente vuota di rune non iniziali, cioè rune che lo fanno (tipicamente accenti).

L'algoritmo di normalizzazione elabora un carattere alla volta.

Usando quel pacchetto e il suo Iter tipo, il numero effettivo di "carattere" sarebbe:

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

Ecco, questo usa il Modulo di normalizzazione Unicode NFKD "Decomposizione della compatibilità"


Oliver'S risposta punta a SEGMENTAZIONE DEL TESTO UNICODE come unico modo per determinare in modo affidabile i confini predefiniti tra determinati elementi di testo significativi:caratteri, parole e frasi percepiti dall'utente.

Per questo, hai bisogno di una libreria esterna come rivo/uniseg, il che fa Segmentazione del testo Unicode.

Conterà davvero"grafema grappolo", dove più punti di codice possono essere combinati in un unico carattere percepito dall'utente.

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

Due grafemi, anche se ci sono tre rune (punti di codice Unicode).

Altri suggerimenti

C'è un modo per ottenere il conteggio delle rune senza pacchetti convertendo la stringa in []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)))

}

conteggio dei byte 30 16

conteggio delle rune 16 16

Dipende molto dalla tua definizione di "personaggio".Se "la runa equivale a un carattere" va bene per il tuo compito (generalmente non lo è), allora la risposta di VonC è perfetta per te.Altrimenti, probabilmente andrebbe notato che ci sono poche situazioni in cui il numero di rune in una stringa Unicode è un valore interessante.E anche in quelle situazioni è meglio, se possibile, dedurre il conteggio mentre "attraversa" la stringa mentre le rune vengono elaborate per evitare di raddoppiare lo sforzo di decodifica UTF-8.

Se è necessario prendere in considerazione i cluster di grafemi, utilizzare il modulo regexp o unicode.Per la convalida è necessario anche il conteggio del numero di punti di codice (rune) o byte poiché la lunghezza del cluster di grafemi è illimitata.Se vuoi eliminare sequenze estremamente lunghe, controlla se le sequenze sono conformi a formato di testo sicuro per lo streaming.

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

Dovrei sottolineare che nessuna delle risposte fornite finora ti dà il numero di caratteri che ti aspetteresti, soprattutto quando hai a che fare con gli emoji (ma anche con alcune lingue come il tailandese, il coreano o l'arabo). I suggerimenti di VonC restituirà quanto segue:

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

Questo perché questi metodi contano solo i punti di codice Unicode.Esistono molti caratteri che possono essere composti da più punti di codice.

Lo stesso per l'utilizzo di Pacchetto di normalizzazione:

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

La normalizzazione non è esattamente la stessa cosa del conteggio dei caratteri e molti caratteri non possono essere normalizzati in un equivalente di un punto di codice.

la risposta di masakielastic si avvicina ma gestisce solo i modificatori (la bandiera arcobaleno contiene un modificatore che quindi non viene conteggiato come proprio punto di codice):

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

Il modo corretto per dividere le stringhe Unicode in caratteri (percepiti dall'utente), ad es.cluster di grafemi, è definito nel Allegato n. 29 dello standard Unicode.Le regole le potete trovare in Sezione 3.1.1.IL github.com/rivo/uniseg Il pacchetto implementa queste regole in modo da poter determinare il numero corretto di caratteri in una stringa:

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

Esistono diversi modi per ottenere la lunghezza di una stringa:

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)

}

Ho provato a rendere la normalizzazione un po' più veloce:

    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
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top