Como obter o número de caracteres em uma string?

145

Como posso obter o número de caracteres de uma string no Go?

Por exemplo, se eu tiver uma string, "hello"o método retornará 5. Eu vi que len(str)retorna o número de bytes e não o número de caracteres, então len("£")retorna 2 em vez de 1 porque £ é codificado com dois bytes em UTF-8.

Ammar
fonte
2
Retorna 5 . Talvez não o faça quando a codificação do arquivo for UTF-8.
Moshe Revah
7
Sim, neste caso, mas quero torná-lo geral para outros caracteres UTF-8, como o árabe, que não se traduz em 1 byte.
Ammar

Respostas:

177

Você pode tentar RuneCountInStringno pacote utf8.

retorna o número de runas em p

que, conforme ilustrado neste script : o comprimento do "Mundo" pode ser 6 (quando escrito em chinês: "世界"), mas sua contagem de runas é 2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen acrescenta nos comentários :

Na verdade, você pode fazer len()sobre runas apenas digitando casting.
len([]rune("世界"))irá imprimir 2. No pulo no Go 1.3.


E com o CL 108985 (maio de 2018, para Go 1.11), len([]rune(string))agora está otimizado. (Conserta problema 24923 )

O compilador detecta o len([]rune(string))padrão automaticamente e o substitui pela chamada r: = range s.

Adiciona uma nova função de tempo de execução para contar runas em uma sequência. Modifica o compilador para detectar o padrão len([]rune(string)) e o substitui pela nova função de tempo de execução de contagem 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 aponta para a postagem do blog " Normalização de texto no Go "

O que é um personagem?

Como foi mencionado na postagem do blog em strings , os caracteres podem abranger várias runas .
Por exemplo, um ' e' e '◌́◌́' (agudo "\ u0301") podem combinar para formar 'é' (" e\u0301" no NFD). Juntas, essas duas runas são um personagem .

A definição de um personagem pode variar dependendo do aplicativo.
Para normalização , vamos defini-lo como:

  • uma sequência de runas que começa com um iniciador,
  • uma runa que não modifica ou combina com outras runas,
  • seguido por uma sequência possivelmente vazia de não iniciantes, ou seja, runas que fazem (normalmente acentos).

O algoritmo de normalização processa um caractere de cada vez.

Usando esse pacote e seu Itertipo , o número real de "caractere" seria:

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

Aqui, isso usa o formulário de Normalização Unicode NFKD "Decomposição de Compatibilidade"


A resposta de Oliver aponta para SEGMENTAÇÃO DE TEXTO UNICODE como a única maneira de determinar com segurança os limites padrão entre certos elementos significativos do texto: caracteres, palavras e frases percebidos pelo usuário.

Para isso, você precisa de uma biblioteca externa como o rivo / uniseg , que faz a Segmentação de Texto Unicode .

Na verdade, isso contará " cluster grafema ", onde vários pontos de código podem ser combinados em um caractere percebido pelo usuário.

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

Dois grafemas, embora existam três runas (pontos de código Unicode).

Você pode ver outros exemplos em " Como manipular seqüências de caracteres no GO para revertê-las? "

👩🏾‍🦰 sozinho é um grafema, mas, de unicode para conversor de pontos de código , 4 runas:

VonC
fonte
4
Você pode vê-lo em ação nesta função de reversão de string em stackoverflow.com/a/1758098/6309
VonC 1/12
5
Isso informa apenas o número de runas, não o número de glifos. Muitos glifos são feitos de várias runas.
Stephen Weinberg
5
Na verdade, você pode executar len () sobre runas apenas digitando casting ... len ([] rune ("世界")) imprimirá 2. Ao pular no Go 1.3, não sei quanto tempo isso faz.
Phrozen
3
@VonC: Na verdade, um caractere (termo coloquial da linguagem Glyph) pode - ocasionalmente - abranger várias runas; portanto, essa resposta é usar o termo técnico preciso, ERRADO. O que você precisa é a contagem Grapheme / GraphemeCluster, não a contagem de runas. Por exemplo, um 'e' e '◌́' (agudo "\ u0301") podem ser combinados para formar 'é' ("e \ u0301" no NFD). Mas um humano (corretamente) consideraria & eacute; como UM personagem. Aparentemente, faz a diferença em Telugu. Mas provavelmente também em francês, dependendo do teclado / localidade usado. blog.golang.org/normalization
Stefan Steiger
1
@JustinJohnson Concordou. Eu editei a resposta para melhor referenciar o de Oliver, que eu anteriormente votei.
VonC 23/04/19
43

Existe uma maneira de obter a contagem de runas sem nenhum pacote, convertendo string para [] rune 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)))

}

contagem de bytes 30 16

contagem de runas 16 16

Denis Kreshikhin
fonte
5

Depende muito da sua definição do que é um "personagem". Se "runa é igual a um personagem" está OK para a sua tarefa (geralmente não é), então a resposta do VonC é perfeita para você. Caso contrário, deve-se notar provavelmente que existem poucas situações em que o número de runas em uma string Unicode é um valor interessante. E mesmo nessas situações, é melhor, se possível, inferir a contagem enquanto "percorre" a cadeia enquanto as runas são processadas para evitar duplicar o esforço de decodificação UTF-8.

zzzz
fonte
Quando você não vê uma runa como personagem? A especificação Go define uma runa como um ponto de código Unicode: golang.org/ref/spec#Rune_literals .
Thomas Kappler
Além disso, para evitar duplicar o esforço de decodificação, eu apenas faço uma runa [] (str), trabalho nisso e, em seguida, converto novamente em string quando terminar. Eu acho que é mais fácil do que acompanhar os pontos de código ao atravessar uma string.
Thomas Kappler
4
@ThomasKappler: Quando? Bem, quando runa não é um personagem, o que geralmente não é. Apenas algumas runas são iguais a caracteres, nem todas. Supondo que "rune == caractere" seja válido apenas para um subconjunto de caracteres Unicode. Exemplo: en.wikipedia.org/wiki/…
zzzz
@ThomasKappler: mas se você olhar dessa maneira, por exemplo, Stringo .length()método de Java também não retorna o número de caracteres. Nem Cacau do NSString's -lengthmétodo. Aqueles simplesmente retornam o número de entidades UTF-16. Mas o número real de pontos de código raramente é usado, porque leva um tempo linear para contá-lo.
Newacct 01/10/12
5

Se você precisar levar em consideração os clusters de grafema, use o módulo regexp ou unicode. Contar o número de pontos de código (runas) ou bytes também é necessário para validação, uma vez que o comprimento do cluster grafema é ilimitado. Se você deseja eliminar sequências extremamente longas, verifique se as sequências estão em conformidade com o formato de texto seguro para fluxo .

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
fonte
Obrigado por isso. Eu tentei o seu código e ele não funciona para alguns grafemas emoji como estes: 🖖🏿🇸🇴. Alguma idéia de como contar com precisão?
Bjorn Roche
O regexp compilado deve ser extraído como varfora das funções.
Dolmen
5

Existem várias maneiras de obter um comprimento de string:

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)

}
leitão
fonte
3

Devo salientar que nenhuma das respostas fornecidas até o momento fornece o número de caracteres esperado, especialmente quando você lida com emojis (mas também com alguns idiomas como tailandês, coreano ou árabe). As sugestões do VonC produzirão o seguinte:

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

Isso ocorre porque esses métodos contam apenas pontos de código Unicode. Existem muitos caracteres que podem ser compostos de vários pontos de código.

O mesmo para usar o pacote de normalização :

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

Normalização não é realmente o mesmo que contar caracteres e muitos caracteres não podem ser normalizados no equivalente a um ponto de código.

A resposta da masakielastic chega perto, mas apenas lida com modificadores (a bandeira do arco-íris contém um modificador que, portanto, não é contado como seu próprio ponto de código):

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

A maneira correta de dividir seqüências de caracteres Unicode em caracteres (percebidos pelo usuário), ou seja, grupos de grafemas, é definida no Anexo Padrão 29 da Unicode . As regras podem ser encontradas na Seção 3.1.1 . O pacote github.com/rivo/uniseg implementa essas regras para que você possa determinar o número correto de caracteres em uma string:

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

Eu tentei fazer a normalização um pouco mais 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
fonte