Перейти к содержимому
Zer0Galaxy

Шаблоны

Рекомендуемые сообщения

- Я умею читать чужие мысли!

- А я умею читать чужие регулярные выражения!

- Ты победил

 

Люди, которые программируют приложения, связанные с обменом по сети rednet или сохранением на диск, часто сталкиваются с необходимостью преобразования массива данных различных типов в строку и последующим его восстановлением. Под массивом я подразумеваю не отдельно взятую таблицу, а несколько переменных, которые нужно упаковать в одну строку. Самый простой но не самый лучший способ решить эту задачу - поместить все необходимые данные в таблицу и воспользоваться функциями serialize, unserialize из библиотеки textutilse. О недостатках такого способа я уже когда-то говорил и сегодня повторяться не буду. А расскажу о том, как эту проблему решаю я. Предположим нам необходимо упаковать в одну строку значения нескольких переменных (x, y, z). Причем строка должна содержать не только значения переменных, но и информацию о том, что это за переменные. Думаю с упаковкой ни у кого больших проблем не возникнет. Сделать это можно, к примеру, так: s='x='..x..' y='..y..' z='..z В результате мы получим строку, которая содержит имена наших переменных и их значения, отделенные от имен знаками равенства. 'x=10 y=11 z=12' Вроде просто. Но как из этой строки извлечь заложенную в нее информацию, а именно имена и значения? Конечно, можно написать большой и сложный алгоритм посимвольного разбора строки. К счастью, вся скучная работа уже сделана за нас и поставленную задачу мы можем выполнить буквально в одно действие. Я говорю о функциях match и gmatch из библиотеки string. Назначение этих функций - поиск шаблона в строке. К примеру, в строке s, которую я привел выше, я хочу найти подстроку 'y=11'. Для этого я вызываю функцию s:match('y=11') которая и вернет мне искомую подстроку, если она там есть. Параметр 'y=11' будем называть шаблоном. Если шаблон в строке не найден, функция вернет nil. Правда здорово?

Изменено пользователем Zer0Galaxy

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вы наверняка подумали, а зачем искать в строке значение переменной y, если для поиска нужно знать значение этой переменной? Как быть если значение y заранее не известно? Чаще всего так и происходит. Тогда в шаблон вместо цифр необходимо подставить специальные символы. В нашем случае это %d.

 

s:match('y=%d%d')

 

Спецсимволы в шаблоне заменяют собой другие символы. Спецсимволов припоминаю несколько:

 

%a - заменяют все буквы

%d - все цифры

%w - буквы и цифры

%p - знаки препинания

%s - пробел

.(точка) - любой символ

%. - символ "точка"

 

Спецсимволов чуть больше, но остальные не столь актуальны.

 

Надеюсь, понятно, что функция s:match('y=%d%d') будет искать в строке s подстроку состоящую из символов 'y=' и еще каких то двух цифр. Именно эту подстроку она и вернет. Если найдет.

А вдруг мы не знаем из скольки символов состоит значение переменной y. В этом случае на помощь приходят модификаторы. Модификаторы это такие спецсимволы, которые изменяют количество символа за которым стоят в шаблоне.

 

Вот такие я знаю модификаторы:

 

+ - соответствует одному или более повторений символа;

* - соответствует нулю или более повторений;

- - соответствует нулю или более повторений. В отличии от модификатора * возвращает строку минимально возможной длины, но так, что бы она соответствовала шаблону;

? - соответствует нулю или одному символу.

 

Если мы знаем, что значение переменной y состоит из какого то количества цифр (но не менее одной), следует применить модификатор +

 

s:match('y=%d+')

 

Эта функция будет искать в строке s подстроку состоящую из символов 'y=' и какого то количества цифр. Причем вернет подстроку максимально возможной длины, т.е. 'y=' и все цифры следующие за знаком равенства.

Продолжение следует ...

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Символы и спецсимволы в шаблоне можно объединять в наборы (сеты). Для этого несколько символов заключаются в квадратные скобки. Набор замещает собой один из символов, входящих в его состав. Например, вот такой набор [%d%p] замещает цифру или знак препинания. А такой [_%w] - букву, цифру или символ подчеркивания. Порядок символов, в котором они идут в наборе значения не имеет.

 

Теперь мы можем найти в строке значение переменной y даже если оно будет отрицательным или сопровождаться знаком +

 

s:match('y=[%+%-]?%d+')

 

Знаки % перед + и - говорят о том, что это не спецсимволы, а обычные + и -

Знак ? после набора указывает на то, что + или - могут отсутствовать.

 

А вот такой шаблон

 

s:match('y=[%+%-%d]%d*%.?%d*')

 

позволит найти число в формате с десятичной точкой.

Если набор начинается с символа ^ , то такой набор интерпретируется как "всё кроме указанных символов".

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Шаблоны, которые я рассматривал до сих пор, возвращали строку, требующую дополнительной обработки. В самом деле, если функция match вернула строку 'y=11', то из этой строки необходимо выделить символы, соответствующие значению переменной. Например, вызвать функцию match еще раз. А нельзя ли обойтись одним вызовом? Оказывается можно. Делается это при помощи структуры, называемой захват (capture). Захват оформляется в виде группы символов, заключенных в круглые скобки. Если шаблон содержит захват, то он возвращает не всю совпавшую подстроку, а только захваченное значение, т.е. символы, соответствующие символам захвата.

 

Пример: s:match('y=(%d+)')

вернет строку не 'y=11', а '11'

 

Обращаю внимание, match всегда возвращает строку, даже если вы ищите число. Так что от преобразования типа tonumber никуда не деться.

 

Захватов в шаблоне может быть несколько. В этом случае match возвращает столько значений сколько захватов присутствует в шаблоне. Это позволяет искать сразу несколько значений в строке за один раз. Например:

 

s='x=10 y=11 z=12'

x,y,z=s:match('x=(%d+)%s*y=(%d+)%s*z=(%d+)')

 

Тут нужно быть внимательным. Во-первых, не забываем про разделители, если они есть (в примере разделителями являются символы пробел - %s). Во-вторых, следует помнить, что шаблон стреляет только тогда, когда совпал целиком. Т.е. если из трех захватов, приведенных в примере, найдены два, а третий не найден, результатом работы функции будет nil и все три переменные x,y,z будут не определены. Поэтому, если вы не уверены, что в исходной строке присутствуют все искомые значения, воспользуйтесь тремя разными поисками. Или же функцией gmatch, о которой я расскажу чуть позже.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

попробуй написать свою функцию типа match только быстрее

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

попробуй написать свою функцию типа match только быстрее

На lua вряд ли получится. Match, всё же на джаве писано.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Уточню перечень спецсимволов, используемых в шаблонах

. (точка) - заменяет любой символ.

%a --- все буквы.

%c --- все управляющие символы. (Символы с кодом от 0 до 31, которые не имеют графического представления)

%d --- все цифры.

%l --- все строчные буквы.

%p --- все знаки препинания.

%s --- все пробелы (символы с кодом 9, 10, 12, 13, 32).

%u --- все заглавные буквы.

%w --- все алфавитно-цифровые символы (буквы и цифры).

%x --- все шестнадцатеричные цифры (цифры и буквы a-f, A-F).

%z --- символ с кодом 0.

 

Использование заглавной буквы в качестве спецсимвола меняет смысл на противоположный. Например %A - все символы кроме букв, %S - все печатные символы

 

Так же хочу добавить еще два спецсимвола ^ и $. Если шаблон начинается с символа ^ или заканчивается символом $, то искаться такой шаблон будет соответственно в начале или в конце исходной строки.

 

Несколько примеров использования шаблонов:

'[%+%-]?%d+' - ищет целое число в десятичном формате

'0x%x+' - число в шестнадцатеричном формате

s= s:match('^%s*(.*)%s*$') - удаляет из строки начальные и конечные пробелы

key, val = s:match('([%a_][%w_]*)%s*=%s*(%S+)') - разбирает конструкцию типа key = val, причем имя key должно содержать буквы, цифры или символ подчеркивания и начинаться с буквы или подчеркивания, а val может содержать любые символы кроме пробела.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вернемся к строке s='x=10 y=11 z=12', с которой мы начинали знакомство с шаблонами. Мы видели, что извлечь из нее значения переменных можно так:

x,y,z=s:match('x=(%d+)%s*y=(%d+)%s*z=(%d+)')
или так:

x=s:match('x=(%d+)')
y=s:match('y=(%d+)')
z=s:match('z=(%d+)')
Но как быть, если количество, содержащихся в строке переменных очень большое, скажем, несколько тысяч? Неужели их все придется прописывать вручную? Нельзя ли это как то автоматизировать? Последний пример из предыдущего поста подсказывает, что можно. Мы ведь можем захватывать не только значения, но и имена переменных. А полученные значения размещать в таблице где имя переменной будет именем элемента таблицы. Для простоты будем считать, что имена могут состоять из букв или цифр, а значения только из цифр. Пропишем шаблон для поиска одной переменной и заключим его в цикл для поиска остальных.

s='x=10 y=11 z=12' -- исходная строка
t={}  -- таблица, в которой будем хранить результат
for i=1,3 do
  key, val=s:match('(%w+)=(%d+)')  -- извлекаем имя и значение
  t[key]=tonumber(val)  -- помещаем результат в таблицу
end
for key, val in pairs(t) do  -- выводим результат
  print(key,'=',val)
end
Приведенный пример кода загружает из исходной строки имена и значения переменной в таблицу t. Однако, если мы попытаемся этот код выполнить, то увидим, что в результате таблица t будет содержать только одно значение x=10. Произошло так потому, что функция match всегда начинает поиск совпадений с начала строки. Всегда, если ей не указать с какого места нужно начинать поиск. Оказывается match имеет еще один необязательный параметр, указывающий с какой позиции строки следует начинать поиск. Его можно узнать вставив в шаблон пустой захват (). Такой захват вернет позицию символа в строке, следующего за найденной подстрокой.

s='x=10 y=11 z=12' -- исходная строка
t={}  -- таблица, в которой будем хранить результат
n=1  -- начинаем поиск с начала строки
for i=1,3 do
  key, val, n=s:match('(%w+)=(%d+)()',n)  -- извлекаем имя и значение
-- пустой захват ставим в конце шаблона, чтобы от вернул позицию символа, следующего за найденной подстрокой
  t[key]=tonumber(val)  -- помещаем результат в таблицу
end
for key, val in pairs(t) do
  print(key,'=',val)
end
После внесенных изменений код должен работать более корректно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Но лично я в подобных случаях не заморачиваюсь с пустыми захватами, а использую функцию gmatch. Она возвращает функцию-итератор, которая при каждом очередном вызове ищет следующее совпадение шаблона в строке. Функция возвращает функцию! Звучит ужасно, но выглядит всё не так страшно, если применить ее в операторе for in

s='x=10 y=11 z=12' -- исходная строка
t={}  -- таблица, в которой будем хранить результат
for key, val in s:gmatch('(%w+)=(%d+)') do
  t[key]=tonumber(val)  -- помещаем результат в таблицу
end
for key, val in pairs(t) do
  print(key,'=',val)
end
Оператор for in выполнится столько раз, сколько раз будет найден шаблон '(%w+)=(%d+)' в исходной строке. Причем переменные key и val всякий раз будут принимать очередные захваченные значения - имя и значение переменной.

Думаю, теперь мы готовы к тому, что бы написать собственные аналоги функций serialize и unserialize.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ух, наконец-то я осилил прочтение и понимание этого материала...

Спасибо большое, Зеро, написано просто и понятно на столько, на сколько это возможно)

Я прям чувствую, как мой лвл апнулся))

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Кстати, когда я узнал о таких шаблонах наворотил такую шнягу:
 

    arg = { ... }
     
    local function man(algo)
     for v in string.gmatch(algo, "%a") do
      if (v == "l") then turtle.turnLeft()
      elseif (v == "r") then turtle.turnRight()
      elseif (v == "f") then turtle.forward()
      elseif (v == "u") then turtle.up()
      elseif (v == "d") then turtle.down()
      elseif (v == "b") then turtle.back()
      elseif (v == "p") then print("lol")
      else print("Impossible algorithm.") return false
      end
     
     end
     
     return true
    end
     
    man(tostring(arg[1]))


Мне такая штука показалась достаточно удобна. Просто задаем алгоритм телодвижений черепашки одной строкой, например fffllffrfffb.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Можно задавать количество повторений в виде числа. Скажем так:

local function man(algo)
     for v,d in string.gmatch(algo, "(%a)(%d*)") do
     d=tonumber(d) or 1
     for i=1,d do
      if (v == "l") then turtle.turnLeft()
      elseif (v == "r") then turtle.turnRight()
      elseif (v == "f") then turtle.forward()
      elseif (v == "u") then turtle.up()
      elseif (v == "d") then turtle.down()
      elseif (v == "b") then turtle.back()
      elseif (v == "p") then print("lol")
      else print("Impossible algorithm.") return false
      end
     end
     end
     return true
    end

Тогда алгоритм будет выглядеть как-то так "f30u15rf10".

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я использую подобную фишку в своих боевых черепашках, но не на выполнение. Скажите, а как прервать такой алгоритм?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ну Ctrl+T можно что угодно прервать (кроме os.pullEventRaw(), если terminate не обработан).

Ctrl+T это очень не гибко. Еще варинаты есть? Кроме флагов, типа while run do или if run then.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Хочешь гибкости? Тогда нужно решить по какому событию прерывать алгоритм (и нужно ли вообще). Вот это событие и отлавливать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Хочешь гибкости? Тогда нужно решить по какому событию прерывать алгоритм (и нужно ли вообще). Вот это событие и отлавливать.

Я сделал таблицу функций, которая является очередью, и запихал туда turtle.forward() и т.д., в отдельном потоке выполняю эту очередь, если надо прервать выполнение - очищаю таблицу.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...