本网站上的更多内容 |
简介 |
正则表达式快速入门 |
正则表达式教程 |
替换字符串教程 |
应用程序和语言 |
正则表达式示例 |
正则表达式参考 |
替换字符串参考 |
书评 |
可打印 PDF |
关于本网站 |
RSS 提要和博客 |
.NET 正则表达式风格有一个称为平衡组的特殊功能。平衡组的主要目的是匹配平衡结构或嵌套结构,这也是它们名称的由来。从技术上来说,该功能更准确的名称应该是捕获组减法。这就是该功能的实际作用。这是 .NET 为其他正则表达式风格(如 Perl、PCRE 和 Ruby)处理 正则表达式递归 的问题而提供的解决方案。JGsoft V2 同时支持平衡组和递归。
(?<capture-subtract>regex) 或 (?'capture-subtract'regex) 是平衡组的基本语法。它与 .NET 中 命名的捕获组 使用的语法相同,但组名之间用减号分隔。此组的名称为“capture”。您可以省略组的名称。 (?<-subtract>regex) 或 (?'-subtract'regex) 是非捕获平衡组的语法。
名称“subtract”必须是正则表达式中另一个组的名称。当正则表达式引擎进入平衡组时,它会从组“subtract”中减去一个匹配项。如果组“subtract”尚未匹配,或者其所有匹配项均已减去,则平衡组将无法匹配。您可以将平衡组视为 条件,它测试组“subtract”,其中“regex”作为“if”部分,而“else”部分始终无法匹配。不同之处在于,平衡组具有从组“subtract”中减去一个匹配项的附加功能,而条件则不会更改组。
如果平衡组成功并且它具有名称(在此示例中为“capture”),则该组将捕获从从组“subtract”中减去的匹配项的结束和平衡组本身的匹配项的开始之间的文本(在此示例中为“regex”)。
此功能在 .NET 中起作用的原因是 .NET 中的捕获组会保留在匹配过程中捕获的所有内容的堆栈,而这些内容不会回溯或减去。大多数其他正则表达式引擎只存储每个捕获组的最新匹配项。当 (\w)+ 匹配 abc 时,Match.Groups[1].Value 会返回 c,就像其他正则表达式引擎一样,但 Match.Groups[1].Captures 会存储该组的所有三个迭代:a、b 和 c。
让我们将正则表达式 (?'open'o)+(?'between-open'c)+ 应用于字符串 ooccc。 (?'open'o) 匹配第一个 o,并将其存储为组“open”的第一个捕获。 量词 + 重复该组。 (?'open'o) 匹配第二个 o,并将其存储为第二个捕获。再次重复,(?'open'o) 无法匹配第一个 c。但 + 对两次重复感到满意。
正则表达式引擎前进到 (?'between-open'c)。在引擎进入此平衡组之前,它必须检查已减去的组“open”是否捕获了某些内容。它捕获了第二个 o。引擎进入组,从“open”减去最近的捕获。这使得组“open”仅捕获第一个 o。现在在平衡组内部,c 匹配 c。引擎退出平衡组。组“between”捕获了从“open”减去的匹配(第二个 o)和平衡组刚刚匹配的 c 之间的文本。这是一个空字符串,但无论如何它都会被捕获。
平衡组也有 + 作为其量词。引擎再次发现已减去的组“open”捕获了某些内容,即第一个 o。正则表达式进入平衡组,使组“open”没有任何匹配项。c 匹配字符串中的第二个 c。组“between”捕获 oc,它是从“open”减去的匹配(第一个 o)和平衡组刚刚匹配的第二个 c 之间的文本。
平衡组再次重复。但这一次,正则表达式引擎发现组“open”没有剩余的匹配项。平衡组匹配失败。组“between”不受影响,保留其最近的捕获。
+ 对两次迭代感到满意。引擎已到达正则表达式的末尾。它返回 oocc 作为整体匹配。 Match.Groups['open'].Success 将返回 false,因为该组的所有捕获都被减去了。 Match.Groups['between'].Value 返回 "oc"。
如果我们希望匹配平衡的 o 和 c,我们需要修改此正则表达式。为了确保正则表达式不会匹配 ooccc(其中 c 多于 o),我们可以添加 锚点:^(?'open'o)+(?'-open'c)+$。此正则表达式经历与前一个正则表达式相同的匹配过程。但是,在 (?'-open'c)+ 无法匹配其第三次迭代后,引擎到达 $,而不是正则表达式的结尾。这无法匹配。正则表达式引擎将回溯,尝试量词的不同排列,但它们都无法匹配。找不到匹配项。
但是正则表达式 ^(?'open'o)+(?'-open'c)+$ 仍然匹配 ooc。匹配过程再次相同,直到平衡组匹配第一个 c,并将组“open”留给第一个 o 作为其唯一的捕获。量词使引擎再次尝试平衡组。引擎再次发现减去的组“open”捕获了某些内容。正则表达式进入平衡组,将组“open”留给没有任何匹配项。但是现在,c 无法匹配,因为正则表达式引擎已到达字符串的末尾。
正则表达式引擎现在必须回溯出平衡组。在回溯平衡组时,.NET 也会回溯减法。由于在进入平衡组时第一个 o 的捕获从“open”中减去,因此在回溯出平衡组时,此捕获现在已还原。重复组 (?'-open'c)+ 现在减少到一次迭代。但是量词对此没有问题,因为 + 始终表示“一次或多次”。仍然在字符串的末尾,正则表达式引擎到达正则表达式中的 $,它匹配。整个字符串 ooc 作为整体匹配项返回。 Match.Groups['open'].Captures 将字符串中的第一个 o 作为 CaptureCollection 中的唯一项保存。这是因为,在回溯后,第二个 o 从组中减去,但第一个 o 没有。
为了确保正则表达式匹配 oc 和 oocc 但不匹配 ooc,我们需要检查组“open”在匹配过程到达正则表达式的末尾时没有剩余捕获。我们可以使用 条件 来实现此目的。(?(open)(?!)) 是一个条件,用于检查组“open”是否匹配某些内容。在 .NET 中,匹配某些内容意味着在堆栈上仍然有未回溯或未减去的捕获。如果组已捕获某些内容,则评估条件的“if”部分。在这种情况下,这是空负向前查找 (?!)。此向前查找中的空字符串始终匹配。由于向前查找为负,因此这会导致向前查找始终失败。因此,如果组已捕获某些内容,则条件始终失败。如果组未捕获任何内容,则评估条件的“else”部分。在这种情况下,没有“else”部分。这意味着如果组未捕获任何内容,则条件始终成功。这使得 (?(open)(?!)) 成为验证组“open”没有剩余捕获的适当测试。
正则表达式 ^(?'open'o)+(?'-open'c)+(?(open)(?!))$ 无法匹配 ooc。当 c 无法匹配,因为正则表达式引擎已到达字符串末尾,引擎将回溯出平衡组,使“open”具有单个捕获。正则表达式引擎现在到达条件,无法匹配。正则表达式引擎将回溯尝试量词的不同排列,但它们都无法匹配。找不到匹配项。
正则表达式 ^(?'open'o)+(?'-open'c)+(?(open)(?!))$ 确实匹配 oocc。在 (?'-open'c)+ 匹配 cc 后,正则表达式引擎无法第三次进入平衡组,因为“open”没有剩余的捕获。引擎前进到条件。条件成功,因为“open”没有剩余的捕获,并且条件没有“else”部分。现在 $ 在字符串末尾匹配。
^(?:(?'open'o)+(?'-open'c)+)+(?(open)(?!))$ 将捕获组和平衡组包装在 非捕获组 中,该组也会重复。此正则表达式匹配任何类似 ooocooccocccoc 的字符串,其中包含任意数量的完全平衡的 o 和 c,以及任意数量的成对序列,嵌套到任意深度。平衡组确保正则表达式永远不会匹配在字符串中的任何点处 c 比其左侧的 o 多的字符串。位于重复组外部的末尾条件确保正则表达式永远不会匹配 o 比 c 多的字符串。
^(?>(?'open'o)+(?'-open'c)+)+(?(open)(?!))$ 通过使用原子组而不是非捕获组来优化之前的正则表达式。原子组(也是非捕获的)消除了几乎所有正则表达式无法找到匹配项时的回溯,当在包含大量 o 和 c 但在结尾处未正确平衡的长字符串上使用时,这可以极大地提高性能。原子组不会改变正则表达式匹配具有平衡 o 和 c 的字符串的方式。
^m*(?>(?>(?'open'o)m*)+(?>(?'-open'c)m*)+)+(?(open)(?!))$ 允许字符串中的任何位置出现任意数量的字母 m,同时仍然要求所有 o 和 c 保持平衡。m* 在正则表达式的开头允许在第一个 o 之前出现任意数量的 m。(?'open'o)+ 已更改为 (?>(?'open'o)m*)+ 以允许每个 o 后出现任意数量的 m。类似地,(?'-open'c)+ 已更改为 (?>(?'-open'c)m*)+ 以允许每个 c 后出现任意数量的 m。
这是使用 .NET 平衡组或捕获组减法特性匹配平衡结构的通用解决方案。您可以用任何正则表达式替换 o、m 和 c,只要这三个表达式中的任意两个不能匹配相同的文本即可。
^[^()]*(?>(?>(?'open'\()[^()]*)+(?>(?'-open'\))[^()]*)+)+(?(open)(?!))$ 将此技术应用于匹配其中所有括号都完美平衡的字符串。
您可以对被平衡组减去的组使用 回引。回引匹配组最近一次未回溯或减去的匹配。正则表达式 (?'x'[ab]){2}(?'-x')\k'x' 匹配 aaa、aba、bab 或 bbb。它不匹配 aab、abb、baa 或 bba。字符串的第一个和第三个字母必须相同。
让我们看看 (?'x'[ab]){2}(?'-x')\k'x' 如何匹配 aba。 (?'x'[ab]) 的第一次迭代捕获 a。第二次迭代捕获 b。现在,正则表达式引擎到达平衡组 (?'-x')。它检查组“x”是否已匹配,它已匹配。引擎进入平衡组,从组“x”的堆栈中减去匹配 b。平衡组内没有正则表达式标记。它匹配而不向前推进字符串。现在,正则表达式引擎到达反向引用 \k'x'。组“x”堆栈顶部的匹配是 a。字符串中的下一个字符也是反向引用匹配的 a。 aba 被发现为整体匹配。
当你将此正则表达式应用于 abb 时,匹配过程是相同的,除了反向引用无法匹配字符串中的第二个 b。由于正则表达式没有正则表达式引擎可以尝试的其他排列,因此匹配尝试失败。
^(?'letter'[a-z])+[a-z]?(?:\k'letter'(?'-letter'))+(?(letter)(?!))$ 匹配任何长度的回文词。此正则表达式利用了反向引用和捕获组减法协同工作的事实。它还使用空平衡组作为上一节中的正则表达式。
让我们看看此正则表达式如何匹配回文 radar。 ^ 匹配字符串的开头。然后 (?'letter'[a-z])+ 迭代五次。组“letter”最终在其堆栈上匹配五次:r、a、d、a 和 r。正则表达式引擎现在位于字符串的末尾和正则表达式中的 [a-z]?。它不匹配,但没关系,因为量词使其可选。引擎现在到达反向引用 \k'letter'。组“letter”在其堆栈顶部有 r。这无法匹配字符串末尾后的空隙。
正则表达式引擎回溯。(?'letter'[a-z])+ 减少到四次迭代,在组“letter”的堆栈中留下 r、a、d 和 a。[a-z]? 匹配 r。反向引用再次无法匹配字符串末尾后的空值。引擎回溯,强制 [a-z]? 放弃其匹配。现在,“letter” 的堆栈顶部有 a。这会导致反向引用无法匹配 r。
接下来是更多的回溯。(?'letter'[a-z])+ 减少到三次迭代,在组“letter”的堆栈顶部留下 d。引擎再次使用 [a-z]?。它再次失败,因为没有 d 与反向引用匹配。
再次回溯,组“letter”的捕获堆栈减少到 r 和 a。现在形势逆转。[a-z]? 匹配 d。反向引用匹配 a,这是组“letter”最近一次未回溯的匹配。引擎现在到达空平衡组 (?'-letter')。这匹配,因为组“letter”有一个匹配 a 要减去。
反向引用和平衡组位于一个重复的非捕获组内,因此引擎再次尝试它们。反向引用匹配 r,平衡组从“letter”的堆栈中减去它,使捕获组没有任何匹配。再次迭代,反向引用失败,因为组“letter”的堆栈上没有匹配项。这使得该组充当一个不参与组。在 .NET 中,对不参与组的反向引用总是失败,就像在大多数正则表达式风格中一样。
(?:\k'letter'(?'-letter'))+ 已成功匹配两次迭代。现在,条件 (?(letter)(?!)) 成功,因为组“letter”没有匹配项。锚 $ 也匹配。回文 radar 已匹配。
| 快速入门 | 教程 | 工具和语言 | 示例 | 参考 | 书籍评论 |
| 简介 | 目录 | 特殊字符 | 不可打印字符 | Regex 引擎内部 | 字符类 | 字符类减法 | 字符类交集 | 简写字符类 | 点 | 锚 | 词边界 | 交替 | 可选项 | 重复 | 分组和捕获 | 反向引用 | 反向引用,第 2 部分 | 命名组 | 相对反向引用 | 分支重置组 | 自由间距和注释 | Unicode | 模式修饰符 | 原子分组 | 占有量词 | 前瞻和后顾 | 前瞻和后顾,第 2 部分 | 将文本保留在匹配之外 | 条件 | 平衡组 | 递归 | 子例程 | 无限递归 | 递归和量词 | 递归和捕获 | 递归和反向引用 | 递归和回溯 | POSIX 方括号表达式 | 零长度匹配 | 继续匹配 |
页面 URL:https://regexper.cn/balancing.html
页面最后更新时间:2021 年 8 月 12 日
网站最后更新时间:2024 年 3 月 15 日
版权所有 © 2003-2024 Jan Goyvaerts。保留所有权利。