如何获取字符串中的字符数

如何在 Go 中获得字符串的字符数?

例如,如果我有一个字符串 "hello",该方法应该返回 5。我看到 len(str)返回字节数 而不是返回字符数,所以 len("£")返回2而不是1,因为在 UTF-8中用两个字节编码。

127303 次浏览

您可以从 utf8包中尝试 RuneCountInString

返回 p 中的符号数

这个剧本所示: “世界”的长度可能是6(如果用中文写: “世界”) ,但“世界”的符文数是2:

package main
    

import "fmt"
import "unicode/utf8"
    

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

Phrozen 补充了 在评论中:

实际上,你可以通过类型施法对符文进行 len()
len([]rune("世界"))将打印 2。至少在去1.3。


有了 CL 108985(2018年5月,用于 Go 1.11) ,len([]rune(string))现在得到了优化

编译器自动检测 len([]rune(string))模式,并将其替换为 r: = range s 调用。

添加一个新的运行时函数来计算字符串中的符号。 修改编译器以检测模式 len([]rune(string)) 并用新的符文计数运行时函数替换它。

RuneCount/lenruneslice/ASCII        27.8ns ± 2%  14.5ns ± 3%  -47.70%
RuneCount/lenruneslice/Japanese     126ns ± 2%   60  ns ± 2%  -52.03%
RuneCount/lenruneslice/MixedLength  104ns ± 2%   50  ns ± 1%  -51.71%

Stefan Steiger 指向博客文章“ Go 中的文本规范化

什么是角色?

正如在 字符串博客文章字符可以跨越多个符文中提到的。
例如,‘ e’和‘ something something’(急性的‘ u0301’)可以结合形成‘ é’(在 NFD 中是‘ e\u0301’)。

字符的定义可能因应用程序而异。
对于 标准化,我们将其定义为:

  • 一系列以起始符号开始的符文,
  • 不会向后修改或与任何其他符文结合的符文,
  • 接下来可能是空的非起始序列,也就是说,如尼文(通常是重音符号)。

归一化算法一次处理一个字符。

使用这个包和它的 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“兼容性分解”


Oliver 回答指出,联合国编码文本段落是可靠地确定某些重要文本元素(用户感知的字符、单词和句子)之间的默认边界的唯一方法。

为此,您需要一个类似于 强大的外部库,它执行 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 代码点)。

你可以在“ 如何在 GO 中操作字符串来反转它们?”中看到其他例子

单独是一个字母,但是,从 Unicode 到码点转换器,4个如尼文:

这在很大程度上取决于你对“角色”的定义。如果“ rune 等于一个字符”对于你的任务来说是可以的(通常情况下是不可以的) ,那么 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)
}

有一种方法可以在没有任何包的情况下,通过将字符串转换为[]符文来计算符文的数量,如 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)))


}

字节数3016

如尼文计数1616

我试着让正常化的速度快一点:

    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
}

我应该指出的是,到目前为止提供的答案都没有给出你所期望的字符数量,特别是当你处理表情符号时(还有一些语言,如泰语、韩语或阿拉伯语)。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".

标准化与计数字符实际上并不相同,许多字符不能标准化为一个代码点等价物。

Masaki 的回答 很接近,但只处理修饰符(彩虹旗包含一个修饰符,因此不算作它自己的代码点) :

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)


}