如何在Go语言中替换字符串特定索引处的字母?

36
我想在字符串中特定的索引位置替换一个字母: aaaaaaa -> aaabaaa。有没有内置的方法可以实现这个功能?我编写了以下辅助函数,在此期间使用它:
func main() {
    input := "aaaaaaa"
    output := replaceAtIndex(input, 'b', 3)
}

func replaceAtIndex(input string, replacement byte, index int) string {
    return strings.Join([]string{input[:index], string(replacement), input[index+1:]}, "")
}
5个回答

41

在Go语言中,字符串是不可变的,如果要修改一个字符串,你需要先将其转换为rune类型,然后进行修改,最后再将其转换回字符串类型。

@chendesheng的解决方案是半正确的,除了你可以使用rune而不是byte,这样它也可以处理Unicode。

func replaceAtIndex(in string, r rune, i int) string {
    out := []rune(in)
    out[i] = r
    return string(out)
}

playground


30

两个答案(OneOfOneDenys Séguret)都是正确的。我只是想展示它们之间的性能差异(当字符串很大时,这种差异非常明显)。

结果证明使用str[:index] + string(replacement) + str[index+1:]速度更快。

所以这个基准测试:

package main
import (
    "testing"
)

func replaceAtIndex1(str string, replacement rune, index int) string {
    out := []rune(str)
    out[index] = replacement
    return string(out)
}

func replaceAtIndex2(str string, replacement rune, index int) string {
    return str[:index] + string(replacement) + str[index+1:]
}

func generateString(n int) string{
    s := ""
    for i := 0; i < n; i++{
        s += "a"
    }
    return s
}

func BenchmarkSmall1(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex1(str, replacement, index)
    }
}

func BenchmarkSmall2(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkMedium1(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex1(str, replacement, index)
    }
}

func BenchmarkMedium2(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkBig1(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex1(str, replacement, index)
    }
}

func BenchmarkBig2(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func main(){}

显示以下结果(感谢Thomasz发现了一个复制粘贴错误):


BenchmarkSmall1-4   10000000           228 ns/op
BenchmarkSmall2-4   10000000           126 ns/op
BenchmarkMedium1-4    500000          2091 ns/op
BenchmarkMedium2-4  10000000           190 ns/op
BenchmarkBig1-4        10000        209232 ns/op
BenchmarkBig2-4       500000          3629 ns/op

2
这不是一个公平的比较。在不转换为rune的情况下切片str是无用的。 - Amos
3
str[:index] 像操作字节一样,与 rune 有很大的区别,这种方式可能会破坏一个 rune。例如,你可以把“很好,hello world”作为字符串使用。 - Cytown

20

您可以使用 + 运算符连接字符串:

return input[:index] + string(replacement) + input[index+1:]

请注意,索引不是“字母”的索引,而是字节的索引。


这种方法与 strings.Join 相比是否有任何好处? - Ferguzz
有人对与 strings.Join 的比较感兴趣吗? - joanlopez
str[:index] 像操作字节一样,它与rune非常不同,你可能会以那种方式破坏一个rune。例如,您可以使用“很好,hello world”作为字符串。 - Cytown

7

仅供娱乐:

package main                                                                                                                                                                                                

import (                                                                                                                                                                                                    
    "fmt"                                                                                                                                                                                                   
    "reflect"                                                                                                                                                                                               
    "syscall"                                                                                                                                                                                               
    "unsafe"                                                                                                                                                                                                
)                                                                                                                                                                                                           

// We should do this because by default strings in Go are read-only.                                                                                                                                                                                                                                                                                                                                                                                                                  
func mprotect(ptr uintptr, w bool) {                                                                                                                                                                        
    // Need to avoid "EINVAL addr is not a valid pointer,
    // or not a multiple of PAGESIZE."                                                                                                                   
    start := ptr & ^(uintptr(syscall.Getpagesize() - 1))                                                                                                                                                    

    prot := syscall.PROT_READ                                                                                                                                                                               
    if w {                                                                                                                                                                                                  
        prot |= syscall.PROT_WRITE                                                                                                                                                                          
    }                                                                                                                                                                                                       

    _, _, err := syscall.Syscall(                                                                                                                                                                           
        syscall.SYS_MPROTECT,                                                                                                                                                                               
        start, uintptr(syscall.Getpagesize()),                                                                                                                                                              
        uintptr(prot),                                                                                                                                                                                      
    )                                                                                                                                                                                                       
    if err != 0 {                                                                                                                                                                                           
        panic(err.Error())                                                                                                                                                                                  
    }                                                                                                                                                                                                       
}                                                                                                                                                                                                           

// This function is very, very very very unsafe.                                                                                                                                                            
// Nowhere and never use it!                                                                                                                                                                                
func replaceAtIndex(s string, b byte, i int) {                                                                                                                                                              
    h := *(*reflect.StringHeader)(unsafe.Pointer(&s))                                                                                                                                                       

    mprotect(h.Data, true)                                                                                                                                                                                  
    defer mprotect(h.Data, false)                                                                                                                                                                           

    *(*byte)(unsafe.Pointer(h.Data + uintptr(i))) = b                                                                                                                                                       
}                                                                                                                                                                                                           

func main() {                                                                                                                                                                                               
    h := "Hello, playground"                                                                                                                                                                                
    replaceAtIndex(h, 'x', 0)                                                                                                                                                                               
    fmt.Println(h)                                                                                                                                                                                          
}

不要在代码中尝试使用它。它比任何标准解决方案或上面的示例都慢得多,而且更加不安全。=)

(它不在playground中工作,因为syscall在那里没有定义)。


4
我对Salvador的基准测试进行了修改,并添加了strings.Join()。他的答案仍然正确 - 对于大字符串,假设您想保留原始字符串,str[:index] + string(replacement) + str[index+1:]是最快的选择。对于小字符串来说,strings.Join()也很接近,对于大字符串来说则非常接近。我还使用更大的字符串添加了测试,以查看是否在任何时候strings.Join()会变得更快 - 看起来似乎不是这样。
此外,仅仅为了好玩,我用unsafereflect编写了另外两个实现。
  • 其中一个进行了复制,因此它不会修改原始字符串,并且具有非常有趣的性能结果 - 对于小字符串来说更快,对于中等大小的字符串来说要快得多,但对于大字符串来说要慢得多。
  • 另一个只是使用可变字符串,并且在所有情况下都显著快得多,并且以恒定时间运行,但要注意它会修改原始字符串。
我还必须修改generateString(),以便生成的最大字符串可以在合理的时间内生成 ;)
无论如何,以下是代码:
package main
import (
    "reflect"
    "strings"
    "testing"
    "unsafe"
)

func replaceAtIndex1(str string, replacement rune, index int) string {
    out := []rune(str)
    out[index] = replacement
    return string(out)
}

func replaceAtIndex2(str string, replacement rune, index int) string {
    return str[:index] + string(replacement) + str[index+1:]
}

func replaceAtIndex3(str string, replacement rune, index int) string {
    return strings.Join([]string{str[:index], str[index + 1:]}, string(replacement))
}

func strToBytes(str string) []byte {
    string_header := (*reflect.StringHeader)(unsafe.Pointer(&str))
    bytes_header := &reflect.SliceHeader{
        Data : string_header.Data,
        Len : string_header.Len,
        Cap : string_header.Len,
    }
    return *(*[]byte)(unsafe.Pointer(bytes_header))
}

func strToBytesCopy(str string) []byte {
    bytes_unsafe := strToBytes(str)
    bytes := make([]byte, len(bytes_unsafe))
    copy(bytes, bytes_unsafe)
    return bytes
}

func bytesToStr(bytes []byte) string {
    bytes_header := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
    string_header := &reflect.StringHeader{
        Data : bytes_header.Data,
        Len : bytes_header.Len,
    }
    return *(*string)(unsafe.Pointer(string_header))
}

func replaceAtIndex4(str string, replacement rune, index int) string {
    bytes := strToBytesCopy(str)
    bytes[index] = byte(replacement)
    return bytesToStr(bytes)
}

func replaceAtIndex5(str string, replacement rune, index int) string {
    bytes := strToBytes(str)
    bytes[index] = byte(replacement)
    return bytesToStr(bytes)
}

func generateString(n int) string{
    var b strings.Builder
    b.Grow(n)
    for i := 0; i < n; i++ {
        b.WriteRune('a')
    }
    return b.String()
}

func BenchmarkSmall1(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex1(str, replacement, index)
    }
}

func BenchmarkSmall2(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkSmall3(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex3(str, replacement, index)
    }
}

func BenchmarkSmall4(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex4(str, replacement, index)
    }
}

func BenchmarkSmall5(b *testing.B) {
    n := 10
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex5(str, replacement, index)
    }
}

func BenchmarkMedium1(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex1(str, replacement, index)
    }
}

func BenchmarkMedium2(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkMedium3(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex3(str, replacement, index)
    }
}

func BenchmarkMedium4(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex4(str, replacement, index)
    }
}

func BenchmarkMedium5(b *testing.B) {
    n := 100
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex5(str, replacement, index)
    }
}

func BenchmarkBig1(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex1(str, replacement, index)
    }
}

func BenchmarkBig2(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkBig3(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex3(str, replacement, index)
    }
}

func BenchmarkBig4(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex4(str, replacement, index)
    }
}

func BenchmarkBig5(b *testing.B) {
    n := 10000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex5(str, replacement, index)
    }
}

func BenchmarkHuge2(b *testing.B) {
    n := 100000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkHuge3(b *testing.B) {
    n := 100000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex3(str, replacement, index)
    }
}

func BenchmarkHuge4(b *testing.B) {
    n := 100000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex4(str, replacement, index)
    }
}

func BenchmarkHuge5(b *testing.B) {
    n := 100000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex5(str, replacement, index)
    }
}

func BenchmarkGargantuan2(b *testing.B) {
    n := 10000000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex2(str, replacement, index)
    }
}

func BenchmarkGargantuan3(b *testing.B) {
    n := 10000000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex3(str, replacement, index)
    }
}

func BenchmarkGargantuan4(b *testing.B) {
    n := 10000000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex4(str, replacement, index)
    }
}

func BenchmarkGargantuan5(b *testing.B) {
    n := 10000000
    str, index, replacement := generateString(n), n / 2, 'B'

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        replaceAtIndex5(str, replacement, index)
    }
}

func main(){}

以下是结果:

BenchmarkSmall1-8           20000000            99.9 ns/op
BenchmarkSmall2-8           50000000            29.5 ns/op
BenchmarkSmall3-8           20000000            58.1 ns/op
BenchmarkSmall4-8           50000000            32.0 ns/op
BenchmarkSmall5-8           1000000000           2.93 ns/op
BenchmarkMedium1-8           1000000          1034 ns/op
BenchmarkMedium2-8          20000000            68.4 ns/op
BenchmarkMedium3-8          20000000            78.8 ns/op
BenchmarkMedium4-8          30000000            49.3 ns/op
BenchmarkMedium5-8          1000000000           3.02 ns/op
BenchmarkBig1-8                20000         89557 ns/op
BenchmarkBig2-8              1000000          1204 ns/op
BenchmarkBig3-8              1000000          1257 ns/op
BenchmarkBig4-8              1000000          1200 ns/op
BenchmarkBig5-8             1000000000           2.93 ns/op
BenchmarkHuge2-8              200000         10260 ns/op
BenchmarkHuge3-8              200000          9908 ns/op
BenchmarkHuge4-8              100000         13628 ns/op
BenchmarkHuge5-8            1000000000           2.99 ns/op
BenchmarkGargantuan2-8          2000        822881 ns/op
BenchmarkGargantuan3-8          2000        807522 ns/op
BenchmarkGargantuan4-8          1000       2148387 ns/op
BenchmarkGargantuan5-8      1000000000           2.96 ns/op

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接