【问题标题】:Find matching bracket with Regex使用正则表达式查找匹配的括号
【发布时间】:2011-04-05 09:24:25
【问题描述】:

输入是表示元素列表的字符串。

列表被定义为一个开放的大花 { 后跟 0 个或多个以空格分隔的元素,后跟一个封闭的大花 }

一个元素要么是一个字面量,要么是一个元素列表。

文字是一系列非空白字符。如果元素包含大括号,则必须使用反斜杠对其进行转义:\{\}。 (或者,为了简单起见,您可以假设文字中不允许使用花括号)

例子:

"{abc { def ghi } 7 { 1 {2} {3 4} } {5 6} x\{yz \}foo }"

文字中没有花括号:

"{abc { def ghi } 7 { 1 {2} {3 4} } {5 6} xyz foo }"

(这是 Tcl 列表的简化定义。)

我想知道的是:输入是否可以使用正则表达式拆分为最外层循环的元素?

预期输出:

abc
{ def ghi }
7
{ 1 {2} {3 4} }
{5 6}
x{yz
}foo

真正的问题是:这可以用正则表达式来完成吗?

我对 .NET 风格最感兴趣,但会接受任何答案。

我将在答案中发布我自己的假设,看看它是否被验证或销毁。

【问题讨论】:

  • 为什么}foo 是文字,而4} 不是?事实上,} 根据您的定义是一个有效的文字。
  • @Kobi 你是对的。我试图得到一个与 Tcl 解释器类似的定义,但它做了一些奇怪的事情。例如,它将允许set a 3{4,但不允许set a {1 2 3{4 }。闭合卷曲的类似行为。我会更新问题。

标签: .net regex parsing grammar matching


【解决方案1】:

对此的传统答案是响亮的“不”。正如我们在编译器类中所了解的,常规语法不能用于描述具有递归定义的语言(即不能使用有限状态机)

这里需要的是一个上下文无关的解析器,它的实现归结为一个有限状态机 + 一个堆栈。
ANTLRbison等。

【讨论】:

  • 下次您可能要考虑在发布您的答案之前留出几分钟,因为如果已经有一个被赞成的帖子,其他人很难获得任何选票,所以这可能会阻止许多其他人发布......甚至从查看问题(如果问题已经回答很多人甚至不会看到它)。我确实假设您有兴趣收到其他意见,否则您不会发布...对吗? PS:在 .NET 中,我相信可以使用“正则”表达式来做到这一点,但你是对的,不建议为此目的使用正则表达式。
  • @mark 已记笔记。是的,我对答案很感兴趣。我记得在某处读过一些关于正则表达式库的一些不太正统的扩展,该库允许在某些情况下匹配括号,但我不记得哪个库或哪种情况......
  • @EJP:这是一个自我回答,严格来说说这是不正确的,因为 .NET 中的“正则”表达式实际上根本不是正则的,事实上它可以解析上面的。但总的来说,这个答案仍然是很好的建议。发布自己的答案并没有错,这个答案也没有错,我只是说如果他真的 想要不同的意见 而不仅仅是快速的代表提升(我假设他这样做)那么他应该在发帖前等待以避免阻止其他人回答。
  • @Mark,不仅仅是.NET,而且现在使用的几乎所有编程语言的正则表达式实现都不能称为“常规”。如果正则表达式实现支持组和对这些组的反向引用,或者环顾断言,则在更严格的定义中它不是“常规的”。
  • @Crista Diaconescu:我能找到的关于这个主题的最佳信息在这里:stackoverflow.com/questions/3349999/…
【解决方案2】:

@Cristi 对正则表达式的看法是正确的:理论上,使用无堆栈的有限状态自动机解决递归表达式是不可能的。 但是,解决方案更简单:您只需要保留一个开括号数的计数器,并确保它不会低于 0。它比维护堆栈更节省内存,您只需要计数- 不是括号的内容。

算法:

counter = 0                        // Number of open parens
For char c in string:              
    print c                        
    if c=='{':                     // Keep track on number of open parens
        counter++
    if c=='}':
        counter--
    if counter==1:                 // New line if we're back to the top level
        print "\n"
    elif counter<1:                // Error if the nesting is malformed
        print "ERROR: parentheses mismatch"
        break

【讨论】:

  • 没错,但修复很简单。
【解决方案3】:

不幸的是,对于某种正则表达式,答案是肯定的,例如PCRE 和 .NET,因为它们分别支持递归模式和类似堆栈的操作。

语法可以写成

ELEMENT  -> (?!\{)\S+ | LIST
LIST     -> '\{\s*' ELEMENT? ('\s+' ELEMENT)* '\s*\}' 

因此在 PCRE 中,这可以转化为模式:

   \{\s*(?0)?(?:\s+(?0))*\s*\}|(?!\{)(?:[^\s}]|\}(?![\s}]))+

#  ---------------------------                   ^^^^^^^^^
#            LIST                    Make sure the } is not closing the group

例如见http://www.ideone.com/SnGsU(为了简单起见,我已经去掉了顶级{})。

(当然,不要在工作中尝试这个:))

(顺便说一句,我不知道如何将这个 PCRE 转换为 .NET 风格。如果有人知道,请尝试Converting PCRE recursive regex pattern to .NET balancing groups definition

【讨论】:

  • 哇!只有一个问题:在你的语法定义中,ELEMENT 开头的 (?!\{) 是什么意思?
  • @Cristi:这是一个negative lookahead
  • 我希望我可以选择两个答案作为“已接受的答案”,因为这个答案非常完整。但是,Kobi 的答案更适合我搜索的内容,而且 IMO 的正则表达式更具可读性。
【解决方案4】:

好吧,编辑从标记中删除了花括号并从问题中消除了刺痛,现在使用 .Net 正则表达式很容易实现,使用平衡组。它只是匹配大括号,这是一个基本示例。
就像 KennyTM 的回答一样,这仅在您删除顶级大括号时才有效,否则它将匹配整个输入。
同样,这更适合用于娱乐目的:

(?:                    # try matching...
    (?:\\[{}]|[^\s{}])+\s*? # a literal (allow escaped curly braces)
    |                       # OR
    (?<Curly>{)\s*          # "{" and push to stack
    |                       # OR
    (?<-Curly>})\s*?        # "}", pop from stack and fail if the stack is empty
)+?                    # ...a few times, and stop whenever you can.
(?(Curly)(?!))         # Make sure there aren't any extra open curly braces

更多详情请看这篇文章:Regex Balancing Group in Depth

【讨论】:

  • 在更新问题之前,我无法让它工作。在另一个问题上,我们从头到尾验证 (^(?:...)+$),因此引擎必须尝试每种组合。但是,当你匹配令牌时,引擎可以满足更少,并且很难设置优先级。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-19
  • 1970-01-01
  • 2011-08-03
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
相关资源
最近更新 更多