Question

Comment puis-je obtenir le nombre de caractères d’une chaîne dans Go ?

Par exemple, si j'ai une chaîne "hello" la méthode devrait revenir 5.j'ai vu ça len(str) renvoie le nombre d'octets et pas le nombre de caractères donc len("£") renvoie 2 au lieu de 1 car £ est codé avec deux octets en UTF-8.

Était-ce utile?

La solution

Tu peux essayer RuneCountInString du paquet utf8.

renvoie le nombre de runes dans p

que, comme l'illustre ce scénario:la longueur de « Monde » peut être de 6 (lorsqu'il est écrit en chinois :"世界"), mais son nombre de runes est de 2 :

package main

import "fmt"
import "unicode/utf8"

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

Phrozen ajoute dans les commentaires:

En fait, tu peux le faire len() sur les runes en tapant simplement le casting.
len([]rune("世界")) imprimera 2.Au moins dans Go 1.3.


Et avec CL108985 (mai 2018, pour Go 1.11), len([]rune(string)) est désormais optimisé.(Corrections numéro 24923)

Le compilateur détecte len([]rune(string)) automatiquement et le remplace par l'appel de for r := range .

Ajoute une nouvelle fonction d'exécution pour compter les runes dans une chaîne.Modifie le compilateur pour détecter le modèle len([]rune(string))et le remplace par la nouvelle fonction d'exécution de comptage de runes.

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 pointe vers le billet de blog "Normalisation du texte dans Go"

Qu'est-ce qu'un personnage ?

Comme cela a été mentionné dans le article de blog sur les cordes, les personnages peuvent s'étendre sur plusieurs runes.
Par exemple, un 'e' et '◌́◌́' (aigu "\u0301") peuvent se combiner pour former 'é' ("e\u0301" dans NFD). Ensemble, ces deux runes ne forment qu'un seul personnage.

La définition d'un caractère peut varier selon l'application.
Pour normalisation nous le définirons comme :

  • une séquence de runes qui commence par un démarreur,
  • une rune qui ne se modifie ni ne se combine à l'envers avec aucune autre rune,
  • suivie d'une séquence éventuellement vide de non-départs, c'est-à-dire de runes qui le font (généralement des accents).

L'algorithme de normalisation traite un caractère à la fois.

En utilisant ce package et son Iter taper, le nombre réel de « caractères » serait :

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

Ici, cela utilise le Formulaire de normalisation Unicode NFKD "Décomposition de compatibilité"


Olivierc'est répondre pointe vers SEGMENTATION DE TEXTE UNICODE comme seul moyen de déterminer de manière fiable les limites par défaut entre certains éléments de texte importants :caractères, mots et phrases perçus par l'utilisateur.

Pour cela, vous avez besoin d'une bibliothèque externe comme rivo/uniseg, qui fait Segmentation de texte Unicode.

Cela comptera effectivement "graphème grappe", où plusieurs points de code peuvent être combinés en un seul caractère perçu par l'utilisateur.

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

Deux graphèmes, même s'il y a trois runes (points de code Unicode).

Autres conseils

Il existe un moyen d'obtenir le nombre de runes sans aucun paquet en convertissant la chaîne en []rune comme 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)))

}

nombre d'octets 30 16

nombre de runes 16 16

dépend beaucoup de votre définition de ce qu'est un "caractère".Si "Rune est égal à un caractère", c'est d'accord pour votre tâche (ce n'est généralement pas) alors la réponse de VONC est parfaite pour vous.Sinon, il convient probablement de noter qu'il y a peu de situations où le nombre de runes dans une chaîne Unicode est une valeur intéressante.Et même dans ces situations, il est préférable que possible, si possible, de déduire le compte tout en "traverser" la chaîne alors que les runes sont traitées pour éviter de doubler l'effort de décodage UTF-8.

Si vous devez prendre en compte les clusters graphèmes, utilisez le module REGEXP ou Unicode.Compter le nombre de points de code (runes) ou d'octets est également nécessaire pour Validaiton car la longueur du graphème est illimitée.Si vous souhaitez éliminer des séquences extrêmement longues, vérifiez si les séquences sont conformes à Format de texte Stream-Safe .

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

Je dois souligner qu'aucune des réponses fournies jusqu'à présent vous donner le nombre de caractères que vous attendez, en particulier lorsque vous avez affaire à Emojis (mais aussi des langues comme thaï, coréenne ou arabe). Les suggestions de VONC produiront les éléments suivants:

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

C'est parce que ces méthodes ne comptent que des points de code Unicode. Il existe de nombreux personnages pouvant être composés de plusieurs points de code.

même pour utiliser le Paquet de normalisation :

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

La normalisation n'est pas vraiment la même que celle des caractères de comptage et de nombreux caractères ne peuvent pas être normalisés dans un équivalent à point unique.

La réponse de Masakielastic s'approche de la fermeture mais ne gère que des modificateurs (le drapeau Rainbow contient un modificateur qui n'est donc pas compté comme sa propre Point de code):

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

La bonne façon de diviser les chaînes Unicode en caractères (perçus par l'utilisateur), c'est-à-dire que les grappes graphémères sont définies dans le Annexe Standard Unicode # 29 . Les règles peuvent être trouvées dans section 3.1.1 . Le Github.com/rivo/uniseg Package implémente ces règles afin que vous puissiez déterminer le nombre correct de caractères dans une chaîne:

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

Il existe plusieurs façons d'obtenir une longueur de chaîne:

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)

}

J'ai essayé de faire la normalisation un peu plus rapidement:

    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
    }

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top