【问题标题】:Tcl greedy subexpression difference between + and *+和*之间的Tcl贪婪子表达式差异
【发布时间】:2015-07-23 13:12:05
【问题描述】:

我试图理解 Tcl 子表达式匹配和“贪婪”,但完全不知道发生了什么。参考http://wiki.tcl.tk/396的示例:

%regexp -inline (.*?)(n+)(.*) ennui
en e n {} 
%regexp -inline ^(.*?)(n+)(.*)$ ennui
ennui e nn ui

尽管我并不完全理解“嵌套表达式”(这就是括号中的含义,对吗?)匹配,但我还是决定从小处着手,尝试将 * 和 + 作为贪婪运算符的区别:

% regexp -inline (.*)(u*)(.*) ennui
ennui ennui {} {}
% regexp -inline (.*)(u+)(.*) ennui
ennui enn u i

如果 * 匹配零个或多个,而 + 匹配一个或多个,我不明白这两个命令之间的输出差异。为什么 u* 和 u+ 在同一个字符串上会产生两个不同的结果?

我觉得这是一个非常重要的细微差别——如果我能掌握这个简单的模式匹配/正则表达式中发生的事情,我的生活就会变得完整。帮助!

提前致谢。

【问题讨论】:

    标签: regex tcl regex-greedy


    【解决方案1】:

    关于不贪心。 Tcl 正则表达式有一个怪癖:表达式中的 first 量词设置 whole 表达式的贪心度。 (请参阅re_syntax manual page 的“匹配”部分,密切关注“preference”一词):

    一个分支与其中第一个具有偏好的量化原子具有相同的偏好。

    %regexp -inline (.*?)(n+)(.*) ennui
    en e n {} 
    
    • (.*?) 抓取零个或多个字符,首选最短匹配项
    • (n+) 抓取一个或多个n,继承最短偏好
    • (.*) 抓取零个或多个字符,继承最短的偏好

    第一个子表达式匹配从第一个字符到但不包括第一个n。第二部分匹配一个n。第三部分匹配第一个和第二个n之间的零个字符。

    我有点惊讶第一个子表达式捕获e 而不是在第一个n 之前捕获零个字符,但这可以通过“最左”匹配到正则表达式引擎的更高优先级来解释:

    如果一个 RE 可以匹配给定字符串的多个子字符串,则 RE 匹配字符串中最早开始的那个。

    锚定表达式的结果也让我感到惊讶:我会期待e n nui 而不是e nn ui。添加$ 锚点似乎已经放弃了表达式对最短匹配的偏好。

    【讨论】:

      【解决方案2】:

      (.*)(u*)(.*)(.*)(u+)(.*) 不同的原因是第二个正则表达式至少需要1 个u

      Tcl 中的 ARE 正则表达式使用回溯(与大多数 NFA 一样)。使用(.*),引擎从头到尾抓取整个字符串,并开始回溯以查找它是否可以容纳下一个子模式。

      在第一个表达式中,u 是可选的(由于* 可以为0),因此,贪婪的.* 决定它不会产生任何字符。那么,最后一个.*也可以匹配0个字符,同样,不需要给那个组任何字符。

      在第二个表达式中,u 是强制性的,必须至少出现一次。因此,引擎会抓取第一个.* 的所有字符串,然后回溯并找到u。因此,它将起始序列放入第 1 组,并匹配并捕获 u(u+)。由于u 仅是 1,因此最后一个 (.*) 匹配并捕获字符串的其余部分。

      【讨论】:

      • 太棒了!你的解释很有启发性。自从阅读您的回复以来,我已经花了大约一个小时尝试不同的模式组合,而过去让我感到困惑的事情现在可以解释了。 感谢您的出色回复!
      • 如果您无法理解第一种情况(引用的混合贪心示例),请查看Interaction Between Quantifiers with Different Greediness分支中的所有量词都切换到相同的贪心,因此添加非贪婪量词使分支中的其他量词也隐含地非贪婪。 此外,您可能会发现这个Henry Spencer's post 感兴趣。
      【解决方案3】:

      @stribizhev 的回答几乎解释了一切。至于您的非贪婪版本 - 最后的问号告诉引擎它不应该消耗整个字符串,而是抓住尽可能少的匹配并从那里继续。

      • (.*?) for "ennui" 匹配 0 个字符,没关系,因为我们并不贪心
      • (n+) for "ennui" 匹配失败,因此引擎再次返回匹配 (.*?)
      • (.*?) for "ennui" 现在匹配一个字符 e
      • (n+) for "nnui" 匹配 nn,因为它很贪心
      • (.*) for "ui" 匹配剩下的,ui

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多