Русские буквы
В лавке Эммы появились ценники по-русски: картофель и пюре из тыквы. Со словами вроде этих нужно быть осторожнее, и вот почему.
Компьютер хранит текст как последовательность байтов, небольших чисел от 0 до 255. Тексты в ваших программах, как и большинство текстов в мире, используют кодировку UTF-8: каждая буква английского алфавита, каждая цифра, пробел и знак препинания занимают один байт, а русская буква — два (как и буква с акцентом, например é или ñ; а некоторые символы, например эмодзи, занимают четыре). Строковые функции Lua вообще не знают о буквах: они считают и режут байты. В английском тексте один байт — это один символ, поэтому разницы не видно. С русскими буквами она видна, и в четырёх местах.
1. Длина. # и len считают байты. Чтобы посчитать символы, используйте utf8.len из библиотеки utf8, которая есть в Lua как раз для этого:
local word = "пюре"
print(#word, word:len(), utf8.len(word))Он выводит:
8 8 42. Заглавные и строчные буквы. upper и lower меняют только буквы от a до z; русские буквы остаются как есть:
print(("пюре"):upper())
print(("ПЮРЕ"):lower())Он выводит:
пюре
ПЮРЕКогда такое слово нужно написать заглавными буквами, пишите его так прямо в коде: "ПЮРЕ".
3. Разрезание. Позиции в sub — тоже байты. В слове "пюре" буква ю занимает позиции 3 и 4, поэтому первые две буквы — это sub(1, 4), а sub(1, 3) разрезает ю пополам:
local word = "пюре"
print(word:sub(1, 4))
print(word:sub(1, 3))
print(word:sub(1, 3) == "пю")Он выводит:
пю
п�
falseПоловина буквы — это вообще не символ, поэтому при выводе на её месте вы видите �. То же с reverse: он переставляет два байта каждой русской буквы, и ("пюре"):reverse() даёт �Ѐюѿ�, а не «ерюп». Резать безопасно по позициям, которые вам вернул find, потому что это тоже байты, и по отрицательным позициям, если конец строки написан по-английски.
4. Ширина. %-10s дополняет строку до 10 байт, поэтому слову не хватает по одному пробелу на каждую русскую букву:
print(("%-10s|"):format("carrots"))
print(("%-10s|"):format("пюре"))Он выводит:
carrots |
пюре |Исправить это можно, добавив пробелы самостоятельно: столько, какова ширина минус число символов. Это делает небольшая функция:
local function pad(text, width)
return text .. (" "):rep(width - utf8.len(text))
end
print(pad("carrots", 10) .. "|")
print(pad("пюре", 10) .. "|")Он выводит:
carrots |
пюре |Если строка длиннее ширины, разность получается отрицательной, и rep возвращает пустую строку, так что ничего не обрезается, как и с %-10s.
Шаблоны из следующего упражнения сталкиваются с той же проблемой: вы увидите это там. Общее правило: выводить, склеивать и искать строки с русскими буквами можно без опасений. Когда вы считаете, режете, выравниваете или меняете регистр, помните о байтах.
Задание
- 1
Запустите программу: две строки списка сдвинуты. Чтобы понять почему, добавьте над списком отдельный цикл, который выводит число символов в каждом названии, посчитанное с помощью
utf8.len:carrots: 7 картофель: 9 пюре из тыквы: 13 - 2
Теперь исправьте список. Вместо
%-14sдополните каждое название пробелами до 14 символов самостоятельно, с помощьюrepиutf8.len; вес по-прежнему форматируется через%6.2f. Список выровняется:carrots 12.50 kg картофель 2.00 kg пюре из тыквы 6.75 kg