Ответ 1
Рекурсия, группа самореференций (трюк Qtax), обратные Qtax или балансировочные группы
Введение
Идея добавления списка целых чисел в нижней части ввода аналогична известному взлому базы данных (не имеет никакого отношения к регулярному выражению), где вы присоединяетесь к таблице целых чисел. Мой оригинальный ответ использовал трюк @Qtax. В текущих ответах используются либо рекурсия, трюк Qtax (прямой, либо обратный вариант), либо балансировочные группы.
Да, это возможно... С некоторыми оговорками и обманом регулярного выражения.
- Решения в этом ответе означают как средство демонстрации синтаксиса регулярного выражения, а не практические ответы, которые должны быть реализованы.
- В конце вашего файла мы вставим список номеров, которым предшествует уникальный разделитель. Для этого эксперимента добавленная строка
:1:2:3:4:5:6:7
Это аналогичный метод известного хакера базы данных, который использует таблицу целых чисел. - Для первых двух решений нам нужен редактор, который использует аромат регулярного выражения, который позволяет рекурсию (решение 1) или группы саморегуляции захвата (решения 2 и 3). Два приходят на ум: Notepad ++ и EditPad Pro. Для третьего решения нам нужен редактор, который поддерживает балансировочные группы. Это, вероятно, ограничивает нас EditPad Pro или Visual Studio 2013 +.
Входной файл:
Скажем, мы ищем pig
и хотим заменить его номером строки.
Мы будем использовать это как ввод:
my cat
dog
my pig
my cow
my mouse
:1:2:3:4:5:6:7
Первое решение: рекурсия
Поддерживаемые языки: помимо текстовых редакторов, упомянутых выше (Notepad ++ и EditPad Pro), это решение должно работать на языках, использующих PCRE (PHP, R, Delphi), в Perl и на Python с использованием модуля Matthew Barnett regex
(непроверенные).
Рекурсивная структура живет в представлении и не является обязательной. Его задача состоит в том, чтобы сбалансировать строки, которые не содержат pig
, слева, с цифрами справа: подумайте об этом как о балансировке вложенной конструкции, например {{{ }}}
... За исключением того, что слева у нас нет -match lines, а справа мы имеем числа. Дело в том, что когда мы выходим из lookahead, мы знаем, сколько строк было пропущено.
Поиск:
(?sm)(?=.*?pig)(?=((?:^(?:(?!pig)[^\r\n])*(?:\r?\n))(?:(?1)|[^:]+)(:\d+))?).*?\Kpig(?=.*?(?(2)\2):(\d+))
Версия свободного пробела с комментариями:
(?xsm) # free-spacing mode, multi-line
(?=.*?pig) # fail right away if pig isn't there
(?= # The Recursive Structure Lives In This Lookahead
( # Group 1
(?: # skip one line
^
(?:(?!pig)[^\r\n])* # zero or more chars not followed by pig
(?:\r?\n) # newline chars
)
(?:(?1)|[^:]+) # recurse Group 1 OR match all chars that are not a :
(:\d+) # match digits
)? # End Group
) # End lookahead.
.*?\Kpig # get to pig
(?=.*?(?(2)\2):(\d+)) # Lookahead: capture the next digits
Заменить: \3
В демо, см. подстановки внизу. Вы можете играть с буквами в первых двух строках (удалить пробел, чтобы сделать pig
), чтобы перенести первое вхождение pig
в другую строку и посмотреть, как это влияет на результаты.
Второе решение: группа, которая относится к самому себе ( "Qtax Trick" )
Поддерживаемые языки: помимо текстовых редакторов, упомянутых выше (Notepad ++ и EditPad Pro), это решение должно работать на языках, использующих PCRE (PHP, R, Delphi), в Perl и на Python с использованием модуля Matthew Barnett regex
(непроверенные). Решение легко адаптируется к .NET, преобразовывая \K
в lookahead и притяжательный квантификатор в атомную группу (см..NET Version несколько строк ниже.)
Поиск:
(?sm)(?=.*?pig)(?:(?:^(?:(?!pig)[^\r\n])*(?:\r?\n))(?=[^:]+((?(1)\1):\d+)))*+.*?\Kpig(?=[^:]+(?(1)\1):(\d+))
Версия .NET: Назад в будущее
.NET не имеет \K
. Это его место, мы используем "назад к будущему" lookbehind (lookbehind, который содержит взгляд, который пропускает перед матчем). Кроме того, нам нужно использовать атомную группу вместо притяжательного квантификатора.
(?sm)(?<=(?=.*?pig)(?=(?>(?:^(?:(?!pig)[^\r\n])*(?:\r?\n))(?=[^:]+((?(1)\1):\d+)))*).*)pig(?=[^:]+(?(1)\1):(\d+))
Версия свободного пробела с комментариями (версия Perl/PCRE):
(?xsm) # free-spacing mode, multi-line
(?=.*?pig) # lookahead: if pig is not there, fail right away to save the effort
(?: # start counter-line-skipper (lines that don't include pig)
(?: # skip one line
^ #
(?:(?!pig)[^\r\n])* # zero or more chars not followed by pig
(?:\r?\n) # newline chars
)
# for each line skipped, let Group 1 match an ever increasing portion of the numbers string at the bottom
(?= # lookahead
[^:]+ # skip all chars that are not colons
( # start Group 1
(?(1)\1) # match Group 1 if set
:\d+ # match a colon and some digits
) # end Group 1
) # end lookahead
)*+ # end counter-line-skipper: zero or more times
.*? # match
\K # drop everything we've matched so far
pig # match pig (this is the match!)
(?=[^:]+(?(1)\1):(\d+)) # capture the next number to Group 2
Заменить:
\2
Выход:
my cat
dog
my 3
my cow
my mouse
:1:2:3:4:5:6:7
В демо, см. подстановки внизу. Вы можете играть с буквами в первых двух строках (удалить пробел, чтобы сделать pig
), чтобы перенести первое вхождение pig
в другую строку и посмотреть, как это влияет на результаты.
Выбор разделителя для цифр
В нашем примере разделитель :
для строки цифр довольно распространен и может произойти в другом месте. Мы можем придумать UNIQUE_DELIMITER
и слегка подстроить выражение. Но следующая оптимизация еще более эффективна и позволяет нам хранить :
Оптимизация для второго решения: обратная строка цифр
Вместо того, чтобы вставлять наши цифры в порядок, нам может быть полезно использовать их в обратном порядке: :7:6:5:4:3:2:1
В наших взглядах это позволяет нам спуститься к нижней части ввода с помощью простого .*
и начать отступать оттуда. Поскольку мы знаем, что мы находимся в конце строки, нам не нужно беспокоиться о том, что :digits
является частью другого раздела строки. Вот как это сделать.
Ввод:
my cat pi g
dog p ig
my pig
my cow
my mouse
:7:6:5:4:3:2:1
Поиск:
(?xsm) # free-spacing mode, multi-line
(?=.*?pig) # lookahead: if pig is not there, fail right away to save the effort
(?: # start counter-line-skipper (lines that don't include pig)
(?: # skip one line that doesn't have pig
^ #
(?:(?!pig)[^\r\n])* # zero or more chars not followed by pig
(?:\r?\n) # newline chars
)
# Group 1 matches increasing portion of the numbers string at the bottom
(?= # lookahead
.* # get to the end of the input
( # start Group 1
:\d+ # match a colon and some digits
(?(1)\1) # match Group 1 if set
) # end Group 1
) # end lookahead
)*+ # end counter-line-skipper: zero or more times
.*? # match
\K # drop match so far
pig # match pig (this is the match!)
(?=.*(\d+)(?(1)\1)) # capture the next number to Group 2
Заменить: \2
Смотрите подстановки в демо.
Третье решение: балансировочные группы
Это решение специфично для .NET.
Поиск:
(?m)(?<=\A(?<c>^(?:(?!pig)[^\r\n])*(?:\r?\n))*.*?)pig(?=[^:]+(?(c)(?<-c>:\d+)*):(\d+))
Версия свободного пробела с комментариями:
(?xm) # free-spacing, multi-line
(?<= # lookbehind
\A #
(?<c> # skip one line that doesn't have pig
# The length of Group c Captures will serve as a counter
^ # beginning of line
(?:(?!pig)[^\r\n])* # zero or more chars not followed by pig
(?:\r?\n) # newline chars
) # end skipper
* # repeat skipper
.*? # we're on the pig line: lazily match chars before pig
) # end lookbehind
pig # match pig: this is the match
(?= # lookahead
[^:]+ # get to the digits
(?(c) # if Group c has been set
(?<-c>:\d+) # decrement c while we match a group of digits
* # repeat: this will only repeat as long as the length of Group c captures > 0
) # end if Group c has been set
:(\d+) # Match the next digit group, capture the digits
) # end lokahead
Заменить: $1