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,类似于标准库函数。

实现非常简单,没有高度优化,但鉴于我认为这是一件小众的事情,所以优化这种类型的功能无论如何我都会归类为微优化。