如何获取字符串中的字符数?
-
12-12-2019 - |
题
Go中如何获取字符串的字符数?
例如,如果我有一个字符串 "hello"
该方法应该返回 5
. 。我看到了 len(str)
返回字节数 并不是 字符数所以 len("£")
返回 2 而不是 1,因为 £ 在 UTF-8 中使用两个字节进行编码。
解决方案
你可以试试 RuneCountInString
来自 utf8 包。
返回 p 中的符文数量
即,如图所示 这个脚本:“World”的长度可能是6(用中文写时:“世界”),但其符文数为 2:
package main
import "fmt"
import "unicode/utf8"
func main() {
fmt.Println("Hello, 世界", len("世界"), utf8.RuneCountInString("世界"))
}
其实你可以做 len()
仅通过类型转换来覆盖符文。
len([]rune("世界"))
将打印 2
. 。Go 1.3 中的至少。
与 CL 108985 (2018 年 5 月,Go 1.11), len([]rune(string))
现已优化。(修复 问题 24923)
编译器检测到 len([]rune(string))
自动模式,并将其替换为 for r := range s 调用。
添加一个新的运行时函数来计算字符串中的符文数量。修改编译器以检测模式
len([]rune(string))
并将其替换为新的符文计数运行时函数。
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)
斯特凡·施泰格 指向博客文章“Go 中的文本规范化"
什么是角色?
正如在 字符串博客文章, 字符可以跨越多个符文.
例如,一个 'e
' 和 '◌́◌́'(锐音“\u0301”)可以组合形成 'é'(“e\u0301
“在 NFD 中)。 这两个符文合在一起就是一个字符.字符的定义可能因应用程序而异。
为了 正常化 我们将其定义为:
- 以启动器开始的一系列符文,
- 不会修改或与任何其他符文向后组合的符文,
- 接下来是可能为空的非起始序列,即符文(通常是重音符号)。
标准化算法一次处理一个字符。
使用该包及其 Iter
类型, ,“字符”的实际数量为:
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)
}
在这里,这使用了 Unicode 规范化形式 NFKD《兼容性分解》
奥利弗的 回答 指着 UNICODE 文本分割 作为可靠地确定某些重要文本元素之间的默认边界的唯一方法:用户感知的字符、单词和句子。
为此,您需要一个外部库,例如 里沃/尤尼塞格, ,这确实 Unicode 文本分割.
这实际上会算“字素 簇”,其中多个代码点可以组合成一个用户感知的字符。
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]
}
两个字素,尽管有三个符文(Unicode 代码点)。
其他提示
有一种方法可以通过将字符串转换为 []rune 来获取没有任何包的符文计数: 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)))
}
字节数 30 16
符文数量 16 16
很大程度上取决于您对“角色”的定义。如果“符文等于一个字符”适合您的任务(通常不是),那么 VonC 的答案非常适合您。否则,可能应该注意的是,在很少的情况下,Unicode 字符串中的符文数量是一个有趣的值。即使在这些情况下,如果可能的话,最好在处理符文时“遍历”字符串时推断计数,以避免双倍的 UTF-8 解码工作。
如果需要考虑字素簇,请使用 regexp 或 unicode 模块。由于字素簇的长度是无限的,因此验证还需要计算代码点(符文)或字节的数量。如果要消除极长的序列,请检查序列是否符合 流安全文本格式.
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)
}
我应该指出,到目前为止提供的答案都没有提供您所期望的字符数,特别是当您处理表情符号时(还有一些语言,如泰语、韩语或阿拉伯语)。 VonC的建议 将输出以下内容:
fmt.Println(utf8.RuneCountInString("🏳️🌈🇩🇪")) // Outputs "6".
fmt.Println(len([]rune("🏳️🌈🇩🇪"))) // Outputs "6".
这是因为这些方法只计算 Unicode 代码点。有许多字符可以由多个代码点组成。
与使用相同 标准化包:
var ia norm.Iter
ia.InitString(norm.NFKD, "🏳️🌈🇩🇪")
nc := 0
for !ia.Done() {
nc = nc + 1
ia.Next()
}
fmt.Println(nc) // Outputs "6".
规范化实际上与计数字符并不相同,并且许多字符无法规范化为等效的单代码点。
玛莎琪弹性的回答 很接近,但仅处理修饰符(彩虹标志包含一个修饰符,因此不被视为其自己的代码点):
fmt.Println(GraphemeCountInString("🏳️🌈🇩🇪")) // Outputs "5".
fmt.Println(GraphemeCountInString2("🏳️🌈🇩🇪")) // Outputs "5".
将 Unicode 字符串拆分为(用户感知的)字符的正确方法,即字素簇,定义在 Unicode 标准附件 #29. 。规则可以在 第3.1.1节. 。这 github.com/rivo/uniseg 包实现了这些规则,因此您可以确定字符串中正确的字符数:
fmt.Println(uniseg.GraphemeClusterCount("🏳️🌈🇩🇪")) // Outputs "2".
获取字符串长度有多种方法:
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)
}
我试图让标准化更快一点:
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
}