Pergunta

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

Por exemplo, se eu tiver uma string "hello" o método deve retornar 5.eu vi isso 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.

Foi útil?

Solução

Podes tentar RuneCountInString do pacote utf8.

retorna o número de runas em p

que, como ilustrado em este roteiro:o comprimento de "World" 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("世界"))
}

Congelado adiciona nos comentários:

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


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

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

Adiciona uma nova função de tempo de execução para contar runas em uma string.Modifica o compilador para detectar o padrão len([]rune(string))e a 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 em Go"

O que é um personagem?

Como foi mencionado no postagem no blog de cordas, personagens podem abranger múltiplas runas.
Por exemplo, um 'e' e '◌́◌́' (agudo "\u0301") podem se combinar para formar 'é' ("e\u0301"em NFD). Juntas, essas duas runas formam um personagem.

A definição de um caractere pode variar dependendo da aplicação.
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 ao contrário com qualquer outra runa,
  • seguido por uma sequência possivelmente vazia de não-iniciantes, ou seja, runas que sim (normalmente acentos).

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

Usando esse pacote e seu Iter tipo, 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"


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

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

Isso vai realmente contar"grafema conjunto", 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).

Outras dicas

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

Depende muito da sua definição do que é um “personagem”.Se "runa é igual a um caractere" for adequado para sua tarefa (geralmente não é), então a resposta de VonC é perfeita para você.Caso contrário, provavelmente deve-se notar 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 "atravessa" a string à medida que as runas são processadas para evitar duplicar o esforço de decodificação UTF-8.

Se você precisar levar em consideração os clusters de grafemas, use o módulo regexp ou unicode.Contar o número de pontos de código (runas) ou bytes também é necessário para a validação, uma vez que o comprimento do cluster de grafema é ilimitado.Se você quiser eliminar sequências extremamente longas, verifique se as sequências estão em conformidade com 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) 
}

Devo salientar que nenhuma das respostas fornecidas até agora fornece o número de caracteres esperado, especialmente quando se trata de emojis (mas também de alguns idiomas como tailandês, coreano ou árabe). Sugestões de VonC produzirá 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".

A normalização não é realmente o mesmo que contar caracteres e muitos caracteres não podem ser normalizados em um equivalente de um ponto de código.

resposta do masakielastic chega perto, mas apenas lida com modificadores (o sinalizador 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 strings Unicode em caracteres (percebidos pelo usuário), ou seja,clusters de grafemas, é definido no Anexo padrão Unicode nº 29.As regras podem ser encontradas em Seção 3.1.1.O github.com/rivo/uniseg O pacote implementa estas regras para que você possa determinar o número correto de caracteres em uma string:

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

Existem várias maneiras de obter o comprimento de uma 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)

}

Tentei fazer a normalização um pouco mais rápida:

    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
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top