День 19 — глубже
Всё здесь необязательно. Материалы на английском помечены.
Документация и статья (англ.)
То же открывается в терминале через go doc: go doc utf8, go doc strings.Fields, go doc os.ReadFile, go doc bufio.Scanner.
- Strings, bytes, runes and characters in Go — https://go.dev/blog/strings Статья одного из авторов языка ровно про сегодняшнюю тему: почему строка — байты, что такое руна и почему «буква» — понятие сложнее, чем кажется.
- unicode/utf8 — https://pkg.go.dev/unicode/utf8 Кроме
RuneCountInString:ValidString— целы ли буквы в строке,RuneLen— сколько байтов займёт руна. - strings — https://pkg.go.dev/strings Всё, что режет, чистит и ищет.
strings.Cutзнаком по дню 12.
fmt считает буквы сам
Ширина в Printf — %-10s — считается в буквах, а не в байтах. Точность %.3s — сколько букв оставить.
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func main() {
a := "й"
b := "и\u0306"
fmt.Println(a, b, a == b)
fmt.Println(len(a), utf8.RuneCountInString(a))
fmt.Println(len(b), utf8.RuneCountInString(b))
fmt.Printf("[%-10s]\n", "Болт")
fmt.Printf("[%.3s]\n", "Болт М8")
fmt.Printf("%q\n", strings.Split("TK-1;Болт\r\nTK-2;Шайба\r\n", "\n"))
var sb strings.Builder
sb.WriteString("Болт")
sb.WriteString(strings.Repeat(".", 3))
fmt.Println(sb.String(), sb.Len())
}й й false
2 1
4 2
[Болт ]
[Бол]
["TK-1;Болт\r" "TK-2;Шайба\r" ""]
Болт... 11Ширину и точность можно передать аргументом — %-*s и %.*s. Тогда fitName обходится без utf8 и []rune: точность отрезает лишние буквы, а разница в байтах между строкой, добитой пробелами, и самим названием — ровно число недостающих букв.
package main
import (
"fmt"
"strings"
)
func fitName(name string, width int) string {
if fmt.Sprintf("%.*s", width, name) != name {
return fmt.Sprintf("%.*s", width-1, name) + "~"
}
pad := fmt.Sprintf("%-*s", width, name)
return name + strings.Repeat(".", len(pad)-len(name))
}
func main() {
for _, name := range []string{"Болт М8", "Анкер клиновой", "Кронштейн усиленный"} {
fmt.Println(fitName(name, 14) + "|")
}
}Болт М8.......|
Анкер клиновой|
Кронштейн уси~|Такое решение задания засчитывается: колонка ровная по буквам. На страницах fitName написана через руны, чтобы было видно, что fmt делает внутри.
Одна буква — две руны
Первые три строки вывода первой программы. й можно записать одной руной (номер 1081) или двумя: и и отдельный знак «кратка» — в переменной b он записан как \u0306, так в строке Go записывают знак по его номеру. На экране одно и то же, а для Go это разные строки: a == b — false, у второй две руны и четыре байта. Такое приходит из файлов, созданных на других системах. Для отчётов дня это не важно; для поиска по названиям — важно, и решается пакетом golang.org/x/text/unicode/norm (не из стандартной библиотеки).
Выгрузки из Windows
Строка %q выше: файлы, сохранённые в Windows, кончают строки не \n, а \r\n. После Split по "\n" в конце каждой строки остаётся \r — невидимый в терминале. strings.TrimSpace его убирает, поэтому parseLine дня с такими файлами справляется. bufio.Scanner убирает \r сам.
strings.Builder
Последняя строка вывода выше. Когда строку собирают из многих кусков в цикле, + каждый раз создаёт новую строку. strings.Builder копит куски и отдаёт строку один раз — sb.String(). sb.Len() — длина в байтах: 11, а букв семь.
encoding/csv
В стандартной библиотеке есть пакет, который разбирает такие файлы сам: понимает поля в кавычках, внутри которых может стоять разделитель.
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
f, err := os.Open("orders.csv")
if err != nil {
fmt.Fprintln(os.Stderr, "ошибка:", err)
os.Exit(1)
}
r := csv.NewReader(f)
r.Comma = ';'
rows, err := r.ReadAll()
f.Close()
fmt.Println(len(rows), err)
for _, row := range rows {
fmt.Println(len(row), row)
}
}Файл orders.csv для опыта — во второй строке название в кавычках с точкой с запятой внутри, третья строка битая:
TK-101;Болт М8;12;1500
TK-102;"Шайба; кровельная";100;90
TK-103;Гайка0 record on line 3: wrong number of fieldsReadAll на первой битой строке останавливается и возвращает ошибку — ни одной строки не отдаёт. Чтобы идти дальше, как требует отчёт дня, читают по одной записи методом r.Read() в цикле и решают, что делать с ошибкой каждой. Разберите go doc csv.Reader.Read: когда он возвращает ошибку, а когда запись вместе с ошибкой.
Попробуйте сейчас: csv против Split.
Цель: увидеть, чем поле в кавычках отличается для
encoding/csvи дляstrings.Split.Создайте файл из трёх строк выше в
scratch, наберите программу и запустите. Потом уберите третью строку и запустите ещё раз:▶ Выполнитеcd ~/gocourse/day19/scratch go run .Готово, когда: с битой строкой программа напечатала
0и ошибку, без неё — две записи, и во второй полеШайба; кровельнаяцеликом. Проверка дня этот опыт не сверяет.