Como obter o número de caracteres em uma string?
-
12-12-2019 - |
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.
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
}