在可变长度lookbehind中平衡组[重复]

问题描述 投票:0回答:2

TL;DR: 在.NET 的lookbehinds 中使用捕获(特别是平衡组)会更改获得的捕获,尽管它不会产生任何影响。 .NET 的lookbehind 是什么破坏了预期的行为?

我试图找到“另一个问题”的答案,作为尝试 .NET 平衡组的借口。但是,我无法让它们在可变长度的lookbehind中工作。 首先,请注意,我并不打算有效地使用这个特定的解决方案。这更多是出于学术原因,因为我觉得可变长度lookbehind发生了一些我不知道的事情。并且知道当我实际上需要使用这样的东西来解决问题时,这在将来会派上用场。

考虑这个输入:

~(a b (c) d (e f (g) h) i) j (k (l (m) n) p) q

目标是匹配括号内以 
~

开头的所有字母,无论有多深(因此从

a
i
的所有字母)。我的尝试是检查后向查找中的正确位置,以便我可以在一次调用
Matches
中获取所有字母。这是我的模式:

(?<=~[(](?:[^()]*|(?<Depth>[(])|(?<-Depth>[)]))*)[a-z]

在lookbehind中,我尝试找到一个
~(

,然后使用命名的组堆栈

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
之间的字母,之后的所有情况都会失败。

那么(可变长度)lookbehind 会做什么来打破平衡组的这种使用呢?我整个晚上都试图研究这个(并找到像

this one

这样的页面),但我在后向查找中找不到这个的单一用途。 如果有人可以将我链接到一些有关 .NET 正则表达式引擎如何在内部处理 .NET 特定功能的深入信息,我也会很高兴。我发现了

这篇令人惊叹的文章

,但它似乎并没有涉及(可变长度)lookbehinds,例如。

.net regex regex-lookarounds balancing-groups
2个回答
13
投票
首先,正如我在一条评论中提到的,

(?<=(?<A>.)(?<-A>.))
永远不会匹配。

但转念一想,那
(?<=(?<-A>.)(?<A>.))
呢?确实很匹配!
(?<=(?<A>.)(?<A>.))
怎么样?与
"12"
匹配,
A
捕获
"1"
,如果我们查看
Captures
集合,它是
{"2", "1"}
- 前两个,然后一个 - 它是相反的。
因此,在lookbehind内部时,.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



2
投票

(a b ( c ) d e f )

其中d e 和f 需要匹配。更

平衡

的数据将会是 (a b (c)(d)(e)(f))

因此,我在此示例数据上采取的策略需要大括号后的赛后情况:

~(a b (c) d (e f (g) h) i) j k

其中 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

© www.soinside.com 2019 - 2024. All rights reserved.