Come ottenere il numero di caratteri in una stringa?
-
12-12-2019 - |
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.
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
}