Comment obtenir le nombre de caractères dans une chaîne?

145

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 doit retourner 5. J'ai vu que cela len(str)renvoie le nombre d'octets et non le nombre de caractères, donc len("£")renvoie 2 au lieu de 1 car £ est codé avec deux octets en UTF-8.

Ammar
la source
2
Il renvoie 5 . Peut-être que ce n'est pas le cas lorsque le codage du fichier est UTF-8.
Moshe Revah
7
Oui, c'est le cas pour ce cas, mais je veux le rendre général pour d'autres caractères UTF-8 comme l'arabe, qui ne se traduit pas par 1 octet.
Ammar

Réponses:

177

Vous pouvez essayer à RuneCountInStringpartir du package utf8.

renvoie le nombre de runes en p

que, comme illustré dans ce script : la longueur de "World" 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, vous pouvez faire len()plus de runes en tapant simplement.
len([]rune("世界"))imprimera 2. Aux sauts dans Go 1.3.


Et avec CL 108985 (mai 2018, pour Go 1.11), len([]rune(string))est maintenant optimisé. ( Résout le problème 24923 )

Le compilateur détecte len([]rune(string))automatiquement le motif 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 en Go "

Qu'est-ce qu'un personnage?

Comme mentionné dans le billet de blog sur les chaînes , les personnages peuvent s'étendre sur plusieurs runes .
Par exemple, un ' e' et un '◌́◌́' (aigu "\ u0301") peuvent se combiner pour former 'é' (" e\u0301" dans NFD). Ensemble, ces deux runes forment un seul personnage .

La définition d'un caractère peut varier en fonction de l'application.
Pour la normalisation, nous le définirons comme:

  • une séquence de runes qui commence par un démarreur,
  • une rune qui ne modifie ni ne se combine à l'envers avec aucune autre rune,
  • suivi d'une séquence éventuellement vide de non-démarreurs, 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 type , le nombre réel de "caractère" 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é"


Oliver de » réponse des points à UNICODE TEXTE SEGMENTATION que la seule façon de déterminer de manière fiable les limites par défaut entre certains éléments de texte significatifs: caractères perçus par l' utilisateur, des mots et des phrases.

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

Cela comptera en fait " grappe de graphèmes ", 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).

Vous pouvez voir d'autres exemples dans " Comment manipuler des chaînes dans GO pour les inverser? "

👩🏾‍🦰 seul est un graphème, mais, du convertisseur unicode en points de code , 4 runes:

VonC
la source
4
Vous pouvez le voir en action dans cette fonction de réversion de chaîne sur stackoverflow.com/a/1758098/6309
VonC
5
Cela vous indique uniquement le nombre de runes, pas le nombre de glyphes. De nombreux glyphes sont constitués de plusieurs runes.
Stephen Weinberg
5
En fait, vous pouvez faire len () sur les runes en tapant simplement casting ... len ([] rune ("世界")) imprimera 2. À des sauts de Go 1.3, je ne sais pas depuis combien de temps.
Phrozen
3
@VonC: En fait, un caractère (terme de langage familier pour Glyph) peut - occasionnellement - s'étendre sur plusieurs runes, donc cette réponse est, pour utiliser le terme technique précis, FAUX. Ce dont vous avez besoin est le nombre de Grapheme / GraphemeCluster, pas le nombre de runes. Par exemple, un 'e' et '◌́' (aigu "\ u0301") peuvent se combiner pour former "é" ("e \ u0301" dans NFD). Mais un humain considérerait (correctement) & eacute; comme UN personnage .. Apparemment, cela fait une différence en Telugu. Mais probablement aussi le français, selon le clavier / les paramètres régionaux que vous utilisez. blog.golang.org/normalization
Stefan Steiger
1
@JustinJohnson D'accord. J'ai édité la réponse pour mieux faire référence à Oliver, que j'avais précédemment voté pour.
VonC
43

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

Denis Kreshikhin
la source
5

Cela dépend beaucoup de votre définition de ce qu'est un «personnage». Si "rune est égal à un personnage" est OK pour votre tâche (ce n'est généralement pas le cas) 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, si possible, de déduire le décompte tout en "parcourant" la chaîne pendant que les runes sont traitées pour éviter de doubler l'effort de décodage UTF-8.

zzzz
la source
Quand ne verriez-vous pas une rune en tant que personnage? La spécification Go définit une rune comme un point de code Unicode: golang.org/ref/spec#Rune_literals .
Thomas Kappler
Aussi, pour éviter de doubler l'effort de décodage, je fais juste une [] rune (str), je travaille dessus, puis je la reconvertis en chaîne quand j'ai terminé. Je pense que c'est plus facile que de garder une trace des points de code lors de la traversée d'une chaîne.
Thomas Kappler
4
@ThomasKappler: Quand? Eh bien, quand la rune n'est pas un personnage, ce n'est généralement pas le cas. Seules certaines runes sont égales à des personnages, pas toutes. En supposant que "rune == character" n'est valide que pour un sous-ensemble de caractères Unicode. Exemple: en.wikipedia.org/wiki/…
zzzz
@ThomasKappler: mais si vous regardez les choses de cette façon, alors par exemple, Stringla .length()méthode de Java ne renvoie pas non plus le nombre de caractères. Ni ne de cacao NSStringde -lengthméthode. Ceux-ci renvoient simplement le nombre d'entités UTF-16. Mais le nombre réel de points de code est rarement utilisé, car il faut un temps linéaire pour le compter.
newacct
5

Si vous devez prendre en compte les clusters de graphèmes, utilisez le module regexp ou unicode. Le comptage du nombre de points de code (runes) ou d'octets est également nécessaire pour la validation car la longueur du cluster de graphèmes est illimitée. Si vous souhaitez éliminer les séquences extrêmement longues, vérifiez si les séquences sont conformes au format de texte sécurisé par flux .

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) 
}
masakielastic
la source
Merci pour cela. J'ai essayé votre code et cela ne fonctionne pas pour quelques graphèmes emoji comme ceux-ci: 🖖🏿🇸🇴. Des réflexions sur la façon de les compter avec précision?
Bjorn Roche
L'expression régulière compilée doit être extraite comme en vardehors des fonctions.
dolmen
5

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)

}
porcelet
la source
3

Je dois souligner qu'aucune des réponses fournies jusqu'à présent ne vous donne le nombre de caractères que vous attendez, en particulier lorsque vous avez affaire à des emojis (mais aussi à certaines langues comme le thaï, le coréen ou l'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 les points de code Unicode. Il existe de nombreux caractères qui peuvent être composés de plusieurs points de code.

Idem pour l'utilisation du package 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 chose que le comptage des caractères et de nombreux caractères ne peuvent pas être normalisés en un équivalent à un point de code.

La réponse de masakielastic se rapproche mais ne gère que les modificateurs (le drapeau arc-en-ciel contient un modificateur qui n'est donc pas compté comme son propre point de code):

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

La manière correcte de diviser les chaînes Unicode en caractères (perçus par l'utilisateur), c'est-à-dire en grappes de graphèmes, est définie dans l' Annexe 29 de la norme Unicode . Les règles se trouvent dans la section 3.1.1 . Le package github.com/rivo/uniseg 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".
Oliver
la source
0

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

    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
la source