В языке Go строки хранятся как последовательность байтов (byte). Для корректной работы с символами Unicode часто требуется преобразовать строку в срез рун ([]rune), где каждая руна – это 32‑битовое целое (int32), представляющее одну кодовую точку Unicode. Именно такой подход позволяет выполнять правильную сортировку символов, особенно когда строка содержит нелатинские буквы, эмодзи или комбинирующие знаки.
Что такое руна и почему она важна?
- Руна – это отдельная кодовая точка Unicode, представляемая типом
rune(aliasint32). - В отличие от
byte, руна может занимать до четырёх байт в UTF‑8, что позволяет хранить любой символ мира. - Преобразование строки в срез рун упрощает работу с символами, потому что каждый элемент уже соответствует одному логическому символу (коду точки).
Почему сортировать строки по рунам?
Сортировка «по байтам» приводит к неверному порядку для многобайтовых символов. Например, строка "áß" в UTF‑8 представлена как [0xC3 0xA1 0xC3 0x9F]. При обычном сравнении байтов «ß» окажется раньше «á», хотя в Unicode её кодовая точка (U+00DF) больше.
Сортируя по рунам, мы сравниваем кодовые точки, а не их байтовое представление, что соответствует официальному порядку Unicode.
Как преобразовать строку в срез рун?
package main
import "fmt"
func main {
s := "Привет, мир! 🌍"
// Преобразуем строку в срез рун
r := []rune(s)
fmt.Printf("Исходная строка: %s
", s)
fmt;Printf("Срез рун: %v
", r)
}
В результате получаем массив целых чисел, где каждый элемент – кодовая точка Unicode.
Сортировка среза рун
Для сортировки удобно использовать пакет sort. Ниже показан пример сортировки в лексикографическом порядке Unicode.
package main
import (
"fmt"
"sort"
)
func main {
// Строка с разными символами
s := "zαβγδaΩБвгд"
// Преобразуем в руны
r := []rune(s)
// Сортируем с помощью sort.Slice
sort.Slice(r, func(i, j int) bool {
return r[i] < r[j] // сравнение кодовых точек
})
// Обратное преобразование в строку
sorted := string(r)
fmt.Printf("Исходная: %s
", s)
fmt.Printf("Отсортированная: %s
", sorted)
}
Исходная: zαβγδaΩБвгд
Отсортированная: aБвгдzαβγδΩ
Обратное преобразование: из рун в строку
После сортировки достаточно выполнить приведение типа string(runes):
sortedString := string(runes)
Это создаст новую строку, где каждый rune будет автоматически закодирован в UTF‑8.
Подводные камни: графемы и комбинирующие знаки
Не все визуальные символы соответствуют одной руне. Некоторые «символы» состоят из нескольких кодовых точек (графемный кластер). Пример – буква é, которая может быть представлена как:
- U+00E9 (одна руна) – «é»
- U+0065 U+0301 (буква e + комбинирующий акут) – две руны
При сортировке по рунам такие кластеры могут попасть в разный порядок, хотя визуально они одинаковы. Чтобы правильно сортировать «по пользователю», используют нормализацию Unicode (NFC, NFD) и/или collation-библиотеки, например golang.org/x/text/collate.
Пример нормализации перед сортировкой
package main
import (
"fmt"
"sort"
"golang.org/x/text/unicode/norm"
)
func main {
words := []string{"éclair", "éclair", "apple", "banana"}
// Приведём все строки к NFC (композитная форма)
for i, w := range words {
words[i] = norm.NFC.String(w)
}
// Сортируем обычным способом
sort.Strings(words)
fmt.Println(words) // => [apple banana éclair éclair]
}
После нормализации обе формы «é» становятся одинаковыми, и сортировка будет предсказуемой.
Полный практический пример
Ниже собран полностью рабочий пример, который:
- Считывает список строк.
- Нормализует их в форму NFC.
- Преобразует каждую строку в срез рун.
- Сортирует руны внутри каждой строки (полезно, например, для анаграмм).
- Собирает строки обратно и выводит их в отсортированном порядке.
package main
import (
"bufio"
"fmt"
"os"
"sort"
"golang.org/x/text/unicode/norm"
)
func sortRunesInString(s string) string {
// Нормализуем, чтобы одинаковые визуальные символы имели одинаковый набор рун
s = norm.NFC.String(s)
// Преобразуем в руны
r := []rune(s)
// Сортируем руны
sort.Slice(r, func(i, j int) bool { return r[i] < r[j] })
// Возвращаем строку
return string(r)
}
func main {
fmt.Println("Введите строки (пустая строка завершит ввод):")
scanner := bufio.NewScanner(os.Stdin)
var lines []string
for scanner.Scan {
line := scanner.Text
if line == "" {
break
}
lines = append(lines, line)
}
// Сортируем строки целиком (по Unicode)
sort.Strings(lines)
fmt.Println("
Отсортированные строки с сортировкой внутри каждой строки:")
for _, line := range lines {
fmt.Printf("%s → %s
", line, sortRunesInString(line))
}}
Запуск программы:
$ go run sort_runes.go
Введите строки (пустая строка завершит ввод):
привет
мир
áß
zαβγδ
Отсортированные строки с сортировкой внутри каждой строки:
áß → ßá
мир → имр
привет → еипрвт
zαβγδ → αβγδz
- В Go строки – это массив байтов, а
rune– удобный способ работать с отдельными Unicode‑символами. - Для корректной сортировки необходимо преобразовать строку в срез рун, отсортировать их и собрать обратно.
- Если в строках могут встречаться комбинирующие знаки, следует нормализовать их (NFC/NFD) перед сортировкой.
- Для «человеческой» сортировки (collation) используйте пакет
golang.org/x/text/collateили аналогичные библиотеки.
Таким образом, любой разработчик, начинающий работать с текстом в Go, может быстро внедрить надёжную сортировку строк, опираясь на руны и стандартные возможности языка.