Как перевернуть строку в Go: обратный порядок символов
Проблема байтовой итерации
Ваш первый подход не работает для не-ASCII символов, потому что Go хранит строки как последовательность байтов в кодировке UTF-8. Один символ (руна) может занимать от 1 до 4 байтов. Когда вы итерируете по байтам, вы разбиваете многобайтовые символы на отдельные байты, что приводит к некорректному результату.
Правильное решение: работа с рунами
В Go есть встроенный тип rune — это алиас для int32, представляющий один Unicode-символ. Чтобы корректно перевернуть строку, нужно сначала преобразовать её в слайс рун, затем перевернуть слайс, и только потом собрать обратно в строку.
package main
import "fmt"
func reverseRunes(s string) string {
// Преобразуем строку в слайс рун
runes := []rune(s)
// Переворачиваем слайс
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
// Преобразуем обратно в строку
return string(runes)
}
func main() {
tests := []string{
"hello",
"привет",
"👋Привет",
"世界",
"a b c",
}
for _, s := range tests {
fmt.Printf("%q -> %q\n", s, reverseRunes(s))
}
}
Вывод программы:
"hello" -> "olleh"
"привет" -> "тевирп"
"👋Привет" -> "тевирП👋"
"世界" -> "界世"
"a b c" -> "c b a"
Как это работает?
- Преобразование в руны:
[]rune(s)корректно декодирует UTF-8 последовательность, создавая слайс, где каждый элемент — это один символ, независимо от количества байтов. - Реверс слайса: Классический алгоритм с двумя указателями (i — с начала, j — с конца), меняющими элементы местами до встречи посередине.
- Обратное преобразование:
string(runes)кодирует руны обратно в UTF-8 строку.
Важные нюансы
- Производительность: Преобразование в руны создаёт новый слайс (аллокация памяти), что может быть затратно для очень больших строк. Для большинства случаев это приемлемо.
- Графемные кластеры: Даже работа с рунами не идеальна для всех случаев. Например, флаг "🇷🇺" состоит из двух рун (региональных индикаторов). Реверс по рунам разобьёт флаг на части. Для полной поддержки Unicode нужна библиотека
golang.org/x/text. - Изменяемость: Строки в Go неизменяемы, поэтому мы работаем с копией данных через слайс рун.
Альтернатива с bytes.Buffer (менее эффективно)
Можно использовать bytes.Buffer для построения результата:
func reverseBuffer(s string) string {
var buf bytes.Buffer
runes := []rune(s)
for i := len(runes) - 1; i >= 0; i-- {
buf.WriteRune(runes[i])
}
return buf.String()
}
Этот вариант менее эффективен из-за дополнительных операций записи в буфер, но может быть полезен для потоковой обработки.
Итог
Для корректного реверса строки в Go всегда используйте преобразование в []rune. Это стандартный идиоматичный подход, который работает для всех Unicode-символов, кроме составных графемных кластеров. Если вам нужна работа с текстом на уровне человеческого восприятия (например, для UI), рассмотрите библиотеки для работы с графемами.
Реверсирование без создания нового слайса рун
Если вы хотите избежать аллокации памяти под слайс рун (например, для оптимизации работы с очень большими строками), можно использовать пакет unicode/utf8 для итерации по рунам напрямую:
import "unicode/utf8"
func reverseUTF8(s string) string {
// Сначала считаем количество рун
size := utf8.RuneCountInString(s)
if size <= 1 {
return s
}
// Создаём слайс байтов нужного размера
result := make([]byte, len(s))
pos := len(result)
for len(s) > 0 {
r, size := utf8.DecodeRuneInString(s)
pos -= size
utf8.EncodeRune(result[pos:], r)
s = s[size:]
}
return string(result)
}
Как это работает?
Мы используем utf8.DecodeRuneInString для извлечения первой руны из строки, а затем utf8.EncodeRune для записи её в конец результирующего байтового слайса. Таким образом, мы обходимся без промежуточного слайса рун, работая напрямую с байтами.
Когда это нужно?
- При работе с очень длинными строками (сотни мегабайт), где двойная аллокация памяти критична.
- В высоконагруженных системах, где важна каждая микросекунда.
- При обработке потоковых данных, когда строка не помещается в память целиком.
Однако для 99% случаев простой подход с []rune является более читаемым и предпочтительным. Оптимизация нужна только если профилирование показало, что реверс строк является узким местом.
Ответов пока нет.
Войдите чтобы оставить ответ