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
    }
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top