Балансирующие группы в виде переменной длины
TL;DR: использование захвата (и, в частности, групп балансировки) внутри видимости.NET изменяет полученные захваты, хотя это не должно иметь значения. Что с внешними взглядами.NET нарушает ожидаемое поведение?
Я пытался придумать ответ на этот другой вопрос в качестве предлога, чтобы поиграться с балансирующими группами.NET. Тем не менее, я не могу заставить их работать внутри вида переменной длины.
Прежде всего, обратите внимание, что я не намерен продуктивно использовать это конкретное решение. Это больше по академическим причинам, потому что я чувствую, что с изменяемой длиной происходит что-то, чего я не знаю. И зная, что это может пригодиться в будущем, когда мне действительно нужно использовать что-то подобное для решения проблемы.
Рассмотрим этот вход:
~(a b (c) d (e f (g) h) i) j (k (l (m) n) p) q
Цель состоит в том, чтобы сопоставить все буквы, которые находятся внутри скобок, которым предшествует ~
, неважно, насколько глубоко (так что все из a
в i
). Моя попытка состояла в том, чтобы проверить правильность позиции во взгляде сзади, чтобы я мог получить все буквы за один звонок Matches
, Вот мой образец:
(?<=~[(](?:[^()]*|(?<Depth>[(])|(?<-Depth>[)]))*)[a-z]
Во взгляде сзади я пытаюсь найти ~(
, а затем я использую стек именованной группы Depth
посчитать лишние открывающие скобки. Пока скобка открыта в ~(
никогда не закрывается, взгляд должен совпадать. Если закрывающая скобка к этому достигнута, (?<-Depth>...)
не может вытолкнуть что-либо из стека, и просмотр позади должен потерпеть неудачу (то есть для всех писем из j
). К сожалению, это не работает. Вместо этого я соответствую a
, b
, c
, e
, f
, g
а также m
, Так что только эти:
~(a b (c) _ (e f (g) _) _) _ (_ (_ (m) _) _) _
Похоже, это означает, что внешний вид не может соответствовать ничему после того, как я закрыл одну скобку, если я не вернусь к самому высокому уровню вложения, который был до этого.
Ладно, это может означать, что с моим регулярным выражением есть что-то странное, или я неправильно понял балансировочные группы. Но потом я попробовал это, не оглядываясь назад. Я создал строку для каждой буквы, как это:
~(z b (c) d (e f (x) y) g) h (i (j (k) l) m) n
~(a z (c) d (e f (x) y) g) h (i (j (k) l) m) n
~(a b (z) d (e f (x) y) g) h (i (j (k) l) m) n
....
~(a b (c) d (e f (x) y) g) h (i (j (k) l) z) n
~(a b (c) d (e f (x) y) g) h (i (j (k) l) m) z
И использовал этот шаблон на каждом из них:
~[(](?:[^()]*|(?<Depth>[(])|(?<-Depth>[)]))*z
И по желанию все случаи совпадают, где z
заменяет букву между a
а также i
и все дела после этого терпят неудачу.
Так что же делает (переменная длина) внешний вид, который нарушает это использование балансировочных групп? Я пытался исследовать это весь вечер (и нашел страницы, подобные этой), но я не смог найти ни одного использования этого во взгляде.
Я также был бы рад, если бы кто-нибудь связал меня с какой-то подробной информацией о том, как механизм регулярных выражений.NET внутренне обрабатывает специфичные для.NET функции. Я нашел эту удивительную статью, но, похоже, она не входит в (с переменной длиной) взгляды за спиной.
2 ответа
Я думаю, что понял.
Во-первых, как я уже упоминал в одном из комментариев, (?<=(?<A>.)(?<-A>.))
никогда не совпадает
Но потом я подумал, что насчет (?<=(?<-A>.)(?<A>.))
? Это соответствует!
А как насчет (?<=(?<A>.)(?<A>.))
? Сопоставлено с "12"
, A
захватывает "1"
и если мы посмотрим на Captures
коллекция, это {"2", "1"}
- сначала два, потом один - все наоборот.
Таким образом, находясь внутри области просмотра,.net сопоставляется и захватывает справа налево.
Теперь, как мы можем сделать захват слева направо? На самом деле это довольно просто - мы можем обмануть движок, посмотрев вперед:
(?<=(?=(?<A>.)(?<A>.))..)
Применительно к вашему оригинальному шаблону, самый простой вариант, который я придумал, был:
(?<=
~[(]
(?=
(?:
[^()]
|
(?<Depth>[(])
|
(?<-Depth>[)])
)*
(?<=(\k<Prefix>)) # Make sure we matched until the current position
)
(?<Prefix>.*) # This is captured BEFORE getting to the lookahead
)
[a-z]
Проблема заключалась в том, что теперь сбалансированная часть может заканчиваться где угодно, поэтому мы заставляем ее доходить до текущей позиции (что-то вроде \G
или же \Z
было бы полезно здесь, но я не думаю, что.net имеет это)
Вполне возможно, что это поведение где-то задокументировано, я постараюсь найти его.
Вот другой подход. Идея проста -.net хочет совпадать справа налево? Отлично! Возьми это:
(совет: начните читать снизу - вот как это делает.net)
(?<=
(?(Depth)(?!)) # 4. Finally, make sure there are no extra closed parentheses.
~\(
(?> # (non backtracking)
[^()] # 3. Allow any other character
|
\( (?<-Depth>)? # 2. When seeing an open paren, decreace depth.
# Also allow excess parentheses: '~((((((a' is OK.
|
(?<Depth> \) ) # 1. When seeing a closed paren, add to depth.
)*
)
\w # Match your letter
Я думаю, что проблема с данными, а не с шаблоном. Данные имеют элементы "Post", которые должны быть сопоставлены, такие как
(ab (c) def)
где де и е должны быть сопоставлены. Более сбалансированные данные будут
(ab (c) (d) (e) (f))
Таким образом, уловка, которую я взял на этом примере данных, требовала ситуации после матча после фигурных скобок:
~ (ab (c) d (ef (g) h) i) jk
где J & K следует игнорировать... мой шаблон не удалось и захватил их.
Интересно то, что я назвал группы захвата, чтобы узнать, куда они пришли, а j и k вошли в плен три. Я оставляю вас с не ответом, а попыткой выяснить, можете ли вы улучшить его.
(~ # Anchor to a Tilde
( # Note that \x28 is ( and \x29 is )
( # --- PRE ---
(?<Paren>\x28)+ # Push on a match into Paren
((?<Char1>[^\x28\x29])(?:\s?))*
)+ # Represents Sub Group 1
( #---- Closing
((?<Char2>[^\x28\x29])(?:\s?))*
(?<-Paren>\x29)+ # Pop off a match from Paren
)+
(
((?<Char3>[^\x28\x29])(?:\s?))* # Post match possibilities
)+
)+
(?(Paren)(?!)) # Stop after there are not parenthesis
)
Вот совпадение с инструментом, который я создал сам (возможно, однажды я опубликую). Обратите внимание, что ˽ показывает, где пробел был сопоставлен.
Match #0
[0]: ~(a˽b˽(c)˽d˽(e˽f˽(g)˽h)˽i)˽j˽k
["1"] → [1]: ~(a˽b˽(c)˽d˽(e˽f˽(g)˽h)˽i)˽j˽k
→1 Captures: ~(a˽b˽(c)˽d˽(e˽f˽(g)˽h)˽i)˽j˽k
["2"] → [2]: (e˽f˽(g)˽h)˽i)˽j˽k
→2 Captures: (a˽b˽(c)˽d˽, (e˽f˽(g)˽h)˽i)˽j˽k
["3"] → [3]: (g
→3 Captures: (a˽b˽, (c, (e˽f˽, (g
["4"] → [4]: g
→4 Captures: a˽, b˽, c, e˽, f˽, g
["5"] → [5]: ˽i)
→5 Captures: ), ), ˽h), ˽i)
["6"] → [6]: i
→6 Captures: ˽, h, ˽, i
["7"] → [7]:
→7 Captures: ˽d˽, , ˽j˽k,
["8"] → [8]: k
→8 Captures: ˽, d˽, ˽, j˽, k
["Paren"] → [9]:
["Char1"] → [10]: g
→10 Captures: a, b, c, e, f, g
["Char2"] → [11]: i
→11 Captures: ˽, h, ˽, i
["Char3"] → [12]: k
→12 Captures: ˽, d, ˽, j, k