¿Cómo obtener el número de caracteres en una cadena?

145

¿Cómo puedo obtener el número de caracteres de una cadena en Go?

Por ejemplo, si tengo una cadena, "hello"el método debería volver 5. Vi que len(str)devuelve el número de bytes y no el número de caracteres, por lo que len("£")devuelve 2 en lugar de 1 porque £ está codificado con dos bytes en UTF-8.

Ammar
fuente
2
Sí devuelve 5 . Tal vez no lo hace cuando la codificación del archivo es UTF-8.
Moshe Revah
77
Sí, para este caso, pero quiero que sea general para otros caracteres UTF-8 como el árabe, que no se traduce a 1 byte.
Ammar

Respuestas:

177

Puedes probar RuneCountInStringdesde el paquete utf8.

devuelve el número de runas en p

que, como se ilustra en este script : la longitud de "Mundo" podría ser 6 (cuando se escribe en chino: "世界"), pero su número de runas es 2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen agrega en los comentarios :

En realidad, puedes len()superar las runas con solo escribir casting.
len([]rune("世界"))imprimirá 2. Al menos en Go 1.3.


Y con CL 108985 (mayo de 2018, para Go 1.11), len([]rune(string))ahora está optimizado. ( Soluciona el problema 24923 )

El compilador detecta el len([]rune(string))patrón automáticamente y lo reemplaza por r: = llamada de rango.

Agrega una nueva función de tiempo de ejecución para contar runas en una cadena. Modifica el compilador para detectar el patrón len([]rune(string)) y lo reemplaza con la nueva función de tiempo de ejecución de conteo de runas.

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 señala la publicación del blog " Normalización de texto en Go "

¿Qué es un personaje?

Como se mencionó en la publicación del blog de cadenas , los personajes pueden abarcar múltiples runas .
Por ejemplo, un ' e' y '◌́◌́' (agudo "\ u0301") pueden combinarse para formar 'é' (" e\u0301" en NFD). Juntas, estas dos runas son un solo personaje .

La definición de un personaje puede variar según la aplicación.
Para la normalización lo definiremos como:

  • una secuencia de runas que comienza con un iniciador,
  • una runa que no se modifica ni combina hacia atrás con ninguna otra runa,
  • seguido de una secuencia posiblemente vacía de no iniciadores, es decir, runas que lo hacen (típicamente acentos).

El algoritmo de normalización procesa un carácter a la vez.

Usando ese paquete y su Itertipo , el número real de "carácter" sería:

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

Aquí, esto usa el formulario de normalización Unicode NFKD "Descomposición de compatibilidad"


La respuesta de Oliver apunta a la SEGMENTACIÓN DE TEXTO UNICODE como la única forma de determinar de manera confiable los límites predeterminados entre ciertos elementos de texto significativos: caracteres percibidos por el usuario, palabras y oraciones.

Para eso, necesita una biblioteca externa como rivo / uniseg , que realiza la segmentación de texto Unicode .

Eso contará realmente " grupo de grafemas ", donde se pueden combinar múltiples puntos de código en un carácter percibido por el usuario.

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

Dos grafemas, aunque hay tres runas (puntos de código Unicode).

Puede ver otros ejemplos en " ¿Cómo manipular cadenas en GO para revertirlas? "

👩🏾‍🦰 solo es un grafema, pero, de unicode a convertidor de puntos de código , 4 runas:

VonC
fuente
44
Puede verlo en acción en esta función de reversión de cadenas en stackoverflow.com/a/1758098/6309
VonC
55
Esto solo te dice la cantidad de runas, no la cantidad de glifos. Muchos glifos están hechos de múltiples runas.
Stephen Weinberg
55
En realidad, puedes hacer len () sobre runas simplemente escribiendo casting ... len ([] rune ("世界")) imprimirá 2. En leats en Go 1.3, no sé cuánto tiempo ha pasado.
Phrozen
3
@VonC: En realidad, un personaje (término de lenguaje coloquial para Glifo) puede, ocasionalmente, abarcar varias runas, por lo que esta respuesta es, para usar el término técnico preciso, INCORRECTO. Lo que necesita es el recuento de Grapheme / GraphemeCluster, no el recuento de runas. Por ejemplo, una 'e' y '◌́' (aguda "\ u0301") pueden combinarse para formar 'é' ("e \ u0301" en NFD). Pero un humano consideraría (correctamente) y eacute; como UN personaje .. Aparentemente hace la diferencia en telugu. Pero probablemente también en francés, dependiendo del teclado / configuración regional que use. blog.golang.org/normalization
Stefan Steiger
1
@JustinJohnson De acuerdo. He editado la respuesta para hacer una mejor referencia a la de Oliver, que anteriormente había votado.
VonC
43

Hay una manera de obtener el recuento de runas sin ningún paquete convirtiendo la cadena en [] runas como 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)))

}

recuento de bytes 30 16

conteo de runas 16 16

Denis Kreshikhin
fuente
5

Depende mucho de tu definición de lo que es un "personaje". Si "runa es igual a un personaje" está bien para su tarea (generalmente no lo es), entonces la respuesta de VonC es perfecta para usted. De lo contrario, probablemente debería notarse que hay pocas situaciones en las que el número de runas en una cadena Unicode es un valor interesante. E incluso en esas situaciones es mejor, si es posible, inferir el recuento mientras "atraviesa" la cadena a medida que se procesan las runas para evitar duplicar el esfuerzo de decodificación UTF-8.

zzzz
fuente
¿Cuándo no verías una runa como personaje? La especificación Go define una runa como un punto de código Unicode: golang.org/ref/spec#Rune_literals .
Thomas Kappler
Además, para evitar duplicar el esfuerzo de decodificación, solo hago una [] runa (str), trabajo en eso, luego vuelvo a convertir en cadena cuando termine. Creo que es más fácil que realizar un seguimiento de los puntos de código al atravesar una cadena.
Thomas Kappler
44
@ThomasKappler: ¿Cuándo? Bueno, cuando la runa no es un personaje, que generalmente no lo es. Solo algunas runas son iguales a los personajes, no todos. Suponiendo que "rune == character" es válido solo para un subconjunto de caracteres Unicode. Ejemplo: en.wikipedia.org/wiki/…
zzzz
@ThomasKappler: pero si lo miras de esa manera, entonces, por ejemplo, Stringel .length()método de Java tampoco devuelve el número de caracteres. Tampoco lo hace Cacao de NSString's -lengthmétodo. Esos simplemente devuelven el número de entidades UTF-16. Pero el número verdadero de puntos de código rara vez se usa, porque se necesita tiempo lineal para contarlo.
newacct
5

Si necesita tener en cuenta los grupos de grafemas, use regexp o unicode module. También es necesario contar la cantidad de puntos de código (runas) o bytes para la validación, ya que la longitud del grupo de grafemas es ilimitada. Si desea eliminar secuencias extremadamente largas, verifique si las secuencias se ajustan al formato de texto seguro para la transmisión .

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
fuente
Gracias por esto. Probé su código y no funciona para algunos grafemas de emoji como estos: 🖖🏿🇸🇴. ¿Alguna idea sobre cómo contarlos con precisión?
Bjorn Roche
La expresión regular compilada se debe extraer como varfuera de las funciones.
dolmen
5

Hay varias formas de obtener una longitud de cadena:

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)

}

pigletfly
fuente
3

Debo señalar que ninguna de las respuestas proporcionadas hasta el momento le da la cantidad de caracteres que esperaría, especialmente cuando se trata de emojis (pero también algunos idiomas como tailandés, coreano o árabe). Las sugerencias de VonC generarán lo siguiente:

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

Esto se debe a que estos métodos solo cuentan puntos de código Unicode. Hay muchos caracteres que pueden estar compuestos de múltiples puntos de código.

Lo mismo para usar el paquete de Normalización :

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

La normalización no es realmente lo mismo que contar caracteres y muchos caracteres no se pueden normalizar en un equivalente de un punto de código.

La respuesta de masakielastic se acerca pero solo maneja modificadores (la bandera del arco iris contiene un modificador que por lo tanto no se cuenta como su propio punto de código):

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

La forma correcta de dividir las cadenas Unicode en caracteres (percibidos por el usuario), es decir, grupos de grafemas, se define en el Anexo estándar 29 de Unicode . Las reglas se pueden encontrar en la Sección 3.1.1 . El paquete github.com/rivo/uniseg implementa estas reglas para que pueda determinar el número correcto de caracteres en una cadena:

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

Traté de hacer la normalización un poco más rápido:

    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
fuente