Я не сумел найти разумных примеров применения конструкций {m,n}?
и ??
. Если вам о них известно, пожалуйста, поделитесь со мной своим опытом.
Дополнительная информация о кванторах содержится в разделе 3.13.
3.6. Позитивное и негативное заглядывание вперед
Понятно, что регулярное выражение сопоставляется со строкой линейно (осуществляя при необходимости возвраты). Поэтому существует понятие «текущего положения» в строке, это аналог указателя файла или курсора.
Термин «заглядывание» означает попытку сопоставить часть строки, находящуюся дальше текущего положения. Это утверждение нулевой длины, поскольку даже если соответствие будет найдено, никакого продвижения по строке не произойдет (то есть текущее положение не изменится).
В следующем примере строка "New world"
будет сопоставлена, если за ней следует одна из строк "Symphony"
или "Dictionary"
. Однако третье слово не будет частью соответствия.
s1 = "New World Dictionary"
s2 = "New World Symphony"
s3 = "New World Order"
reg = /New World(?= Dictionary | Symphony)/
m1 = reg.match(s1)
m.to_a[0] # "New World"
m2 = reg.match(s2)
m.to_a[0] # "New World"
m3 = reg.match(s3) # nil
Вот пример негативного заглядывания:
reg2 = /New World(?! Symphony)/
m1 = reg.match(s1)
m.to_a[0] # "New World"
m2 = reg.match(s2)
m.to_a[0] # nil
m3 = reg.match(s3) # "New World"
В данном случае строка "New world"
подходит, только если за ней не следует строка "Symphony"
.
3.7. Обратные ссылки
Каждая заключенная в круглые скобки часть регулярного выражения является отдельным соответствием. Они нумеруются, и есть несколько способов сослаться на такие части по номерам. Сначала рассмотрим традиционный «некрасивый» способ.
Сослаться на группы можно с помощью глобальных переменных $1
, $2
и т.д:
str = "а123b45с678"
if /(a\d+)(b\d+)(c\d+)/ =~ str
puts "Частичные соответствия: '#$1', '#$2', '#$3'"
# Печатается: Частичные соответствия: 'а123', 'b45', 'c768'
end
Эти переменные нельзя использовать в подставляемой строке в методах sub
и gsub
:
str = "а123b45с678"
str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=#$1, 2nd=#$2, 3rd=#$3")
# "1st=, 2nd=, 3rd="
Почему такая конструкция не работает? Потому что аргументы sub
вычисляются перед вызовом sub
. Вот эквивалентный код:
str = "а123b45с678"
s2 = "1st=#$1, 2nd=#$2, 3rd=#$3"
reg = /(a\d+)(b\d+)(c\d+)/
str.sub(reg,s2)
# "1st=, 2nd=, 3rd="
Отсюда совершенно понятно, что значения $1
, $2
, $3
никак не связаны с сопоставлением, которое делается внутри вызова sub.
В такой ситуации на помощь приходят специальные коды \1
, \2
и т.д.:
str = "а123b45с678"
str.sub(/(a\d+)(b\d+)(c\d+)/, '1st=\1, 2nd=\2, 3rd=\3')
# "1st=a123, 2nd=b45, 3rd=c768"
Обратите внимание на одиночные (твердые) кавычки в предыдущем примере. Если бы мы воспользовались двойными (мягкими) кавычками, не приняв никаких мер предосторожности, то элементы, которым предшествует обратная косая черта, были бы интерпретированы как восьмеричные числа:
str = "а123b45с678"
str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=\1, 2nd=\2, 3rd=\3")
# "1st=\001, 2nd=\002, 3rd=\003"
Обойти эту неприятность можно за счет двойного экранирования:
str = "а123b45с678"
str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=\\1, 2nd=\\2, 3rd=\\3")
# "1st=a123, 2nd=b45, 3rd=c678"
Допустима и блочная форма подстановки, в которой можно использовать глобальные переменные:
str = "а123b45с678"
str.sub(/(a\d+)(b\d+)(c\d+)/) { "1st=#$1, 2nd=#$2, 3rd=#$3" }
# "1st=a123, 2nd=b45, 3rd=c678"
При таком применении блока числа с обратной косой чертой нельзя использовать ни в двойных, ни в одиночных кавычках. Если вы немного поразмыслите, то поймете, что это разумно.
Упомяну попутно о том, что существуют незапоминаемые группы (noncapturing groups). Иногда при составлении регулярного выражения нужно сгруппировать символы, но чему будет соответствовать в конечном счете такая группа, несущественно. На этот случай и предусмотрены незапоминаемые группы, описываемые синтаксической конструкцией (?:...)
:
str = "а123b45с678"
str.sub(/(a\d+)(?:b\d+)(c\d+)/, "1st=\\1, 2nd=\\2, 3rd=\\3")
# "1st=a123, 2nd=c678, 3rd="
В предыдущем примере вторая группа не запоминается, поэтому та группа, которая должна была бы быть третьей, становится второй.
Лично мне не нравится ни одна из двух нотаций (\1
и $1
). Иногда они удобны, но никогда не бывают необходимы. Все можно сделать «красивее», в объектно-ориентированной манере.