Как заменить букву на определенный индекс в строке в Go?
Я хочу заменить букву на определенный индекс в строке: 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:]}, "")
}
Ответы
Ответ 1
Строки являются неизменными в Go, вы должны преобразовать их в руны, затем изменить их, а затем преобразовать обратно в строку.
Решение @chendesheng является полукорректным, за исключением того, что вы можете использовать rune
вместо byte
, чтобы оно работало и с юникодом.
func replaceAtIndex(in string, r rune, i int) string {
out := []rune(in)
out[i] = r
return string(out)
}
playground
Ответ 2
Оба ответа (OneOfOne и Denys 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 за обнаружение ошибки copypasting):
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
Ответ 3
Вы можете объединять строки с помощью оператора +:
return input[:index] + string(replacement) + input[index+1:]
Будьте осторожны, что индекс - это не индекс буквы, а байта.
Ответ 4
Просто для удовольствия:
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)
}
Никогда не пытайтесь использовать его где-то в своем коде.
Это медленнее, чем любые стандартные решения или примеры выше и гораздо более опасные. =)
(Он не работает на детской площадке, потому что syscall
там не определен).
Ответ 5
Я взял эталон Сальвадора и добавил к нему strings.Join()
. Его ответ остается верным - str[:index] + string(replacement) + str[index+1:]
- самый быстрый вариант для больших строк, если вы хотите сохранить исходную строку. strings.Join()
довольно близко для маленьких строк и очень очень близко для больших строк. Я также добавил тесты с еще более крупными строками, чтобы проверить, работает ли strings.Join()
в любой момент быстрее - кажется, нет.
Я также, просто для забавы, взломал две другие реализации с unsafe
и reflect
- Каждый делает копию, поэтому он не изменяет исходную строку и имеет очень интересные результаты производительности - быстрее для маленьких строк, намного быстрее для средних строк, намного медленнее для больших строк
- Другой просто делает изменяемую строку и, что неудивительно, значительно быстрее для всех целей и работает в постоянном времени, с оговоркой, что ваша исходная строка будет изменена.
Я также должен был изменить 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