我刚刚从文档中了解了Zero-Width Assertions
这个概念。一些快速问题进入我的脑海 -
Zero-Width Assertions
? Look-ahead
和look-behind
概念如何支持
Zero-Width Assertions
概念? ?<=s
,<!s
,=s
,<=s
- 4个符号在模式中指示了什么?你能帮助我在这里专注于了解实际情况 我还尝试了一些微小的代码来理解逻辑,但对那些输出没有那么自信:
irb(main):001:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
irb(main):002:0> "foresight".sub(/(?=s)ight/, 'ee')
=> "foresight"
irb(main):003:0> "foresight".sub(/(?<=s)ight/, 'ee')
=> "foresee"
irb(main):004:0> "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"
有人可以帮助我理解吗?
修改
在这里,我尝试了两个片段,其中包含“零宽度断言”概念,如下所示:
irb(main):002:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
,另一个没有“零宽度断言”概念如下:
irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"
以上两者都产生相同的输出,现在内部如何regexp
移动它们自己产生输出 - 你可以帮我形象化吗?
由于
答案 0 :(得分:18)
正则表达式从左到右匹配,并移动一种&#34;光标&#34;沿着弦走了。如果正则表达式包含常规字符a
,则表示:&#34;如果光标前面有一个字母a
,请将光标向前移动一个字符,然后继续前进。否则,出了点问题;备份并尝试别的东西。&#34;所以你可能会说a
有一个&#34;宽度&#34;一个角色。
A&#34;零宽度断言&#34;就是这样:它断言关于字符串的东西(即,如果某些条件不能保持不匹配),但它不会向前移动光标,因为它的宽度&#34;是零。
您可能已经熟悉了一些更简单的零宽度断言,例如^
和$
。这些匹配字符串的开头和结尾。如果光标在开始或结束时看到这些符号,则正则表达式引擎将失败,备份并尝试其他操作。但他们实际上并没有向前移动光标,因为它们不匹配字符;它们只检查光标所在的位置。
Lookahead和lookbehind工作方式相同。当正则表达式引擎尝试匹配它们时,它会检查周围光标以查看正确的模式是在其前面还是后面,但是如果匹配,它不会移动光标
考虑:
/(?=foo)foo/.match 'foo'
这将匹配!正则表达式引擎是这样的:
|foo
。(?=foo)
。这意味着:仅在光标后出现foo
时匹配。可以?嗯,是的,所以我们可以继续。但是光标不会移动,因为这是零宽度。我们仍有|foo
。f
。光标前面有f
吗?是的,请继续,将光标移过f
:f|oo
。o
。光标前面有o
吗?是的,请继续,将光标移过o
:fo|o
。foo|
。特别是你的四个断言:
(?=...)
&#34; lookahead&#34 ;;它断言...
出现在光标之后。
1.9.3p125 :002 > 'jump june'.gsub(/ju(?=m)/, 'slu')
=> "slump june"
&#34; ju&#34; in&#34; jump&#34;匹配,因为&#34; m&#34;接下来。但是&#34; ju&#34;在&#34; 6月&#34;没有&#34; m&#34;接下来,所以它一个人留下。
由于它没有移动光标,因此在放置光标后必须小心。 (?=a)b
永远不会匹配任何内容,因为它检查下一个字符是a
,然后还检查相同的字符是否为{{1} },这是不可能的。
b
&#34; lookbehind&#34 ;;它断言(?<=...)
在光标之前 。
...
&#34;我们的&#34;在&#34;四&#34;匹配,因为有&#34; f&#34;在它之前,但是&#34;我们的&#34;在&#34;面粉&#34;有一个&#34; l&#34;在它之前,所以它不匹配。
如上所述,您必须小心之前的内容。 1.9.3p125 :002 > 'four flour'.gsub(/(?<=f)our/, 'ive')
=> "five flour"
永远不会匹配,因为它检查下一个字符是a(?<=b)
,移动光标,然后检查前一个字符是a
。
b
是&#34;否定前瞻&#34 ;;它声称(?!...)
不会出现在光标之后。
...
&#34;子&#34;匹配,因为接下来的是一个空间,而不是&#34; ren&#34;。 &#34;的子&#34;没有按&#39;吨
这可能是我最常用的一个;精确控制接下来会发生什么事情就派上用场了。
1.9.3p125 :003 > 'child children'.gsub(/child(?!ren)/, 'kid')
=> "kid children"
是&#34;负面的背后&#34 ;;它断言<{1}} 在光标之前 。
(?<!...)
&#34; oot&#34; in&#34; foot&#34;很好,因为没有&#34; r&#34;在它之前。 &#34; oot&#34; in&#34; root&#34;显然有一个&#34; r&#34;。
作为附加限制,大多数正则表达式引擎要求...
在这种情况下具有固定长度。因此,您无法使用1.9.3p125 :004 > 'foot root'.gsub(/(?<!r)oot/, 'eet')
=> "feet root"
,...
,?
或+
。
你也可以嵌套这些,否则做各种疯狂的事情。我将它们主要用于一次性我知道我永远不需要维护,所以我没有任何实用应用程序的好例子;老实说,他们很奇怪,你应该先尝试以其他方式做你想做的事。 :)
事后考虑:语法来自Perl regular expressions,后者使用*
后跟各种符号进行大量扩展语法,因为{n,m}
本身无效。所以(?
本身并不意味着任何事情; ?
是一个完整的标记,意思是&#34;这是一个后视的开始&#34;。它就像<=
和(?<=
是独立的运算符一样,即使它们都以+=
开头。
他们很容易记住,但是:++
表示向前看(或者,真的,&#34;此处&#34;),+
表示向后看,{{1它的传统含义是&#34;不是&#34;。
关于你后面的例子:
=
是的,这些产生相同的输出。这是使用前瞻的那个棘手的问题:
<
。!
。 光标irb(main):002:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"
后的字符是吗?不,它是fores|ight
!因此该部分匹配并且匹配继续,但光标不移动,我们仍然有(?!s)
。s
。 i
是否在光标之后?嗯,是的,确实如此,所以移动光标:fores|ight
。光标移动到子串ight
上方,以便完全匹配,以及更换的内容。
执行ight
是没用的,因为您说:下一个字符不能为foresight|
,而必须 ight
。但这与仅匹配(?!a)b
!
这有时很有用,但您需要更复杂的模式:例如,a
将匹配任何不是3的数字。
这就是你想要的:
b
这声称b
在 (?!3)\d
之前没有。
答案 1 :(得分:5)
在您发现正则表达式与位置以及字符匹配之前,很难理解零宽度断言。
当你看到字符串“foo”时,你自然会读到三个字符。但是,还有四个位置,这里用管道标记:“| f | o | o |”。前瞻或后瞻(aka lookarounds)匹配角色在表达式之前或之后的位置。
零宽度表达式与其他表达式之间的区别在于零宽度表达式仅匹配(或“消耗”)位置。所以,例如:
/(app)apple/
将无法匹配“apple”,因为它试图匹配“app”两次。但
/(?=app)apple/
将成功,因为前瞻只匹配位置,其中“app”跟随。它实际上与“app”字符不匹配,允许下一个表达式使用它们。
LOOKAROUND DESCRIPTIONS
积极前瞻:
(?=s)
想象一下,你是一名训练中士,而且你正在进行检查。你从这条线的前面开始,意图走过每个私人,并确保他们满足期望。但是,在这样做之前,你要一个接一个地向前看,以确保他们已经在房产订单中排队。私人的名字是“A”,“B”,“C”,“D”和“E”。
/(?=ABCDE)...../.match('ABCDE')
。是的,他们都在场并且占了一席之地。否定前瞻:
(?!s)
你在线下进行检查并最终站在私人D处。现在你要向前看以确保来自另一家公司的“F”没有再次意外地滑入错误的阵型。
/.....(?!F)/.match('ABCDE')
。不,他这次没有滑倒,所以一切都很好。积极的观察:
(?<=s)
在完成检查后,中士在编队结束时。他转身扫描,确保没有人偷偷溜走。
/.....(?<=ABCDE)/.match('ABCDE')
。是的,每个人都在场并且占了上风。负面反对:
(?<!s)
最后,训练中士最后一次检查以确保私人A和B没有再次切换位置(因为他们喜欢KP)。
/.....(?<!BACDE)/.match('ABCDE')
。不,他们没有,所以一切都很好。
答案 2 :(得分:2)
零宽度断言的含义是在匹配时消耗零个字符的表达式。例如,在此示例中,
"foresight".sub(/sight/, 'ee')
匹配的是
foresight
^^^^^
因此结果将是
foreee
但是,在这个例子中,
"foresight".sub(/(?<=s)ight/, 'ee')
匹配的是
foresight
^^^^
因此结果将是
foresee
零宽度断言的另一个例子是字边界字符\b
。例如,要匹配一个完整的单词,您可以尝试用空格围绕单词,例如
"flight light plight".sub(/\slight\s/, 'dark')
获取
flightdarkplight
但你知道在替换过程中空格的匹配是如何消除的吗?使用单词边界解决了这个问题:
"flight light plight".sub(/\blight\b/, 'dark')
\b
匹配单词的开头或结尾,但实际上并不匹配字符:它是零宽度。
对你的问题最简洁的答案可能就是: Lookahead和lookbehind断言是零宽度断言的一种。所有前瞻和后瞻断言都是零宽度断言。
以下是您的示例的解释:
irb(main):001:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
上面,您说“匹配下一个字符不 s
,然后是i
。”对于i
,始终为真,因为i
永远不是s
,因此替换成功。
irb(main):002:0> "foresight".sub(/(?=s)ight/, 'ee')
=> "foresight"
上面,您说“匹配下一个字符 s
,然后是i
。”这是从不为真,因为i
永远不是s
,因此替换失败。
irb(main):003:0> "foresight".sub(/(?<=s)ight/, 'ee')
=> "foresee"
上面,已经解释过了。 (这是正确的。)
irb(main):004:0> "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"
上面,现在应该清楚了。在这种情况下,“交火”将取代“firefee”,而不是“预见”到“预见”。