Go в примери: Низове и Руни

Низът в Go е непроменяем отрязък от байтове. Езикът и стандартната му библиотека работят с низовете по-особено – сякаш съдържат текст кодиран в UTF-81. В други езици низовете са направени от „знаци“. В Го за понятието „знак“ се използва думата руна – rune. Руната е цяло число, което представлява кодова точка в таблицата Уникод2. Статията „Низове, байтове, руни и знаци“ (https://go.dev/blog/strings) е добро въведение по въпроса. TODO: Да преведа и добавя като приложение към книгата статията https://go.dev/blog/strings


  1. UTF-8 – https://bg.wikipedia.org/wiki/UTF-8
  2. Уникод – https://bg.wikipedia.org/wiki/Уникод
package main
import (
    "fmt"
    "unicode/utf8"
)
func main() {

s е string, на който е присвоена буквална стойност, съдържаща думата „здравей“ на тайски. Буквалните низови стойности в Go са кодиран в UTF-8 текст. Това означава, че и изходният програмен код в Го е такъв текст.

    const s = "สวัสดี"

Тъй като низовете са в същността си []byte – отрязък от байтове, следното изявление ще изведе броя на суровите байтове в низа.

    fmt.Println("дължина в байтове:", len(s))

Ако обходим низа байт по байт по следния начин, можем да видим шестнадесетичните стойности на байтовете, от който е съставен низът s.

    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
    fmt.Println()

За да преброим колко руни (знакове) има в низа, можем да ползваме пакета utf8. Забележете, че времето за изпълнение на RuneCountInString зависи от размера на низа, защото трябва да се разкодира от UTF-8 всяка буква (руна) последователно. Някои знаци в тайски съдържат повече от един байт, когато са представени с кодови точки в UTF-8. Така е и с български – буквите ни са съставени от двубайтови последователности. Така че изходът от следното преброяване може да ви изненада.

    fmt.Println("Брой знаци:", utf8.RuneCountInString(s))

range обхожда низовете по особен начин – като разкодира всеки знак заедно с отстоянието му в низа – мястото на байта, от който започва знака.

    for idx, runeValue := range s {
        fmt.Printf("%#U започва от %d\n", runeValue, idx)
    }

Можем да постигнем същото, като използваме функцията utf8.DecodeRuneInString изрично.

    fmt.Println("\nИзползваме DecodeRuneInString")
    for i, w := 0, 0; i < len(s); i += w {
        runeValue, width := utf8.DecodeRuneInString(s[i:])
        fmt.Printf("%#U започва от %d\n", runeValue, i)
        w = width

Тук показваме подаването на стойност от вид rune на функция.

        examineRune(runeValue)
    }
}
func examineRune(r rune) {

Оградените в единични кавички стойности са буквална стойност на руна. Можем да сравним стойност от вида руна с буквална таква стойност направо.

    if r == 't' {
        fmt.Println("намерих U+0074 LATIN SMALL LETTER T")
    } else if r == 'ส' {
        fmt.Println("намерих U+0E2A THAI CHARACTER SO SUA")
    }
}
$ go run strings-and-runes.go
дължина в байтове: 18
e0 b8 aa e0 b8 a7 e0 b8 b1 e0 b8 aa e0 b8 94 e0 b8 b5
Брой знаци: 6
U+0E2A 'ส' започва от 0
U+0E27 'ว' започва от 3
U+0E31 'ั' започва от 6
U+0E2A 'ส' започва от 9
U+0E14 'ด' започва от 12
U+0E35 'ี' започва от 15
Използваме DecodeRuneInString
U+0E2A 'ส' започва от 0
намерих U+0E2A THAI CHARACTER SO SUA
U+0E27 'ว' започва от 3
U+0E31 'ั' започва от 6
U+0E2A 'ส' започва от 9
намерих U+0E2A THAI CHARACTER SO SUA
U+0E14 'ด' започва от 12
U+0E35 'ี' започва от 15

Следващ пример: .