【问题标题】:Is there a Regex-like that is capable of parsing matching symbols?是否有能够解析匹配符号的正则表达式?
【发布时间】:2012-10-18 05:32:27
【问题描述】:

这个正则表达式

/\(.*\)/

不会匹配匹配的括号,而是字符串中的最后一个括号。是否有正则表达式扩展或类似的东西,其语法允许这样做?例如:

there are (many (things (on) the)) box (except (carrots (and apples)))

/OPEN(.*CLOSE)/ 应该匹配 (many (things (on) the))

括号可以有无限的层次。

【问题讨论】:

  • 你能举一个你试图匹配的字符串的例子吗?
  • 平衡括号是基本正则表达式不能做的最重要的例子。

标签: regex string parsing


【解决方案1】:

如果你只有一层括号,那么有两种可能。

选项 1: 使用不贪婪的重复:

/\(.*?\)/

当它遇到第一个)时会停止。

选项 2:使用否定字符类

/\([^)]*\)/

这只能重复不是) 的字符,因此它必然永远不会超过第一个右括号。由于性能原因,通常首选此选项。此外,这个选项更容易扩展以允许转义括号(这样您就可以匹配这个完整的字符串:(some\)thing) 而不是丢弃thing))。但这可能很少需要。

但是,如果您想要嵌套结构,这对于正则表达式来说通常太复杂了(尽管 PCRE 等一些风格支持递归模式)。在这种情况下,您应该自己遍历字符串并计算括号,以跟踪您当前的嵌套级别。

正如关于这些递归模式的旁注:在 PCRE 中,(?R) 仅代表整个模式,因此将其插入某处会使整个事情递归。但是括号的每个内容必须与整个匹配具有相同的结构。此外,实际上不可能用它进行有意义的一步替换,以及在多个嵌套级别上使用捕获组。总而言之 - 你最好不要对嵌套结构使用正则表达式。

更新:由于您似乎渴望找到一个正则表达式解决方案,以下是您将如何使用 PCRE 匹配您的示例(PHP 中的示例实现):

$str = 'there are (many (things (on) the)) box (except (carrots (and apples)))';
preg_match_all('/\([^()]*(?:(?R)[^()]*)*\)/', $str, $matches);
print_r($matches);

结果

Array
(
    [0] => Array
        (
            [0] => (many (things (on) the))
            [1] => (except (carrots (and apples)))
        )   
)

模式的作用:

\(      # opening bracket
[^()]*  # arbitrarily many non-bracket characters
(?:     # start a non-capturing group for later repetition
(?R)    # recursion! (match any nested brackets)
[^()]*  # arbitrarily many non-bracket characters
)*      # close the group and repeat it arbitrarily many times
\)      # closing bracket

这允许无限的嵌套级别,也允许无限的并行级别。

请注意,不可能将所有嵌套级别作为单独的捕获组。您将始终只获得最内层或最外层的组。另外,像这样进行递归替换是不可能的。

【讨论】:

  • 哇。 Unrolling-the-Recursive-Loop! 我刚刚学到了一些新东西 - 谢谢。大+1。 (我猜你已经读过MRE3!)
  • @ridgerunner,不,我没有;)。只需应用 PHP 的 PCRE 文档:D。但是,我不知道您在这里指的是什么。你的答案不是基本一样吗?
  • 是的,它们在功能上是等效的。但是您在此处实现的{normal* (special normal*)*} 结构是Friedl 先进的“Unrolling-the-Loop” 效率技术的完美示例。 (您有效地采用了我的表达方式,并通过消除不必要的交替(相对较慢)使其变得更快)。 (请注意,the Slashdot review of MRE 将其评为 11 分(满分 10 分!)
  • @ridgerunner 好吧。那是幸运的,因为它只是我想到的第一个实现(就可读性而言,我实际上更喜欢你的实现)。但是,是的,既然您提到了交替效率较低是有道理的(尤其是如果您忘记使重复具有所有格)。
  • @ridgerunner 我终于读完了!实际上,他确实在第 477 页上自己展开了递归循环;)
【解决方案2】:

正则表达式不足以找到匹配的括号,因为括号是嵌套结构。不过,有一个简单的算法可以找到匹配的括号,在 this answer 中有描述。

如果您只是想在表达式中找到 first 右括号,则应在正则表达式中使用非贪婪匹配器。在这种情况下,您的正则表达式的非贪婪版本如下:

/\(.*?\)/

【讨论】:

  • 有一个简单的算法可以找到解决方案,但我有兴趣在类似于 Regex 的东西中使用它们。
  • 不幸的是,正则表达式的功能不足以以您所说的方式处理嵌套结构。正则表达式对应于一种称为确定性有限自动机的机器,它有一个基本限制:它无法记住任何“额外”状态,例如,在您的情况下,您的括号深度有多少。还有另一种称为下推自动机的自动机,它具有您需要的功能,但我不相信有一种简单的语言,例如下推自动机的正则表达式。
  • @Dokkat,虽然我在所有方面都同意 Jordan,但请查看我的更新答案,它包含适用于某些引擎的正则表达式解决方案。
  • 正则表达式当然足够强大,可以将 innermost 匹配的括号集与/\([^()]*\)/ 匹配。并且那些具有(非常规)递归模式(即 Perl、PHP/PCRE、.NET)的正则表达式引擎也可以轻松匹配最外层的匹配对。
【解决方案3】:

给定一个包含嵌套匹配括号的字符串,您可以使用这个(非递归 JavaScript)正则表达式匹配最里面的集合:

var re = /\([^()]*\)/g;

或者你可以用这个(递归 PHP)正则表达式匹配最外层的集合:

$re = '/\((?:[^()]++|(?R))*\)/';

但是您不能轻松匹配位于最内层和最外层之间的匹配括号集。

另请注意,(简单且经常遇到的)表达式:/\(.*?\)/ 将始终不正确地匹配(无论是最内层还是最外层的匹配集)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-11
    • 2011-02-23
    • 2021-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-21
    • 2017-07-02
    相关资源
    最近更新 更多