Несколько Слов О Захватах И Регулярных Выражениях, исключительно для разработчиков |
Здравствуйте, гость ( Вход | Регистрация )
Несколько Слов О Захватах И Регулярных Выражениях, исключительно для разработчиков |
7.8.2008, 15:54
Сообщение
#1
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Итак, я думаю многие сталкивались в скриптах со всевозможными "захватами" и регулярными выражениями.
Почти в каждом скрипте они встречаются. Сразу же начну с нескольких примеров: Код local s, e, a, b, c, d = string.find("11.12.13.14", "^(%d+)%.(%d+)%.(%d+)%.(%d+)$") local _, _, a, b, c, d = string.find("11.12.13.14 ", "^(%d+)%.(%d+)%.(%d+)%.(%d+)$") local _, _, a, b, c, d = string.find("10 11.12.13.14 15", "(%d+)%.(%d+)%.(%d+)%.(%d+)") local _, _, a = string.find("Hello world", "%S+%s(%S+)") local _, _, a, b, c = string.find("<User> !massmsg hello world", "%b<>%s+(%S)(%S+)%s*(.*)") Можете ли вы сказать, что тут выполняется??? Если не можете, то слушайте дальше... Сначала расскажу, что такое захват, и что такое регулярное выражение. Формально, захват - это нахождение определённый последовательности символов и помещение их в переменную. Например, возьмём один из примеров, который я написал сверху: Код local s, e, a, b, c, d = string.find("11.12.13.14", "^(%d+)%.(%d+)%.(%d+)%.(%d+)$") этот пример - типичный пример захвата. Функция string.find "захватывает" из строки "11.12.13.14" определённую последовательность символов. Выражение "^(%d+)%.(%d+)%.(%d+)%.(%d+)$", называется РЕГУЛЯРНЫМ ВЫРАЖЕНИЕМ. в скобках указываются захваты, то есть тут 4 захвата. Из этой функции данные у нас заносятся в 6 локальных переменных, а именно s, e, a, b, c, d. В переменную s (s от слова start) заносится начальная позиция захвата, равная 1. У нас строка "11.12.13.14" имеет 11 символов, следовательно 11 позиций. В переменную e (e от слова end) заносится конечная позиция захвата, равная 11. Обычно, в скриптах эти позиции не нужны, тогда, обычно, переменные обозначают одним именем (для сохранения памяти), обычно, обозначают как "_" (чтобы эта переменная была не столь заметна). Далее в переменные a, b, c, d, соответственно, заносятся захваты (то, что захватывается скобками). Разберём регулярное выражение: "^(%d+)%.(%d+)%.(%d+)%.(%d+)$". Символ ^ означает, что следующее действие в регулярном выражении должно быть в начале строки. У нас следующее за символом ^ действие - это захват (%d+), следовательно, захват у нас должен выполняться обязательно с начала строки. Сейчас объясню более понятно на примерах. Возьмём два регулярных выражения "^(%d+)" и "(%d+)". Теперь с помощью действий: Код local s, e, a1 = string.find("11.12.13.14", "^(%d+)") local s, e, a2 = string.find("11.12.13.14", "(%d+)") в переменные a1 и a2 запишутся данные: a1="11" и a2="11". Теперь изменим исходную строку (дописав вначале строки пробел): Код local s, e, a1 = string.find(" 11.12.13.14", "^(%d+)") local s, e, a2 = string.find(" 11.12.13.14", "(%d+)") смотрим, что в переменных: a1=nil и a2="11". Теперь я думаю вам почти понятно в чем вся фишка, осталось только объяснить, что такое (%d+). (%d+) - это захват числа. Во второй строке вначале стоял пробел, поэтому выражение ^(%d+) не могло захватить число вначале строки, поэтому и функция string.find вернула nil, и записала в переменные s=nil, e=nil, a1=nil, а функция string.find(" 11.12.13.14", "(%d+)") записала в переменные s=1, e=2, a2="11". Заметьте, что хоть мы и захватываем число, но тем не менее оно является строковым числом "11", а не просто 11 !!!!!!!!. Аналогично дело обстоит с символом $, но только для конца строки. Таким образом, мы рассмотрели 3 первых примера, которые я предложил вначале. В первом примере: s=1, e=11, a="11", b="12", c="13", d="14" и захват происходит именно 4 чисел, разделённых точками, и никакой другой комбинации Во втором: _=nil, a=nil, b=nil, c=nil, d=nil и захвата не произошло из-за пробела в конце. В третьем: _=14, a="11", b="12", c="13", d="14" захват 4 чисел, разделённых точками, в любом месте (хоть вначале строки, хоть в середине, хоть в конце), и строка не обязана содержать только 4 числа, разделённых точками, в отличие от первых двух примеров. Идём далее. Выпишу основные регулярные выражения: Цитата . - любой символ %a - латинская буква %с - контрольный символ %d - десятичная цифра %u - латинская буква верхнего регистра %l - латинская буква нижнего регистра %p - знак пунктуации %s - символ пробела %w - латинская буква или арабская цифра %z - нулевой символ %A - не латинская буква %C - не контрольный символ %D - не десятичныая цифра %U - не латинская буква верхнего регистра %L - не латинская буква нижнего регистра %P - не знак пунктуации %S - не символ пробела %W - не латинская буква и не арабская цифра %Z - не нулевой символ вроде становится понятнее, не так ли? Эквиваленты: %d эквивалентно [0-9] %D эквивалентно [^%d] %w эквивалентно [A-Za-z0-9] %W эквивалентно [^%w] в 4 и 5 примерах встречается регулярное выражение %S. Находим в списке выше, и видим: %S - не символ пробела. То есть, это означает, что это не пробел, а следовательно это любой символ, кроме пробела. Теперь рассмотрим всякие плюсики. Я буду рассматривать их совместно с регулярным выражением %s, то есть с символом пробела. Цитата %s - 1 символ пробела %s+ - 1 или более символов пробела %s- - 0 или более символов пробела %s* - 0 или более символов пробела %s? - 0 или 1 символ пробела Теперь объясню разницу между * и - Удобнее всего на следующем примере. Я уже рассказал, что означает точка (смотри выше в списке регулярных выражений: "." - любой символ). Рассмотрим примеры следующих регулярных выражений: Цитата "(.*)" - захват всей строки "/(.*)/" - захват всего, что находится между КРАЙНИМИ символами / "/(.-)/" - захват всего, что находится между ПЕРВЫМИ ДВУМЯ символами / Цитата %bxy - нахождение, а в случае захвата и захват, всего того, что находится между символами x и y, включая концы x и y Ну и последнее... А если, допустим, я хочу захватить точку. Как быть? Дело в том, что точка является так называемым магическим символом. Магические символы: ( ) . % + - * ? [ ] ^ $ Перед всеми магическими символами нужно ставить %, то есть все магические символы надо экранировать. Таким образом, чтобы захватить точку надо написать "(%.)" На последок ещё несколько часто используемых регулярных выражений: Цитата "(%S)" - захват первого не пробельного символа строки; "(%S*)" - захват первого слова или пустого слова ""; "%S*%s*(%S*)" - захват второго слова; "(%S*)%s*(%S*)" - захват первого и второго слова; "(%S%S)" - захват первых двух не пробельных символов; "(%S+)%s*(%d*)" - захват слова и числа; "(%d*%.%d*%.%d*%.%d*)" - захват строки из 4 чисел, разделенных точками (например ip адреса); "%s*(%d*%.%d*%.%d*%.%d*)%s*(%d*)%s*(.*)" - захват чисел, разделенных точками, захват числа, захват всего оставшегося; "()" - захват номера позиции; "(why)" - захват слова why (в любом месте); "^(why)" - захват слова why, только если оно стоит на первых трех (в данном случае) позициях; "(why)$" - захват слова why, только если оно стоит на последних трех (в данном случае) позициях; "%b<>%s+(%S)(%S+)%s*(.*)" - захват первого символа, сразу после текста, ограниченного символами < >, второй захват слова сразу после захваченного символа, третий захват - захват всего оставшегося. [^/]+ - не пустой захват с начала до символа / Теперь, я думаю, что вы и сами сможете написать, что же получается в примерах 4 и 5??? |
|
|
10.11.2008, 9:34
Сообщение
#2
|
|
Постоялец Группа: Пользователи Сообщений: 454 Регистрация: 17.10.2008 Из: Новосибирск Пользователь №: 825 Спасибо сказали: 90 раз |
Уф, суще дельный и юсфульный топик, заслуживает самого пристального изучения, исключительно на трезвую голову и после хорошего здорового сна
Спасибо |
|
|
6.8.2009, 11:19
Сообщение
#3
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Это конечно же не относится к регулярным выражениям, однако очень часто используется до захвата данных.
Речь идёт о методе sub (или функции string.sub). Данный метод (функция) служит для обрезания строк или для выделения подстрок. Код sData = sData:sub(s, e) В переменную sData будет возвращена подстрока строки sData начиная с позиции s и заканчивая позицией e. s и e могут быть также отрицательными. В случае когда они положительные, всё понятно, например sData:sub(3, 6). Тут этот метод возьмёт из sData с третьего до шестого символа включительно. Если же sData:sub(-5, -2), то метод возьмёт из sData со второго до пятого символа включительно, но уже отсчёт позиций будет идти с конца строки. В этой функции последний параметр является необязательным, т.е. можно написать sData:sub(2), в этом случае последний параметр берётся по умолчанию -1. Если последний параметр равен -1 - это означает, что строка берётся до конца, в данном примере со второго символа и до конца. Пример строки: Код <НИК> Пример.| Составим таблицу для большей наглядности: Код 'символ', 'позиция от начала', 'позиция от конца' '<', '1', '-14' 'Н', '2', '-13' 'И', '3', '-12' 'К', '4', '-11' '>', '5', '-10' 'пробел', '6', '-9' 'П', '7', '-8' 'р', '8', '-7' 'и', '9', '-6' 'м', '10', '-5' 'е', '11', '-4' 'р', '12', '-3' '.', '13', '-2' '|', '14', '-1' Последний символ | мы не видим, но он есть, этот символ не выводится в чат, но позволяет скрипту определять конец строки, поэтому в случае использования кода sData:sub(1, -2) как раз таки мы избавляемся от этого символа, т.е. берём строку начиная с первого символа и, заканчивая вторым с конца, так как первый с конца это символ |. А вообще, применяют чаще всего этот метод чтобы отсечь ник и символ | и оставить только данные, посылаемые в чат, вот так: Код sData = sData:sub(tUser.sNick:len() + 4, -2)
|
|
|
10.8.2009, 10:56
Сообщение
#4
|
|
Главный ра******й тут... Группа: Главные администраторы Сообщений: 1 727 Регистрация: 18.5.2008 Из: RF, 2la Пользователь №: 1 Спасибо сказали: 776 раз |
Мерси, а методы len и sub естественно быстрее чем match или find ?!
Т.е. к примеру, берем строку "<Nick> lalalalala|", в ней нам нужен текст сообщения, ник нам известен, вот примерный код теста: CODE sData = "<Nick> lalalalala|" sNick = "Nick" iBegin = os.clock() for i = 1, 1000000 do --sNeedData = sData:match("^%b<>%s(.*)|$") sNeedData = sData:sub(sNick:len() + 4, -2) end --Core.SendToAll((os.clock() - iBegin).." secs with _match_ and data: "..sNeedData) Core.SendToAll((os.clock() - iBegin).." secs with sub and data: "..sNeedData) А вот результат, сначала закоментированы 2е строки, 5 раз, потом первые: Цитата [11:52:22] 0.74899999999991 secs with _match_ and data: lalalalala [11:52:23] 0.71699999999998 secs with _match_ and data: lalalalala [11:52:25] 0.73300000000006 secs with _match_ and data: lalalalala [11:52:25] 0.73299999999995 secs with _match_ and data: lalalalala [11:52:26] 0.74900000000002 secs with _match_ and data: lalalalala [11:52:41] 0.702 secs with sub and data: lalalalala [11:52:42] 0.71799999999996 secs with sub and data: lalalalala [11:52:44] 0.702 secs with sub and data: lalalalala [11:52:44] 0.74900000000002 secs with sub and data: lalalalala [11:52:44] 0.73299999999995 secs with sub and data: lalalalala Как видите на милионе операций разница есть, незначительная, так что лучше использовать? |
|
|
10.8.2009, 11:24
Сообщение
#5
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Быстродействие практически одинаковое. Использование match должно быть по идее эффективнее прежде всего из-за того, что вызывается всего 1 функция. Подобного рода тесты не могут точно определить быстроту выполнения, так как довольно большая погрешность вносится побочными процессами. Поэтому подобное сравнение ни о чём не говорит.
|
|
|
16.10.2009, 23:47
Сообщение
#6
|
|
Абсолютный новичок Группа: Пользователи Сообщений: 8 Регистрация: 19.4.2009 Из: here&now Пользователь №: 3 101 Спасибо сказали: 3 раза |
Ещё стоит добавить, что луа не поддерживает PCRE/PREG
т.е. регэкспа типа "^.+(%.mp[1-3]$|%.wav$|%.flac$)" работать не будет. |
|
|
16.10.2009, 23:51
Сообщение
#7
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Действительно, lua использует урезанную версию регулярных выражений.
Однако, предложенное регулярное выражение может быть преобразовано и будет работать)) |
|
|
28.10.2009, 15:43
Сообщение
#8
|
|
Активный участник Группа: Пользователи Сообщений: 54 Регистрация: 29.12.2008 Из: Украниа, Харьков Пользователь №: 1 599 Спасибо сказали: 6 раз |
м...много материала , но в целом все доступно и понятно
|
|
|
19.11.2010, 2:45
Сообщение
#9
|
|
Продвинутый участник Группа: Пользователи Сообщений: 157 Регистрация: 19.1.2010 Из: Волгоград Пользователь №: 5 756 Спасибо сказали: 77 раз |
как понять это?
Цитата "/(.-)/" - захват всего, что находится между ПЕРВЫМИ ДВУМЯ символами / трудности перевода? Или как-то иначе?Первые два символа "/" - это "//" в начале строки. Что может быть между ними? |
|
|
19.11.2010, 9:54
Сообщение
#10
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Не говорится же, что эти символы обязательно следуют друг за другом.
Если бы имелись ввиду первый два символа строки, то так и было бы написано: "первые два символа СТРОКИ", а тут написано "ПЕРВЫМИ ДВУМЯ символами /", и про расположение этих символов в строке ничего не сказано. Символов в строке может быть больше двух. Мы получаем всё то, что находится между первыми двумя символами /. Код "qwerty/12345/6789/00000///123" -> "12345"
|
|
|
19.11.2010, 11:56
Сообщение
#11
|
|
Белый Волк Группа: Пользователи Сообщений: 1 723 Регистрация: 11.9.2008 Из: г.Томск Пользователь №: 516 Спасибо сказали: 657 раз |
Setuper, это относится ТОЛЬКО к символу слэш '/' ?
Для всего остального (захват МЕЖДУ символами): Цитата %bxy ?
|
|
|
19.11.2010, 12:16
Сообщение
#12
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Можно и то и другое использовать, только в случае %bxy захватываются ещё и концы x и y.
То есть %b// захватить всё между первыми двумя символами /, включая и сами символы. Вообще говоря примеры: Цитата "/(.*)/" - захват всего, что находится между КРАЙНИМИ символами / были написаны для того, чтобы показать разницу между регулярными выражениями .* и .- , а не для того чтобы как-то обыграть слеш
"/(.-)/" - захват всего, что находится между ПЕРВЫМИ ДВУМЯ символами / |
|
|
19.11.2010, 13:01
Сообщение
#13
|
|
Белый Волк Группа: Пользователи Сообщений: 1 723 Регистрация: 11.9.2008 Из: г.Томск Пользователь №: 516 Спасибо сказали: 657 раз |
Кстати, а разницу-то я между '*' и '-' так и не понял из объяснения..
|
|
|
19.11.2010, 13:26
Сообщение
#14
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Может на реальном примере поймёшь, раз на словах не понимаешь:
Код local s = "123/456/789/0"
print(s:match"/(.*)/") --> "456/789" print(s:match"/(.-)/") --> "456" |
|
|
20.11.2010, 3:06
Сообщение
#15
|
|
Продвинутый участник Группа: Пользователи Сообщений: 157 Регистрация: 19.1.2010 Из: Волгоград Пользователь №: 5 756 Спасибо сказали: 77 раз |
Setuper, теперь ясно, спасибо. Дополните первое сообщение приведенными примерами.
Хочу сделать приватные сообщения в общем чате, т.е. обрабатывать сообщения вида "user1, user2,user3: Текст сообщения" "user1: Текст сообщения" "user1, user2, user3: Текст сообщения " и т.д. и отправлять их юзеру либо юзерам перечисленным до ":". Пока моих знаний хватило на это: Код ChatArrival = function(user,sData) Далее nicks парсится и проверяется на ники юзеров. Если какое-либо слово из nicks не является ником юзера, то не происходит отправки сообщения указанным юзерам. (nicks - строка из одного, либо нескольких слов, разделенных ",", может быть наличие пробелов между словами и знаками).local _, _, nicks, msg = string.find(sData, "%b<>%s+(.*):%s*(.*)") ... "%b<>%s+(.*):%s*(.*)" - этого достаточно? Важно учесть смайлики и правила русского языка в применении знака ":", т.е. после него мб перечисление и пр., т.е. попросту проверить на наличие ников в части до символа ":", но это проверяется потом парсингом nicks на наличие ников, главное чтобы захватить строку до первого ":". |
|
|
20.11.2010, 6:40
Сообщение
#16
|
|
Белый Волк Группа: Пользователи Сообщений: 1 723 Регистрация: 11.9.2008 Из: г.Томск Пользователь №: 516 Спасибо сказали: 657 раз |
Попробуй вот этот корявый код:
Код local tShadowChatNicks = { -- эти ники могут писать мультиадресные сообщения ["Indy"] = 1, ["Ksan"] = 1, ["Атыктотакой"] = 1, } local tTable = {} function ChatArrival(tUser, sData) local sData = string.sub(sData, 1, -2) local sMsg = sData:match("^%b<>%s+(%S.*)") if sMsg and sMsg:find":" then if tShadowChatNicks[tUser.sNick] then local sNicks, sShadowMsg = sMsg:match"^(.*):%s*(.*)" sNicks = sNicks:gsub(",","") while sNicks do sWord = sNicks:match"^(%S+)" table.insert(tTable, sWord) sNicks = sNicks:gsub(sWord, "") sNicks = sNicks:match"^%s+(.*)" end for i = 1, #tTable do Core.SendToNick(tTable[i], "<"..tUser.sNick.."> "..sShadowMsg) -- тут сообщение отправляется очередному нику end Core.SendToNick(tUser.sNick, sData) -- а это идёт себе же для контроля tTable = {} -- обнуляем таблицу, готовя к след. сообщению collectgarbage() return true end end end Просто я не знаю, как красивее выцепить (распарсить, как ты говоришь) отдельные слова неизвестного количества из сообщения, потому пришлось заниматься выкорчёвыванием пробелов и запятых...одновременно каждое очередное полученное слово заносил в таблицу, оттуда уже легко вытаскивать и делать что хочешь. Добавил ещё сверку ника автора сообщения со списком имеющих право на такие сообщения. P.S.: Можно еще сделать так, чтоб каждому адресату при этом сообщалось, кому ещё это сообщение отправлено. P.P.S: Только нужно помнить, что это сообщение отправится на запись в чат-лог, поэтому скрипт нужно поставить выше Чат-лога. |
|
|
24.11.2010, 23:51
Сообщение
#17
|
|
Продвинутый участник Группа: Пользователи Сообщений: 157 Регистрация: 19.1.2010 Из: Волгоград Пользователь №: 5 756 Спасибо сказали: 77 раз |
Ksan, несколько иначе "выковыривал" ники, а так почти 1 в 1
пришлось добавить аналог PHP-функций Хотелось бы, конечно увидеть мнение гуру Setuper. P.S. Core.SendToNick(tUser.sNick, sData) -- а это идёт себе же для контроля сильное замечание - я почти сутки дебажил код чтобы увидеть то что отправил
|
|
|
25.11.2010, 11:45
Сообщение
#18
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Цитата "%b<>%s+(.*):%s*(.*)" - этого достаточно? Важно учесть смайлики и правила русского языка в применении знака ":", т.е. после него мб перечисление и пр., т.е. попросту проверить на наличие ников в части до символа ":", но это проверяется потом парсингом nicks на наличие ников, главное чтобы захватить строку до первого ":". Для кого я писал различия между ' * ' и ' - ' ? Код function ChatArrival(user, sData)
local nicks, msg = sData:match"%b<>%s+(.-)%s*:%s*(.*)" ... |
|
|
7.1.2011, 1:04
Сообщение
#19
|
|
Продвинутый участник Группа: Пользователи Сообщений: 104 Регистрация: 1.4.2009 Из: Россия Пользователь №: 2 871 Спасибо сказали: 42 раза |
Доброго вам всем времени суток. Подскажите пожалуйста мне такой момент. Пытаюсь сделать захват строки MyINFO. Но получается 5 переменных
Код local _, _, name, desc, speed, email, share = string.find(tUser.sMyINFO, "$MyINFO $ALL (%S+)%s+([^$]*)$ $([^$]*)$([^$]*)$([^$]+)") Я понимаю, что строка целиком выглядит так (Спасибо - Setuper - $MyINFO): Код $MyINFO $ALL [Ник] [Описание][Тэг]$ $[Соедиенние][Флаг]$[E-Mail]$[Шара]$| соответственно, захват из моего примера берёт: $MyINFO $ALL (%S+)%s+ равно $MyINFO $ALL [Ник]+пробел ([^$]*)$ равно [Описание][Тэг]$ - одной строкой до знака - $ $([^$]*)$([^$]*)$([^$]+) равно $[Соедиенние][Флаг] - одной строкой до знака - $ затем $[E-Mail]$[Шара]$| Подскажите, как лучше сделать захват по всем значениям MyINFO - что бы захватило все значения в 7 переменных. Код $MyINFO $ALL [Ник] [Описание][Тэг]$ $[Соедиенние][Флаг]$[E-Mail]$[Шара]$| Голова кругом пошла... Буду признателен за совет. |
|
|
7.1.2011, 1:48
Сообщение
#20
|
|
RusHub team lead Группа: Модераторы Сообщений: 4 030 Регистрация: 20.6.2008 Из: г. Королёв (Моск. обл.) Пользователь №: 46 Спасибо сказали: 1708 раз |
Не знаю зачем парсить это руками, когда обычно хаб парсит это дело, однако вот решение:
Код local sNick, sDesc, sSpeed, sEmail, iShare = tUser.sMyINFO:match"%$MyINFO %$ALL (%S+)%s+(.-)%$ %$(.-)%$(.-)%$(%d+)%$"
|
|
|
Похожие темы
|
Сейчас: 23.11.2024, 1:05 |