Brown paper packages tied up with strings, These are a few of my favorite things.
В прошлых уроках мы мельком касались строк, и вы уже должны себе представлять, что значение типа строка (string) есть некий текст, который можно, например, вывести на экран; или, говоря более строго, строка - это цепочка символов, каждый из которых представлен в памяти одним или двумя байтами. Строку в Lua можно записать одним из следующих образов:
Код
-- В одинарных кавычках str = 'Mickey Mouse'
-- В двойных кавычках str = "stack pile heap"
-- Между так называемыми [i]длинными скобками[/i]. -- Количество знаков равно '=' между двумя открывающими '[' -- и двумя закрывающими ']' знаками, вообще говоря, произвольно. str1 = [=[Stop spitting on the ground]=] str2 = [[Prime number]] str3 = [=====[Windsor McKee]=====]
Большой разницы между ними нет, однако в строке, заключённой в одинарные кавычки разрешено использовать символ ", в строке, заключённой в двойные кавычки разрешён символ ', а в строке в длинных скобках разрешены вообще любые символы, включая перенос строки. Таким образом, длинные скобки лучше всего подходят для записи больших форматированных кусков текста (в следующих примерах за знаком --> будет следовать текст, который выведется на экран).
Код
print "Mah good ol' pal" --> Mah good ol' pal
print '"This is ridiculous!" he said.' --> "This is ridiculous!" he said.
print [====[ O come, O come, Emmanuel, and ransom captive Israel that mourns in lonely exile here until the Son of God appear. ]====] -- Выведет именно то, что видно
Если очень нужно использовать в строке и ', и ", но не хочется писать длинные скобки (вас можно понять: длинные скобки действительно некрасиво выглядят), то желаемый символ экранируется знаком \ (обратная косая черта):
Код
print "Can use both \'s and \"s." --> Can use both 's and "s
-- Также можно экранировать перенос строки. -- Если убрать \ в строке ниже, программа не будет работать.
print 'Hi,\ Eugene.'
--> Hi, --> Eugene.
-- Если требуется записать в строке непосредственно символ \, -- он пишется два раза подряд. Это не очень удобно, но по-другому -- нельзя: либо так, либо используйте длинные скобки.
Записи типа \", \' и \\ называются экранирующими последовательностями или escape-последовательностями. Lua поддерживает и другие экранирующие последовательности, которые вы можете использовать в ваших строках:
Код
-- \n - переход на новую строку, как если бы вы -- нажали Enter в текстовом редакторе:
print "Great\nNews!" --> Great --> News!
-- \b - символ backspace. -- Работу этого символа можно рассмотреть как перемещение -- виртуального указателя на одну позицию влево, так что дальнейший -- текст выводится с новой позиции, замещая все последующие символы. -- Замещаемые символы фактически никуда не деваются, но на экран не -- выводятся.
print 'You are dumb\b\b\b\b\b nice' --> You are nice
-- \r - возврат каретки. -- Виртуальный указатель как бы перемещается в самое начало строки и -- последующие символы выводятся на экран оттуда.
print "Nobody hid the facts\rBush " --> Bush hid the facts
-- \t - табуляция, как если бы вы нажали Tab в -- текстовом редакторе. С его помощью удобно оформлять таблицы.
-- \v - вертиакальная табуляция, почти как если бы вы нажали -- клавишу "Вниз" в текстовом редакторе. -- Используется редко, удобна для вывода стихов Маяковского.
print "Listen,\v if stars are lit\v it means - there is someone who needs it" -- Стих выведется лесенкой.
-- \f - символ form feed. Аналогичен \v, но может не сработать -- (собственно, как и сам \v).
-- \a - звуковой сигнал (англ. alert). Предполагается, что попытка -- вывести этот символ на экран заставит встроенный динамик вашего компьютера -- издать короткий писк, но так как в современных ОС не принято разрешать -- звук из PC спикера, то \a в большинстве случаев просто не даст никакого -- эффекта.
-- \xOO - символ с заданным шестнадцатиричным кодом, где вместо ОО -- вы должны проставить некое двузначное шестнадцатиричное число. -- Чтобы узнать код желаемого символа, обратитесь к таблице кодов ASCII, -- в Интернете её довольно легко найти.
print '\x48\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C\x64' -- Тайная запись на языке хакеров!
-- \dOOO - символ с заданным десятичным кодом. То же самое, что и -- \xOO, только здесь код символа вводится десятичными цифрами. Диапазон -- допустимых чисел - от 0 до 255.
print "\d52\d50" -- Знаменитая программа, записаная строкой выше, на одном суперкомпьютере -- исполнялась миллион лет.
Если вам всё ещё не совсем понятна работа некоторых экранирующих последовательностей - ничего страшного, потому что вам, скорее всего, в жизни пригодится только \n. Учтите, что запись экранирующих последовательностей в строках, заключённых в длинные скобки, не даст никакого эффекта - экранирующие последовательности между длинными скобками игнорируются!
Код
print [[\n\t\v\b\a]]
--> \n\t\v\b\a
Что же, теперь-то мы умеем записать в строке всё, что угодно, но не слишком ли это скучно? В строках самое интересное не то, что мы можем в них записать, а то, что мы можем с ними сделать.
Одна из ключевых операций над строками - конкатенация, или присоединение. Обозначается в тексте программы знаком "..". Суть работы очень проста:
Код
-- Берём строку...
str1 = "mish"
-- И ещё одну...
str2 = "mash"
-- Соединяем...
str3 = str1 .. str2
-- [s]Варим три минуты[/s] Смотрим, что получилось:
print(str3) --> mishmash
С помощью оператора ".." к строке можно присоединять не только другие строки, но и числа, что даёт нам удобное средство форматирования вывода:
Код
kittens = 3 print("I have "..kittens.." kittens.") --> I have 3 kittens
-- Код ниже выведет на экран весьма длинную песенку. for i = 100, 1, -1 do print(i .. " bottles of beer on the wall.") print(i .. " bottles of beer.") print("Take one down and pass it around.") print((i-1) .. " bottles of beer on the wall.") end
print [==[ No more bottles of beer on the wall. No more bottles of beer. Go to the store, buy some more!]==]
Как видно, получить с помощью конкатенации красивый текст с числами гораздо проще, чем если бы вы передали print несколько аргументов через запятую: так бы получилось, что между числом и остальными знаками слишком большой пробел.
Ещё одна полезная операция над строками - сравнение, осуществляемая, как и сравнение чисел, с помощью операторов >, <, ==, ~=, >= и <=. Тут может возникнуть замешательство: если с равенством/неравенством строк всё ясно (в равных строках последовательность символов совпадает), то как проверить, какая строка больше? По длине? Или по сумме кодов символов? Но в первом случае будут считаться равными строки "Jesus" и "Satan", а во втором - "spoon" и "snoop". Непростая задача, выходит!
Понятно, что строке, в отличие от числа, нельзя поставить в соответсвие какое-либо значение, чтобы можно было однозначно, взглянув на него, сказать: какая строка больше, а какая меньше. Поэтому математики, вдохновляясь великими составителями словарей, придумали лексикографическое сравнение. Словари тут притом, что логика лексикографического сравнения примерно такая: большей считается та строка, которая относительно другой стояла бы ниже, если бы их вносили, например, в алфавитный указатель. Более строгое определение порядка сравнения из Википедии:
Слово a предшествует слову b (a < b), если первые m символов совпадают, а m + 1 символ слова a меньше m + 1 символа слова b.
А вот символы запросто сравниваются по величине ASCII-кода, так что сравнение строк теперь полностью определено. Примеры:
print ("bit" < "bitter") --> true -- Если символы в неравнодлинных строках совпадают, меньшей считается -- более короткая.
print ("Satan" == "Satan") --> true
print ("Jesus" ~= "Satan") --> true
Лексикографическое сравнение в основном используется для сортировки строк - именно благодаря нему вы можете в интерактивном телефонном справочнике отсортировать номера по фамилиям и быстро найти нужного человека.
Большинство оставшихся вохможных манипуляций со строками в Lua возложено на модуль string: он, как и модуль math, содержит некоторые функции, обращаться к которым необходимо с предшествующим именем модуля, отделённым точкой. Существование модуля string помимо всего прочего обязывает вас не называть ни одну переменную именем string, иначе доступ к модулю для программы будет потерян ценой создания одной маленькой переменной. А ведь, казалось бы, так хочется!
Многие из функций для работы со строками весьма сложны для понимания и использования и не так часто необходимы (например, string.gsub и string.match, а также string.format), поэтому нашего краткого обзора на них ни в коем случае не хватит, но я их всё равно упоминаю здесь, чтобы разжечь ваше любопытство и подтолкнуть вас к самостоятельному изучению их работы с помощью официального руководства, так как в практике программирования на Lua такие знания вполне могут пригодиться. А сейчас мы вкратце рассмотрим простейшие функции модуля string:
1. string.len(<строка>) - возвращает длину строки. Пример работы:
Код
string.len("") -- Пустая строка, вернёт 0.
string.len("Yes we can") -- Вернёт 10.
Длина строки в Lua измеряется в байтах, а не в символах, что на самом деле прискорбно. Между байтом и символом гарантированно нет никакой разницы, если вы используете только символы ASCII, и, в частности, не употребляете русских букв.
Код
string.len("Зело дивно") -- В кодировке UTF-8 вернёт 19, хоть нам и хотелось бы видеть 10. Зело дивно! -- Дело в том, что один русский символ представляется двумя байтами, а -- не одним. -- Такое поведение может обескуражить программистов на Python, привыкших, -- что подсчитываются именно действительные символы, в Python подобная -- функция возвращает 10.
Другой случай, способный вызвать затруднение - экранирующие последовательности. Хоть вы и записываете их двумя символами, в памяти они, скорее всего, будут представлены одним байтом.
Код
string.len("\a\t\b") -- Возвратит 3.
Исключительное положение в этом смысле у последовательности \n. Дело в том, что перенос строк на разных платформах представлен разными байтами. В Linux это ASCII-символ подача строки с кодом 0xA, в компиьютерах Apple Macintosh - символ возврат каретки с кодом 0xD, а в ОС Windows - это сразу два символа 0xD и 0xA, следующие друг за другом, так что длина строки с последовательностью \n в ней будет иметь разное значение в зависимости от компьютера, на котором запускается программа:
Код
string.len("what a\nhorrible\nnight to\nhave a\ncurse.") -- Возвратит 39 в Linux и 43 в Windows.
2. string.lower(<строка>) и string.upper(<строка>) - возвращают новую строку, в которой все буквы исходной заменены соответсвенно на строчные либо на заглавные. Заметьте, что исходная строка при этом остаётся в сохранности.
Код
string.upper("A quick brown fox jumps over the lazy dog.") -- Возвращает: "A QUICK BROWN FOX JUMPS OVER THE LAZY DOG."
string.lower("The NASDA System Was Developed To Protect US") -- "the nasda system was developed to protect us"
Учтите, что эти функции обычно работают только с латинскими буквами, так что не стоит полагаться на них при работе с русскоязычными символами.
string.toupper и string.tolower удобно применять, если вы хотите сравнить две строки без учёта регистра: просто приведите их к одному регистру и затем производите сравнение.
Код
-- comparestrings.lua --
function compare(str1, str2) return (string.toupper(str1) == string.toupper(str2)) end
print( copmpare("StrANgER", "sTRanGer")) --> true
3. string.reverse(<строка>) - возвращает строку, состоящую из последовательности символов исходной строки, расположенных в обратном порядке. Грубо говоря, string.reverse "переворачивает" строку.
Опять же, так как многобайтовые символы Lua обрабатывает некорректно, использование русских букв в переворачиваемой строке может привести к непредсказуемым результатам. Также в Windows под вопросом корректное переворачивание строк, содержащих символ \n. Следовательно, string.reverse стоит применять с аккуратностью: не думайте, что ("булка" == string.reverse("аклуб")) обязательно возвратит true.
4. string.rep(<исходня строка>, <N>, [строка-разделитель]) - присоединяет друг к другу N копий исходной строки, разделённых строкой-разделителем, если последняя дана.
--Вызов без разделителя: print( string.rep(str, 4) ) --> WOBBLEWOBBLEWOBBLEWOBBLE
Полезность функции string.rep неочевидна, но мне за всю жизнь удалось пару раз успешно применить её. Правда, лучше бы я этого не делал.
5. string.sub(<строка>, <индекс начального символа>, [индекс конечного символа]) - возвращает подстроку, определяемую заданными индексами. Упрощённо говоря, так можно обрезать строку.
Код
str = "John Emmanuel Doe"
-- Указав только один индекс, мы велим функции обрезать строку слева до -- символа с этим индексом:
string.sub(str, 6) -- Возвратит "Emmanuel Doe"
-- Если указанный индекс меньше нуля, то мы указываем оставить только -- некоторое число последних символов, а остальное - обрезать:
string.sub(str, -3) -- Возвратит "Doe"
-- Если указать два индекса, символы, заключённые в позициях между ними, -- будут оставлены, оставшаяся часть строки - отброшена:
string.sub(str, 6, 13) -- Возвратит "Emmanuel"
6. string.byte(<строка>, [индекс начального символа], [индекс конечного символа]) - возвращает численные значения байтов, составляющих строку. Индексы возвращаемых символов указываются точно таким же образом, как в функции string.sub, только string.byte можно вообще не передавать никаких индексов, и тогда будет возвращено значение только первого байта. Так как строки бывают достаточно длинными (средняя строка редко бывает короче десяти символов), возвращаемых значений может быть очень много, поэтому разумнее собирать их не в переменные, а в таблицу, которую мы рассмотрим в следующем уроке.
Код
str = "John Emmanuel Doe"
string.byte(str, 1, 4) -- Возвратит числа: 74, 111, 104, 110 - числовые -- коды символов 'J', 'o', 'h', 'n'
string.byte(str) -- Возвратит 74, код символа 'J'
-- Получаем полное представление строки в кодах ASCII.
7. string.char([число1, число2, ...]) - "собирает" строку из переданной последовательности кодов. Удобна в связке string.byte, когда вы с её помощью разбиваете строку на коды, как-то обрабатываете их, а потом собираете обратно в строку. Учтите, что числа должны находиться в пределах от 0 до 255, иначе функция завершит работу с ошибкой.
--! string(1024, 768, 111) --! Вызовет ошибку, работать не будет.
Напоследок хочется дать вам какую-нибудь интересную программу, включающую в себя манипуляции со строками, но - беда! - занятных задач на обработку строк очень мало, классическая задача о палиндроме в общем виде на Lua, по крайней мере, с помощью известных нам средств, решается слишком громоздко, а в частном случае - без пробелов, пунктуации и специальных символов - слишком тривиально и, следовательно, скучно (но, может, вы попробуете? Условие такое: напишите функцию, которая проверяет, читается ли фраза слева направо точно так же, как и справа налево. Для упражнения точно пригодятся string.reverse и string.upper или string.lower).
Вместо этого я расскажу вам о преобразованиях между строками и другими значениями и как иногда полезно применять эти преобразования.
Допустим, перед вами стоит задача: найти число, которое получается при перестановке цифр заданного числа в обратном порядке. Традиционное решение предполагает множество последовательных целочисленных делений, но Lua предлагает более элегантный подход, буквально в три действия:
Код
-- reversenumber.lua --
function reversenumber(num) local str = tostring(num) -- преобразуем число в строку str = string.reverse(str) -- переворачиваем строку return tonumber(str) -- преобразуем обратно в число и возвращаем end
Ключевыми функциями в работе reversenumber являются tonumber и tostring.
1. tonumber принимает один или два аргумента, первый из которых - строка, содержащая число, а второй, опциональный - основание системы счисления, в которой оно записано (от 2 до 36). Последнее по умолчанию считается равным 10. Системы счисления с основанием большим, чем 10, образуются подобно шестнадцатиричной: недостающие цифры записываются в виде латинских букв. Если строка представляет собой некорректно записанное число, tonumber возвращает nil.
2. tostring работает в обратном направлении: она возвращает строковое представление переданного в качестве параметра значения. Причём значением может быть:
Код
-- Число tostring(128) -- Вернёт "128"
-- Логическое значение tostring(1 > 0) -- "true"
-- nil tostring(nil) -- "nil" (строка "nil", а не значение nil)
-- Функция tostring(tostring) -- "function: 0x418a20"
Поздравляю! Теперь вы стали настоящим волшебником строк, и можете творить с ними почти всё, что вам вздумается. Веселитесь, потому что, как говорится:
Код
print(string.sub(_VERSION, 1, 3).." is "..string.sub(tostring(print), 1, 3))
Резюме: 1. Мы узнали, как записываются строки в тексте программы. 2. Узнали про экранирующие последовательности, как и для чего их применять. 3. Узнали про лексикографическое сравнение. 4. Выяснили, почему Lua плохо работает с русскими символами, и в каких ситуациях их следует избегать. 5. Узнали о целом ряде полезных функций модуля string. 6. Научились превращать числа и строки друг в друга с помощью функций tonumber и tostring.
Упражнения: 1. Взгляните на функцию reversenumber из reversenumber.lua. При каких значениях параметра она не будет работать? 2. Как вы думаете, как можно написать свою версию string.sub с помощью string.char и string.byte? 3. Напишите программу, которая сосчитывает, сколько раз в строке встречается символ 'a'.
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:
Игровые объявления и предложения:
Если вас заинтересовал материал «Lua для всей семьи. Урок 5: работа со строками», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела.
Предлагаются такие схожие материалы:
Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.
function a_count(str) local count=0 for i=1, string.len(str) do if string.byte(str,i)==string.byte('a') then count=count+1 end end print("'a' repeats here "..count.." times") end
print('How many times does letter \'a\' repeat in this sentence?') a_count('How many times does letter \'a\' repeat in this sentence?')
Божественная статья. Она просто необходима людям. Помойму лучше было бы что-то пописать про love или даже INSTEAD, но это, тут ведь по сути все то, что можно прочитать самому в любом яп. В интернете куча работ со строками. Про прошлые статьи вообще молчу.
Скорее всего, сделаю, потому что вещь действительно порой полезная.
Однако, строго говоря, в Lua нет поддержки настоящих регулярных выражений: есть их урезанная версия под названием patterns (шаблоны). Разработчики языка прокомментировали это тем, что реализация полноценных регулярных выражений потребовала бы вдвое больше строк кода, чем в Lua уже есть.
Добавлять комментарии могут только зарегистрированные пользователи. [ Регистрация | Вход ]