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

时间:2012-10-01 06:52:15

标签: string go character string-length

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

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

7 个答案:

答案 0 :(得分:139)

您可以尝试使用utf8软件包中的RuneCountInString

  

返回p

中的符文数

this script所示:“世界”的长度可能为6(用中文写成:“世界”),但其符文数为2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen添加in the comments

实际上你可以通过输入来进行符文len() len([]rune("世界"))将打印2。在Go 1.3中获取。


使用CL 108985(2018年5月,Go 1.11),len([]rune(string))现已优化。 (修正issue 24923

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

  

添加一个新的运行时函数来计算字符串中的符文。   修改编译器以检测模式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)

Stefan Steiger指向博文“Text normalization in Go

什么是角色?

  

正如strings blog post中提到的,字符可以跨越多个符文   例如,“e”和“◌◌”(急性“\ u0301”)可以组合形成“é”(NFD中的“e\u0301”)。 这两个符文是一个字符

     

角色的定义可能因应用程序而异   对于 normalization ,我们将其定义为:

     
      
  • 一系列以首发开始的符文
  •   
  • 一个不会修改或与任何其他符文向后组合的符文,
  •   
  • 后面可能是空的非启动者序列,即符号(通常是重音符号)。
  •   
     

规范化算法一次处理一个字符。

使用该软件包及其Iter type,“字符”的实际数量为:

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 Normalization form NFKD“兼容性分解”


Oliveranswer指向 UNICODE TEXT SEGMENTATION 作为可靠地确定某些重要文本元素之间默认边界的唯一方法:用户感知字符,单词和句子。

为此,您需要一个像rivo/uniseg这样的外部库,它可以执行 Unicode文本分段

这实际上会计入“ grapheme群集 ”,其中多个代码点可能会合并为一个用户感知的角色。

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代码点)。

答案 1 :(得分:33)

有一种方法可以通过将字符串转换为[]符号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

答案 2 :(得分:5)

很大程度上取决于你对#34;字符&#34;的定义。是。如果&#34;符文等于一个字符&#34;对你的任务来说是好的(通常它不是),那么VonC的答案对你来说是完美的。否则,应该注意的是,很少有情况下Unicode字符串中的符文数是一个有趣的值。即使在这些情况下,如果可能的话,在“穿越”时更好地推断计数。处理符文时的字符串,以避免加倍UTF-8解码工作。

答案 3 :(得分:5)

如果您需要考虑字形集群,请使用regexp或unicode模块。由于字素集群的长度不受限制,因此还需要计算代码点(符文)或字节的数量。如果要消除极长的序列,请检查序列是否符合stream-safe text format

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) 
}

答案 4 :(得分:3)

我应该指出,到目前为止,没有提供任何答案,无法提供您期望的字符数,尤其是在处理表情符号(还包括泰语,韩语或阿拉伯语等某些语言)时。 VonC's suggestions将输出以下内容:

fmt.Println(utf8.RuneCountInString("️‍")) // Outputs "6".
fmt.Println(len([]rune("️‍"))) // Outputs "6".

这是因为这些方法仅计算Unicode代码点。有许多字符可以由多个代码点组成。

与使用Normalization package相同:

var ia norm.Iter
ia.InitString(norm.NFKD, "️‍")
nc := 0
for !ia.Done() {
    nc = nc + 1
    ia.Next()
}
fmt.Println(nc) // Outputs "6".

规范化与计数字符并不完全相同,许多字符不能规范化为一个代码点的等价物。

masakielastic's answer接近,但仅处理修饰符(rainbow标志包含修饰符,因此该修饰符不算作自己的代码点):

fmt.Println(GraphemeCountInString("️‍"))  // Outputs "5".
fmt.Println(GraphemeCountInString2("️‍")) // Outputs "5".

Unicode Standard Annex #29中定义了将Unicode字符串拆分为(用户可感知的)字符(即字素簇)的正确方法。规则可以在Section 3.1.1中找到。 github.com/rivo/uniseg包实现了以下规则,因此您可以确定字符串中正确的字符数:

fmt.Println(uniseg.GraphemeClusterCount("️‍")) // Outputs "2".

答案 5 :(得分:1)

有几种获取字符串长度的方法:

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)

}

答案 6 :(得分:0)

我试图使归一化速度更快:

    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
    }