Come ottenere il numero di caratteri in una stringa?


145

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

Ad esempio, se ho una stringa, "hello"il metodo dovrebbe restituire 5. Ho visto che len(str)restituisce il numero di byte e non il numero di caratteri, quindi len("£")restituisce 2 invece di 1 perché £ è codificato con due byte in UTF-8.


2
Restituisce 5 . Forse no quando la codifica del file è UTF-8.
Moshe Revah,

7
Sì, per questo caso, ma voglio renderlo generale per altri caratteri UTF-8 come l'arabo, che non si traduce in 1 byte.
Ammar,

Risposte:


177

Puoi provare RuneCountInStringdal pacchetto utf8.

restituisce il numero di rune in p

che, come illustrato in questa sceneggiatura : la lunghezza di "Mondo" potrebbe essere 6 (quando scritto in cinese: "世界"), ma il suo conteggio delle rune è 2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen aggiunge nei commenti :

In realtà puoi fare len()rune semplicemente digitando il casting.
len([]rune("世界"))stamperà 2. Alle partite in Go 1.3.


E con CL 108985 (maggio 2018, per Go 1.11), len([]rune(string))è ora ottimizzato. (Risolve il problema 24923 )

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

Aggiunge una nuova funzione di 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 di 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 sottolinea il post sul blog " Normalizzazione del testo in Go "

Cos'è un personaggio?

Come menzionato nel post sul blog delle stringhe , i personaggi possono estendersi su più rune .
Ad esempio, un ' e' e '◌́◌́' (acuto "\ u0301") possono combinarsi per formare 'é' (" e\u0301" in NFD). Insieme, queste due rune sono un personaggio .

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

  • una sequenza di rune che inizia con un antipasto,
  • una runa che non modifica o combina all'indietro con qualsiasi altra runa,
  • seguita da una sequenza forse vuota di non-principianti, cioè rune che fanno (in genere accenti).

L'algoritmo di normalizzazione elabora un carattere alla volta.

Usando quel pacchetto e il suo Itertipo , 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)
}

Qui, utilizza il modulo di normalizzazione Unicode NFKD "Decomposizione compatibilità"


La risposta di Oliver indica la SEGMENTAZIONE DEL TESTO UNICODE come unico modo per determinare in modo affidabile i limiti predefiniti tra alcuni elementi di testo significativi: caratteri, parole e frasi percepiti dall'utente.

Per questo, hai bisogno di una libreria esterna come rivo / uniseg , che esegue la segmentazione del testo Unicode .

Ciò conterà effettivamente " grapheme cluster ", in cui più punti di codice possono essere combinati in un 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).

Puoi vedere altri esempi in " Come manipolare le stringhe in GO per invertirle? "

👩🏾‍🦰 da solo è un grafema, ma, dall'unicode al convertitore di punti di codice , 4 rune:


4
Potete vederlo in azione in questa funzione reversione stringa in stackoverflow.com/a/1758098/6309
VonC

5
Questo ti dice solo il numero di rune, non il numero di glifi. Molti glifi sono fatti di più rune.
Stephen Weinberg,

5
In realtà puoi fare len () sulle rune semplicemente digitando casting ... len ([] rune ("世界")) stamperà 2. Nei passi di Go 1.3, non so quanto tempo sia passato.
Phrozen,

3
@VonC: In realtà, un personaggio (termine del linguaggio colloquiale per glifo) può - occasionalmente - abbracciare diverse rune, quindi questa risposta è, per usare il termine tecnico preciso, SBAGLIATO. Ciò di cui hai bisogno è il conteggio Grapheme / GraphemeCluster, non il conteggio delle rune. Ad esempio, 'e' e '◌́' (acuto "\ u0301") possono combinarsi per formare 'é' ("e \ u0301" in NFD). Ma un essere umano (correttamente) considererebbe & eacute; come UN personaggio ... Apparentemente fa la differenza in Telugu. Ma probabilmente anche francese, a seconda della tastiera / locale che usi. blog.golang.org/normalization
Stefan Steiger,

1
@JustinJohnson Concordato. Ho modificato la risposta per fare meglio riferimento a Oliver, che ho precedentemente votato.
VonC,

43

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


5

Dipende molto dalla tua definizione di cosa sia un "personaggio". Se "runa è uguale a un personaggio" è OK per il tuo compito (generalmente non lo è), la risposta di VonC è perfetta per te. Altrimenti, si dovrebbe probabilmente notare 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, inferire il conteggio mentre "attraversiamo" la stringa mentre le rune vengono elaborate per evitare di raddoppiare lo sforzo di decodifica UTF-8.


Quando non vedresti una runa come personaggio? La specifica Go definisce una runa come punto di codice Unicode: golang.org/ref/spec#Rune_literals .
Thomas Kappler,

Inoltre, per evitare di raddoppiare lo sforzo di decodifica, faccio semplicemente una [] runa (str), ci lavoro sopra, quindi riconvertito in stringa quando ho finito. Penso che sia più facile che tenere traccia dei punti di codice quando si attraversa una stringa.
Thomas Kappler,

4
@ThomasKappler: Quando? Bene, quando la runa non è un personaggio, cosa che generalmente non lo è. Solo alcune rune sono uguali ai personaggi, non tutte. Supponendo che "rune == carattere" sia valido solo per un sottoinsieme di caratteri Unicode. Esempio: en.wikipedia.org/wiki/…
zzzz

@ThomasKappler: ma se si guarda in quel modo, quindi, per esempio di Java String's .length()metodo non restituisce il numero di caratteri sia. Nemmeno cacao di NSString's -lengthmetodo. Quelli semplicemente restituiscono il numero di entità UTF-16. Ma il vero numero di punti di codice viene usato raramente, perché ci vuole tempo lineare per contarlo.
newacct

5

Se devi prendere in considerazione i cluster grapheme, usa regexp o il modulo unicode. Il conteggio del numero di punti di codice (rune) o byte è necessario anche per la validit poiché la lunghezza del cluster grapheme è illimitata. Se si desidera eliminare sequenze estremamente lunghe, verificare che le sequenze siano conformi al 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) 
}

Grazie per questo. Ho provato il tuo codice e non funziona con alcuni grafici emoji come questi: 🖖🏿🇸🇴. Qualche idea su come contarli con precisione?
Bjorn Roche,

Il regexp compilato dovrebbe essere estratto come varesterno alle funzioni.
dolmen,

5

Esistono diversi modi per ottenere una lunghezza della 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)

}

3

Devo sottolineare che nessuna delle risposte fornite finora ti dà il numero di caratteri come ti aspetteresti, soprattutto quando hai a che fare con emoji (ma anche alcune lingue come tailandese, coreano o arabo). I suggerimenti di VonC produrranno 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 del 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 è in realtà la stessa 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 non viene quindi conteggiato come proprio punto di codice):

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

Il modo corretto di dividere le stringhe Unicode in caratteri (percepiti dall'utente), cioè i cluster di grafemi, è definito nell'Allegato n . 29 Unicode Standard . Le regole sono disponibili nella Sezione 3.1.1 . Il pacchetto github.com/rivo/uniseg implementa queste regole in modo da poter determinare il numero corretto di caratteri in una stringa:

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

0

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
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.