- PVSM.RU - https://www.pvsm.ru -
Я хочу поделиться своим изобретением, которое позволяет вам использовать только одно регулярное выражение, которое будет искать подстроку в строке с определенным условием. Если хотите, называйте это циклом в RegEx, которого раньше не существовало!
Я поделюсь с вами не только разными полезными шаблонами, но и покажу различные примеры от простых до сложных.
Пожалуйста, обратите внимание, что в регулярном выражении используются пробелы для улучшения читабельности. В регулярном выражении пробелы обычно используются как символы в строке, поэтому, чтобы эти шаблоны работали, требуется флаг [1] (?ix).
В примерах части регулярных выражений разделены на строки, что необходимо для улучшения восприятия, но эта функция не поддерживается регулярными выражениями. В примерах используется Perl syntax [2].
Начнем с простой задачи. Если в начале строки есть c, нужно найти в ней только слова и цифры (подсвечены красным):
c word = word + key
c 12 = word + word
word & word = word + word
12 = word + word
Фактически мы должны найти только слова типа word и цифры 2 и 0 только в первых двух строках.
Казалось бы, в чем проблема? Ввел что-нибудь вроде w+ (фундаментальное выражение поиска букв вроде А-Я) и нашел что надо...
Но как же условие? Ведь нам нужно учесть букву c в начале строки. Попробуем использовать синтаксис условий в RegEx: (?(condition)|(true)(false)). Ввводим на каком-нибуль сайте вроде regex101.com [3] и... RegEx если что и захватит, так это некоторую часть выражения. Хотя по правде он даже не найдет эту часть, ведь перед буквами стоят символы вроде = + &, т.е. RegEx не сработает.
Но мы же видим, что в строке есть буквы и буква c в начале. Значит мы должны закончить маяться ерундой и подключить уже Python с тернарным оператором… значит надо искать другое решение!
В решении данной задачи невозможно использовать look ahead/behind (слова стоят далеко от кавычек), условия (?(condition)|(true)(false)) и подгруппы с квантификатором ( )+ потому что согласно цитате с regex101.com [3]
a repeated capturing group will only capture the last iteration
что на нашем прекрасном языке звучит как
повторяющаяся захватывающая группа захватит только последнюю итерацию
Проще говоря, никаких тебе циклов и тернарных операторов, может пора подключать Python?
Не будем тянуть программиста за нервы и разберем уже простенький шаблон, который выглядит следующим образом:
condition K # Найти условие и пропустить
| # Начало цикла
(?<=G) # Убеждаемся что условие найдено; каждая следующая итерация идет с этой позиции RegEx и с позиции предыдущей итерации
separator*? # Нежадный: разделитель между словами
K # Пропустить все что было прежде
expression # Выражение: w+ или .+ или d+ ...
Идея такова: встретив condition , RegEx пропускает его K и продолжает поиск с его позиции (?<=G) . Проходит мимо нежадного [4] разделителя слов separator, пропускает его K и наконец захватывает нужное expression.
Дойдя до конца, все повторяется вновь с позиции последнего найденного слова (?<=G). Но, чтобы цикл шел верным путем и продолжал шагать по строке, необходимо добавить перед (?<=G) символ или |.
Обратите внимание на символ [5]K, суть которого важно запомнить и уметь применять самостоятельно: он означает, что все, что было найдено прежде, ныне не имеет значения и исчезает из финального варианта. Сдвиг каретки/курсора, если хотите. Позволяет найти условие, отсечь его из результата и вернуть нужное. Главное помните: K не работает в обычных захватывающих группах ( ), только в незахватывающих [6] и атомных группах [7]: (?:) и (?>). Но в примерах я вообще не стал использовать группы. И это тоже работает!
Символ K стоит после условия и разделителя. Повторю еще раз: найдя condition, мы первый раз пропускаем условие-шаблон, и пройдя через separator между словами и мы с каждой итерацией будем пропускать разделитель-шаблон. Они нам не нужны, нам нужны слова. Это лишь вспомогательные конструкции.
Теперь конструируем RegEx согласно шаблону (DEMO [8]):
c K # Условие: буква "c"
| # Начало цикла
(?<=G) # Убеждаемся что условие найдено; каждая следующая итерация идет с этой позиции RegEx и с позиции предыдущей итерации
.*? # Нежадный разделитель: 1 и более любых символов
K # Пропустить все что было прежде
w+ # Жадное выражение: любые буквы, цифры
Как получился такой шаблон? Condition у нас буква c, дальше ничего из шаблона не менялось, потом separator у нас любой символ [9] .*, затем сам шаблон поиска букв w который будет циклично искать буквы до конца всей строки.
Усложним эту задачу: если в начале строки есть c, а затем любые кавычки " ', нужно найти в кавычках только слова и цифры (подсвечены зеленым):
c"word & word" = word + word
c"12 = word" + word
c word & word = word + word
c 12 = word + word
Задача вроде похожа, а значит и шаблон будет не сильно отличаться от предыдущего. Но появилось существенное НО: мы больше не должны жадно хватать все слова из строки. Мы должны остановиться именно тогда, когда первый луч света освободит нас от работы с RegEx когда после слов появится кавычка. Т.е. "bla bla bla" STOOOP. Еще раз: встретили кавычку, подхватили все слова после нее, встретили кавычку вновь и остановились.
Значит теперь у нас есть условие остановки цикла. Шаблон для подобной задачи выглядит следующим образом:
condition K # Найти условие и пропустить
| # Начало цикла
(?<=G) # Убеждаемся что условие найдено; каждая следующая итерация идет с этой позиции RegEx и с позиции предыдущей итерации
stop*? # Символ остановки всего выражения: формат [^exclude]
K # Пропустить все что было прежде
expression # Выражение: w+ или .+ или d+ ...
Теперь конструируем RegEx согласно шаблону. Шаблон аналогичен предыдущему, но к условию добавлены кавычки ["']: c ["']
Появляется условие остановки регулярного выражения: [^"'](здесь символ [10]^ означает, что нужно найти любые символы, кроме кавычек.). После этого поиск завершается. Теперь мы создаем конструируем в соответствии с шаблоном (DEMO [11]):
c ["'] K # Условие: c" или c'
| # Начало цикла
(?<=G) # Убеждаемся что условие найдено
[^"']*? # Кавычки после которых завершается поиск
K # Пропустить все что было прежде
w+ # Жадное выражение: любые буквы, цифры
Попробуйте решить эти задачи не используя данные шаблоны. Я буду очень рад, если вы найдете иное оптимальное решение без Python!
Другая задача: нужно найти в кавычках ` только слова, которые не заключены в скобки { }. Проще говоря, мы должны шагать по строке, обходя стороной все, что заключено в { } или не является словом (подсвечены красным):
`{string} with {exluded} words 12 nums`
`string {with} {exluded} words 12 nums`
"quoted {string} with {exluded} words and 12 nums"
"quoted string {with} exluded {words} and {12} nums"
Значит мы должны изменить шаблон так, чтобы у него было условие остановки, условие обхода и наконец само захватывающее выражение. В данном случае должно быть два разных условия остановки: условие остановки и повтора цикла если обнаружены скобки { }; условие остановки выражения если обнаружены кавычки `:
# Условие после которого запускается 2 часть выражения
^condition # символ ^ означает начало строки
| # Начало цикла
(?<=G) # Убеждаемся что условие найдено
(?> # Атомная группа
skip # условие обхода: например {.*}
| # ИЛИ
stop # условие остановки: например [^"']
) K # Пропустить все что было прежде
expression # Выражение: w+ или .+ или d+ ...
Тут надо сразу рядом показать результат (DEMO [12]) и объяснить его идею:
^[`]K # Находит одиночные/двойные кавычки, убирает их из результата
| # Начало цикла
(?<=G) # Убеждаемся что условие найдено
(?> # Атомная группа
{.*?} # Пропускает содержимое скобок { }
| # ИЛИ
[^`] # Останавливается после вторых кавычек
) K # Пропускает все что было прежде
[^{}`]+ # Ищет 1 и более символов КРОМЕ { } `
Идея такова: встретив condition RegEx начинает с его позиции (?<=G), идет дальше, останавливается если обнаружена кавычка, обходит мимо группу, сбрасывает текущую позицию и наконец захватывает нужное expression. Дойдя до конца, все RegEx повторяется вновь с позиции последнего найденного слова (?<=G). И так до тех пор, пока не встретит главное условие остановки.
Атомная группа [7] (?>...) здесь важна для скорости поиска. Дело в том, что RegEx часто перебирает все варианты поиска подстрок по шаблону. Но как только эта группа найдет содержимое скобок, RegEx не будет искать 100500 вариантов как бы получше ухватить строку и все ее слова в скобках. Проще говоря: нашли, остановились на этом этапе и поехали дальше. Без лишних циклов и поисков.
Я буду очень рад, если вы найдете другое оптимальное решение! Пожалуйста, помогите мне улучшить данные шаблоны. У них есть существенные проблемы с оптимизацией: если не найдено condition, для каждого символа проверяется alternation (?<=G); нет пропуска неподходящих строк; не работают флаги (*SKIP)(*F). Не смотря на быструю скорость работы, количество шагов стремится к 100.000.
Автор: Rafaell0
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/regex/407735
Ссылки в тексте:
[1] флаг: https://(https://riptutorial.com/regex/topic/5138/regex-modifiers--flags-)
[2] Perl syntax: https://www.boost.org/doc/libs/1_85_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html
[3] regex101.com : https://regex101.com/r/wXPPD2/1
[4] нежадного: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%96%D0%B0%D0%B4%D0%BD%D0%B0%D1%8F_%D0%B8_%D0%BB%D0%B5%D0%BD%D0%B8%D0%B2%D0%B0%D1%8F_%D0%BA%D0%B2%D0%B0%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F
[5] символ : https://dev.to/marounmaroun/what-does-k-mean-in-regex-4l6n
[6] незахватывающих: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%93%D1%80%D1%83%D0%BF%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
[7] атомных группах: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%B3%D1%80%D1%83%D0%BF%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
[8] DEMO: https://regex101.com/r/wXPPD2/2
[9] любой символ: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%9B%D1%8E%D0%B1%D0%BE%D0%B9_%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB
[10] символ : https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%A1%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D1%8B_(%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D1%8B_%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%BE%D0%B2)
[11] DEMO: https://regex101.com/r/FUH7Xx/4
[12] DEMO: https://regex101.com/r/vZugRo/2
[13] Источник: https://habr.com/ru/articles/873832/?utm_source=habrahabr&utm_medium=rss&utm_campaign=873832
Нажмите здесь для печати.