strings.index unicode 行为
strings.index unicode behaviour
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Index("ééé hannah","han"))
fmt.Println(strings.Index("eee hannah", "han"))
}
预期输出:
4
4
实际输出:
7
4
我怀疑这种行为与 é
是非 ASCII 字符这一事实有关。你知道我怎样才能达到预期的输出吗?
它在 7 和 4 处有 字节索引 ,看评论,试试 it:
s1 := "ééé hannah"
s2 := "eee hannah"
s3 := "han"
fmt.Println([]rune(s3))
// [104 97 110]
fmt.Println([]rune(s1))
// [233 233 233 32 104 97 110 110 97 104]
fmt.Println([]byte(s1))
// [195 169 195 169 195 169 32 104 97 110 110 97 104]
fmt.Println(strings.Index(s1, s3))
fmt.Println([]rune(s2))
// [101 101 101 32 104 97 110 110 97 104]
fmt.Println([]byte(s2))
// [101 101 101 32 104 97 110 110 97 104]
fmt.Println(strings.Index(s2, s3))
参见:Go/src/strings/strings.go
,它使用 IndexByte
:
// IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
func IndexByte(s string, c byte) int {
return bytealg.IndexByteString(s, c)
}
所以 as wasmup 在他们的回答中已经说明了:strings.Index
returns 字节索引。您期望的是 unicode 索引。像 é
这样的 Unicode 字符实际上是多字节编码的东西,这就是为什么输入字符串中的 3 é
看起来被计算了两次(产生索引 7 而不是预期的 4)。
一些背景
golang 中的字符串基本上就是一个字节片。这就是为什么 strings.Index
returns 它所做的值:以字节为单位找到匹配项的偏移量。然而,Unicode 处理代码点以允许多字节字符。 golang 没有将这种类型命名为 codepoint
,而是将其称为 rune
。关于这件事还有很多话要说,但你可以阅读更多here。
不过,考虑到这一点,我们可以创建自己的 Index
函数,为您提供符文索引,而不是字节索引。让我们调用函数 RuneIndex
。这种功能的即兴实现可能看起来像这样:
func RuneIndex(str, sub string) int {
// ensure valid input
if len(str) == 0 || len(sub) == 0 {
return -1
}
// convert to rune slices
rin, rmatch := []rune(str), []rune(sub)
// iterate over input until end of string - length of match we're trying to find
for i := 0; i < len(rin) - len(rmatch); i++ {
// slight optimisation: if the first runes don't match, don't bother comparing full substrings
if rin[i] != rmatch[0] {
continue
}
// compare substrings directly, if they match, we're done
if string(rin[i:i+len(rmatch)]) == sub {
return i
}
}
return -1
}
它基本上只是将子字符串与我们要搜索的字符串的子片段进行比较。通过将符文子片段转换为字符串,我们可以只使用 ==
运算符,如果找到匹配项,我们 return i
,这是符文索引(而不是字节索引)。我添加了一些检查以确保参数不为空,如果没有找到索引则函数 returns -1,类似于标准库函数。
实现非常简单,没有高度优化,但鉴于我认为这是一件小众的事情,所以优化这种类型的功能无论如何我都会归类为微优化。
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Index("ééé hannah","han"))
fmt.Println(strings.Index("eee hannah", "han"))
}
预期输出:
4
4
实际输出:
7
4
我怀疑这种行为与 é
是非 ASCII 字符这一事实有关。你知道我怎样才能达到预期的输出吗?
它在 7 和 4 处有 字节索引 ,看评论,试试 it:
s1 := "ééé hannah"
s2 := "eee hannah"
s3 := "han"
fmt.Println([]rune(s3))
// [104 97 110]
fmt.Println([]rune(s1))
// [233 233 233 32 104 97 110 110 97 104]
fmt.Println([]byte(s1))
// [195 169 195 169 195 169 32 104 97 110 110 97 104]
fmt.Println(strings.Index(s1, s3))
fmt.Println([]rune(s2))
// [101 101 101 32 104 97 110 110 97 104]
fmt.Println([]byte(s2))
// [101 101 101 32 104 97 110 110 97 104]
fmt.Println(strings.Index(s2, s3))
参见:Go/src/strings/strings.go
,它使用 IndexByte
:
// IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
func IndexByte(s string, c byte) int {
return bytealg.IndexByteString(s, c)
}
所以 as wasmup 在他们的回答中已经说明了:strings.Index
returns 字节索引。您期望的是 unicode 索引。像 é
这样的 Unicode 字符实际上是多字节编码的东西,这就是为什么输入字符串中的 3 é
看起来被计算了两次(产生索引 7 而不是预期的 4)。
一些背景
golang 中的字符串基本上就是一个字节片。这就是为什么 strings.Index
returns 它所做的值:以字节为单位找到匹配项的偏移量。然而,Unicode 处理代码点以允许多字节字符。 golang 没有将这种类型命名为 codepoint
,而是将其称为 rune
。关于这件事还有很多话要说,但你可以阅读更多here。
不过,考虑到这一点,我们可以创建自己的 Index
函数,为您提供符文索引,而不是字节索引。让我们调用函数 RuneIndex
。这种功能的即兴实现可能看起来像这样:
func RuneIndex(str, sub string) int {
// ensure valid input
if len(str) == 0 || len(sub) == 0 {
return -1
}
// convert to rune slices
rin, rmatch := []rune(str), []rune(sub)
// iterate over input until end of string - length of match we're trying to find
for i := 0; i < len(rin) - len(rmatch); i++ {
// slight optimisation: if the first runes don't match, don't bother comparing full substrings
if rin[i] != rmatch[0] {
continue
}
// compare substrings directly, if they match, we're done
if string(rin[i:i+len(rmatch)]) == sub {
return i
}
}
return -1
}
它基本上只是将子字符串与我们要搜索的字符串的子片段进行比较。通过将符文子片段转换为字符串,我们可以只使用 ==
运算符,如果找到匹配项,我们 return i
,这是符文索引(而不是字节索引)。我添加了一些检查以确保参数不为空,如果没有找到索引则函数 returns -1,类似于标准库函数。
实现非常简单,没有高度优化,但鉴于我认为这是一件小众的事情,所以优化这种类型的功能无论如何我都会归类为微优化。