gpt4 book ai didi

regex - 我们需要 Lookahead/Lookbehind 零宽度断言做什么?

转载 作者:行者123 更新时间:2023-12-03 11:01:06 24 4
gpt4 key购买 nike

我刚刚更详细地了解了这两个概念。我一直对 RegEx 很好,似乎我从未见过这 2 个零宽度断言的必要性。

我很确定我错了,但我不明白为什么需要这些结构。考虑这个例子:

Match a 'q' which is not followed by a 'u'.

2个字符串将是输入:
Iraq
quit

使用负前瞻,正则表达式如下所示:
q(?!u)

没有它,它看起来像这样:
q[^u]

对于给定的输入,这两个正则表达式给出相同的结果(即匹配 Iraq 但不匹配 quit)(用 perl 测试)。同样的想法也适用于后视。

我是否错过了使这些断言比经典语法更有值(value)的关键功能?

最佳答案

为什么您的测试可能有效(以及为什么它不应该)

您能够匹配的原因 Iraq在您的测试中,您的字符串可能包含 \n最后(例如,如果您从 shell 中读取它)。如果您有一个以 q 结尾的字符串,然后 q[^u]不能像其他人说的那样匹配,因为[^u]匹配非 u性格 - 但关键是必须有一个性格。

我们实际上需要环顾什么?

显然,在上述情况下,前瞻并不重要。您可以使用 q(?:[^u]|$) 解决此问题.所以我们只匹配 q后跟一个非 u字符或字符串的结尾。不过,前瞻有更复杂的用途,如果你在没有前瞻的情况下使用它们会变得很痛苦。

这个答案试图概述一些重要的标准情况,这些情况最好通过环视来解决。

让我们从查看带引号的字符串开始。匹配它们的常用方法是使用类似 "[^"]*" 的东西。 (不适用于 ".*?" )。开盘后" ,我们只是重复尽可能多的非引号字符,然后匹配结束引号。同样,否定的字符类非常好。但在某些情况下,否定字符类不会削减它:

多字符分隔符

现在如果我们没有双引号来分隔我们感兴趣的子字符串,而是一个多字符分隔符呢?例如,我们正在寻找 ---sometext--- ,其中单双-允许在 sometext 内.现在你不能只使用 [^-]* ,因为那将禁止单 - .标准技术是在每个位置使用负前瞻,并且只消耗下一个字符,如果它不是 --- 的开头。 .像这样:

---(?:(?!---).)*---

如果您以前没有见过它,这可能看起来有点复杂,但它肯定比替代方案更好(并且通常更有效)。

不同的分隔符

您会遇到类似的情况,其中您的分隔符只有一个字符,但可能是两个(或多个)不同字符之一。例如,在我们最初的示例中,我们希望允许单引号和双引号字符串。当然,您可以使用 '[^']*'|"[^"]*" ,但最好在没有替代方案的情况下处理这两种情况。使用反向引用可以轻松处理周围的引号: (['"])[^'"]*\1 .这确保匹配以它开始的相同字符结束。但是现在我们太严格了 - 我们想允许 "在单引号和 '在双引号字符串中。类似 [^\1]不起作用,因为反向引用通常包含多个字符。所以我们使用与上面相同的技术:
(['"])(?:(?!\1).)*\1

那是在开始引号之后,在使用每个字符之前,我们确保它与开始字符不同。我们尽可能长时间地这样做,然后再次匹配开始字符。

重叠匹配

这是一个(完全不同的)问题,如果没有环视,通常根本无法解决。如果您在全局范围内搜索匹配项(或想要在全局范围内使用正则表达式替换某些内容),您可能已经注意到匹配项永远不会重叠。 IE。如果您搜索 ...abcdefghi你得到 abc , def , ghi而不是 bcd , cde等等。如果您想确保您的匹配项之前(或包围)其他内容,这可能会成为问题。

假设您有一个 CSV 文件,例如
aaa,111,bbb,222,333,ccc

并且您只想提取完全数字的字段。为简单起见,我假设任何地方都没有前导或尾随空格。如果没有环顾四周,我们可能会进行捕获并尝试:
(?:^|,)(\d+)(?:,|$)

所以我们确保我们有一个字段的开头(字符串的开头或 ,),然后只有数字,然后是字段的结尾( , 或字符串的结尾)。在此之间,我们将数字捕获到组 1 中.不幸的是,这不会给我们 333在上面的例子中,因为 ,在它之前已经是比赛的一部分 ,222, - 并且匹配不能重叠。环视解决问题:
(?<=^|,)\d+(?=,|$)

或者,如果您更喜欢双重否定而不是交替,这相当于
(?<![^,])\d+(?![^,])

除了能够获得所有匹配项之外,我们还摆脱了通常可以提高性能的捕获。 (感谢 Adrian Pronk 的这个例子。)

多个独立条件

何时使用环视(特别是超前)的另一个非常经典的例子是当我们想要同时检查输入的多个条件时。假设我们想编写一个正则表达式来确保我们的输入包含一个数字、一个小写字母、一个大写字母、一个不是这些字符的字符,并且没有空格(例如,为了密码安全)。如果没有环视,您必须考虑数字、小写/大写字母和符号的所有排列。喜欢:
\S*\d\S*[a-z]\S*[A-Z]\S*[^0-9a-zA_Z]\S*|\S*\d\S*[A-Z]\S*[a-z]\S*[^0-9a-zA_Z]\S*|...

这些只是 24 个必要排列中的两个。如果您还想确保同一正则表达式中的最小字符串长度,则必须将它们分布在 \S* 的所有可能组合中。 - 在单个正则表达式中根本不可能做到。

向前看救援!我们可以简单地在字符串的开头使用几个前瞻来检查所有这些条件:
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^0-9a-zA-Z])(?!.*\s)

因为前瞻实际上不消耗任何东西,所以在检查每个条件后,引擎重置到字符串的开头并可以开始查看下一个。如果我们想添加最小字符串长度(比如 8 ),我们可以简单地附加 (?=.{8}) .更简单,更易读,更易于维护。

重要提示:这是 不是 在任何实际环境中检查这些条件的最佳通用方法。如果您以编程方式进行检查,通常最好为每个条件使用一个正则表达式,并分别检查它们 - 这让您返回一条更有用的错误消息。但是,如果您有一些固定的框架允许您仅通过提供单个正则表达式进行验证,则上述内容有时是必要的。此外,如果您有独立的字符串匹配标准,那么了解通用技术也是值得的。

我希望这些例子能让你更好地了解人们为什么喜欢使用环视。还有更多的应用程序(另一个经典是 inserting commas into numbers ),但重要的是您要意识到 (?!u) 之间存在差异。和 [^u]并且在某些情况下,否定字符类根本不够强大。

关于regex - 我们需要 Lookahead/Lookbehind 零宽度断言做什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17105943/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com