Руны: буквы, а не байты
Байты — то, как строка лежит в памяти. Человеку нужны буквы: сколько их в названии, какая первая, где обрезать. Для этого в Go есть руна — одна буква (точнее, один знак Unicode), записанная числом.
range по строке идёт по буквам
Ситуация. Нужно пройти название по буквам. Цикл for i := 0; i < len(s); i++ идёт по байтам, а range из дня 16 со строкой работает иначе.
for i, r := range "Болт М8" {
fmt.Println(i, r, string(r))
}Что увидите.
0 1041 Б
2 1086 о
4 1083 л
6 1090 т
8 32
9 1052 М
11 56 8Как это читать. Семь проходов — по одному на букву, а не восемь и не двенадцать.
| Переменная | Что в ней |
|---|---|
i |
номер байта, с которого начинается буква. Растёт на 2 после русской буквы и на 1 после пробела и цифры |
r |
сама буква как число — руна. У Б номер 1041, у пробела 32. Тип — rune |
string(r) |
руна обратно в строку из одной буквы: её можно склеивать с другими строками |
range по строке сам разбирает UTF-8: даёт номер байта и букву, поэтому номера i идут с пропусками.
Попробуйте сейчас: что выведет range по строке.
Цель: предсказать номера
iдля строки из разных знаков и сдать ответ.1. Не запуская, ответьте, какие числа окажутся в начале строк вывода:
Код для чтения · разбираем, набирать не нужноfor i, r := range "Щ-7" { fmt.Println(i, string(r)) }2. Сдайте их через пробел:
▶ Выполните · выделенное замените своимcourse answer day19.q3 ЧИСЛО ЧИСЛО ЧИСЛОВместо трёх
ЧИСЛО— номера в том порядке, в каком их напечатает программа.3. Проверьте запуском в
scratch.Готово, когда:
course answerответилПринято: day19.q3 = …(пункт проверкиq3).
Длина в буквах и срез по буквам
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
name := "Болт М8"
fmt.Println(len(name))
fmt.Println(utf8.RuneCountInString(name))
fmt.Println(len([]rune(name)))
runes := []rune(name)
fmt.Println(runes)
fmt.Println(string(runes[:4]))
}Что увидите.
12
7
7
[1041 1086 1083 1090 32 1052 56]
БолтКак это читать.
| Запись | Что даёт | Когда брать |
|---|---|---|
len(name) |
12 — байты | размер в байтах: сколько места займёт в файле |
utf8.RuneCountInString(name) |
7 — буквы | нужна только длина в буквах |
[]rune(name) |
срез рун: каждая буква — отдельный элемент | нужно обращаться к буквам по номеру или резать по буквам |
len([]rune(name)) |
7 — буквы | то же, что RuneCountInString, если срез рун всё равно нужен |
string(runes[:4]) |
Болт — первые четыре буквы |
срез рун обратно в строку |
utf8 — пакет unicode/utf8 стандартной библиотеки: в import пишут полный путь "unicode/utf8", а в коде — последнее слово, utf8.
У []rune номера — номера букв: runes[4] — пробел. Разрезать букву пополам так нельзя.
Колонка по буквам
Колонка с первой страницы: название доводим ровно до ширины — короткое дописываем точками, длинное обрезаем и ставим ~. Обе операции — в буквах.
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func fitName(name string, width int) string {
n := utf8.RuneCountInString(name)
if n > width {
runes := []rune(name)
return string(runes[:width-1]) + "~"
}
return name + strings.Repeat(".", width-n)
}
func main() {
names := []string{"Bolt-M8", "Болт М8", "Шайба", "Анкер клиновой", "Кронштейн усиленный"}
for _, name := range names {
fmt.Println(fitName(name, 14) + "|")
}
}Что увидите.
Bolt-M8.......|
Болт М8.......|
Шайба.........|
Анкер клиновой|
Кронштейн уси~|Как это читать. В n — число букв. Если букв больше ширины, берём первые width-1 букв через срез рун и дописываем ~: вместе ровно width. Иначе дописываем width-n точек — меньше нуля не бывает: этот случай ушёл в if. В Анкер клиновой ровно четырнадцать букв: ни точек, ни обрезки.
Буквы умеет считать и fmt: ширина %-*s и точность %.*s — в буквах. Такая fitName тоже честная, способ — в course extra 19.
Попробуйте сейчас: буквы вместо байтов.
Цель: длина в байтах и в буквах у одной строки и описание
RuneCountInString.1. Наберите программу и запустите:
✎ Наберите в файлscratch/main.gopackage main import ( "fmt" "unicode/utf8" ) func main() { name := "Болт М8" fmt.Println(len(name)) fmt.Println(utf8.RuneCountInString(name)) fmt.Println(len([]rune(name))) runes := []rune(name) fmt.Println(runes) fmt.Println(string(runes[:4])) }▶ Выполнитеcd ~/gocourse/day19/scratch go run . go doc utf8.RuneCountInStringГотово, когда: программа напечатала
12,7,7, срез чисел иБолт, аgo doc— строкуfunc RuneCountInString(s string) (n int)(пункт проверкиt_godoc).
Попробуйте сейчас: починка fix1.
Цель: колонка в
fix1ровная по буквам на любых названиях; работа записана первым коммитом дня.1. Запустите заготовку — ширина колонки у вас своя:
▶ Выполнитеcd ~/gocourse/day19/fix1 go run .Русским названиям не хватает точек, а на
Анкер клиновойпрограмма падает сnegative Repeat count.2. Почините одну строку: точек — ширина минус число букв. Строка
"unicode/utf8"вimportв лимит правки не входит. Строкиwidth :=иnames :=не трогайте: проверка подставит в них другие значения.▶ Выполнитеgo run . diff ~/.course/orig/day19/fix1/main.go main.go3. Заведите репозиторий дня и сделайте первый коммит, как в днях 11–14:
▶ Выполнитеcd ~/gocourse/day19 git init git add . git commit -m "fix1: колонка по буквам"Готово, когда: все черты в выводе стоят одна под другой, программа не падает,
diffпоказывает одну изменённую строку, не считаяimport(пунктыfix1иfixsmall),git log --onelineпоказывает коммит.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
'Ё' (untyped rune constant 1025) overflows byte |
байт s[0] сравнили с буквой в одинарных кавычках: 'Ё' — руна, её номер 1025 в байт не помещается |
начало строки — strings.HasPrefix(s, "Ё"), четвёртая страница |
invalid operation: r + "!" (mismatched types rune and untyped string) |
руну из range склеили со строкой: руна — число, а не строка |
string(r) + "!" |
cannot use runes[:2] (value of type []rune) as string value in variable declaration |
срез рун положили в строковую переменную | обернуть: string(runes[:2]) |
undefined: utf8 |
нет import "unicode/utf8" |
добавить его в import |
"unicode/utf8" imported and not used |
пакет подключён, а ни одной функции из него не вызвано | убрать строку из import или вызвать функцию |
go vet: conversion from int to string yields a string of one rune, not a string of digits |
string(qty) с числом даёт не цифры, а знак с таким номером |
число в строку — strconv.Itoa(qty), как в дне 04 |
| обрезанное название на букву короче или длиннее ширины | обрезали width букв и ещё дописали ~ — или наоборот |
букв width-1, плюс ~ |
Дальше: как разобрать строку выгрузки на поля и сообщить о битой строке — course next