调和贪婪令牌 - 把点放在负向先行断言之前有何不同?

21
<table((?!</table>).)*</table>

匹配所有我的表格标签。然而,

<table(.(?!</table>))*</table>

不行。如果我试着用语言文字表达这个表达式,第二个似乎是有意义的,但我无法理解第一个。

它们的区别是什么?

参考资料:我从这里获得了“Tempered Greedy Token”这个术语:Tempered Greedy Token Solution


顺便提一下,注意这种“调和”的方式特别低效。 - Casimir et Hippolyte
1
然后..他编了一个。实际上,在正则表达式领域,这不是标准行话。如果我进行投票,我敢打赌99%的正则表达式专家会嘲笑它。 - user557597
1
那个网站的作者对我来说似乎很牛逼。此外,我认为给模式(正则表达式或其他)命名是有帮助的。我只会让大牛们笑而不语。顺便问一下,这种模式有更标准的名称吗? - jmrah
2
抱歉,但我想问您和您问我同样的问题:您是不是刚刚编造了“记录分隔符构造”这个名词? 我怀疑这比“温和贪婪的令牌”更标准。事实上,谷歌搜索也没有找到任何相关结果。 - jmrah
2
你有权发表自己的观点,感谢你的分享!我个人认为对构建命名很有价值,所以在这一点上不同意您的看法。此外,我并没有打算推销任何东西作为一种黄金标准。这只是一个直截了当、无心的问题而已。但即使我有意这样做,我也不认为有什么问题。如果我的言论引起了一些不快,我很抱歉。我建议您用列出的所有要点来回答问题。这可能是在保持对话不偏离主题的情况下最有帮助的事情。你怎么想? - jmrah
显示剩余8条评论
3个回答

64

什么是"Tempered Greedy Token"?

rexegg.com tempered greedy token 的参考资料相当简洁:

(?:(?!{END}).)* 中,* 量词应用于点,但现在它是一个tempered点。负的前瞻性 (?!{END}) 断言跟随当前位置的内容不是字符串 {END}。因此,点不能匹配 {END} 的大括号,保证我们不会跳过 {END} 分隔符。

就是这样: "tempered greedy token" 是一种适用于字符序列否定字符类(与适用于单个字符否定字符类相对比)。

注意:具有限制贪婪符号的正则表达式和带否定字符类的正则表达式之间的区别在于前者不会实际匹配除了序列本身之外的文本,而是匹配以该序列开头的单个字符。即(?:(?!abc|xyz).)+不会在defabc中匹配def,但会匹配defbc,因为a开始了被禁止的abc序列,而bc没有。

它由以下部分组成:

  • (?:...)* - 一个量化的非捕获组(它可以是一个捕获组,但捕获每个单独字符没有意义)(*可以是+,这取决于是否期望匹配空字符串)
  • (?!...) - 一个负向前瞻,实际上对当前位置右侧的值施加限制
  • . - (或任何(通常单个)字符)一个消耗型模式。

然而,我们总是可以进一步通过在负向前瞻中使用交替项(例如 (?!{(?:END|START|MID)}))或通过用否定字符类替换全匹配点(例如 (?:(?!START|END|MID)[^<>]) 当尝试仅匹配标签内文本时)来调整令牌。

消耗部分的放置

请注意,原始的贪婪模式中没有提到将消耗部分(原始温和的贪婪标记中的点)放置在前瞻之前。Avinash的答案清楚地解释了这一部分:(.(?!</table>))* 首先匹配任何字符(但在没有DOTALL修饰符的情况下不包括换行符),然后检查它是否不跟随 </table>,导致无法匹配 <table>table</table> 中的 e。*消耗部分(.必须放置在温和的前瞻之后

什么时候应该使用温和的贪婪标记?

Rexegg.com 提供了一个思路:

  • 当我们想要匹配两个定界符之间没有子字符串3的文本块时(例如{START}(?:(?!{(?:MID|RESTART)}).)*?{END}
  • 当我们想要匹配包含特定模式的文本块,但不会溢出到后续的文本块中(例如,与其使用懒惰点匹配,如<table>.*?chair.*?</table>,我们会使用类似于 <table>(?:(?!chair|</?table>).)*chair(?:(?!<table>).)*</table>)。
  • 当我们想要在两个字符串之间匹配尽可能短的窗口时。当您需要从abc 1 abc 2 xyz获取abc 2 xyz时,懒惰匹配是无法帮助您的(请参见abc.*?xyzabc(?:(?!abc).)*?xyz)。

性能问题

温和贪婪模式消耗资源,因为在匹配消耗模式的每个字符后,都会执行一个前瞻检查。展开循环技术可以显著提高温和贪婪模式的性能。

假设我们要在 abc 1 abc 2 xyz 3 xyz 中匹配 abc 2 xyz。与其使用 abc(?:(?!abc|xyz).)*xyz 检查 abcxyz 之间的每个字符,我们可以使用 [^ax]* 跳过所有不是 ax 的字符,然后匹配所有不带有 bc 后缀的 a(使用 a(?!bc))以及所有不带有 yz 后缀的 x(使用 x(?!yz)):abc[^ax]*(?:a(?!bc)[^ax]*|x(?!yz)[^ax]*)*xyz


3
在评论区重新被提及后,我决定接受你详尽的回答。感谢你花时间整理这些内容。 - jmrah

10

((?!</table>).)*会检查要匹配的特定字符是否不是字符串</table>的起始字符。如果是,则仅匹配该特定字符。*重复零次或多次。

(.(?!</table>))*仅在其后没有</table>时,零次或多次匹配任何字符。因此,这将匹配表标记内的所有字符,除了最后一个字符,因为最后一个字符后面跟着</table>。以下模式</table>断言必须在匹配结尾处有一个关闭表格标记。这使得匹配失败。

请参见这里


我仍然在努力理解第一段。好消息是,我理解了你关于为什么 (.(?!</table>))* 会失败的解释。编辑:哦,啊,我现在想我明白了! - jmrah

6

温和的贪婪令牌实际上意味着:

“匹配,但仅限于某一点”

如何实现:

在点(匹配任何一个字符)前面放置否定前瞻(?!notAllowedToMatch)来表示您不希望匹配的标记,然后使用星号*重复整个过程

((?!notAllowedToMatch).)*

工作原理:

“看一下,吃掉一个”, 不断地从左到右移动一个字符, 直到看到不允许的序列(或字符串结束) ,此时匹配停止。

Wiktor的更详细的答案很好,但我认为需要一个更简单的解释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接