前瞻和后视概念如何在Ruby的Regex中支持这种零宽度断言概念?
我刚刚从文档中了解了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(/(? "foresee" irb(main):004:0> "foresight".sub(/(? "foresight"
谁能帮助我在这里理解?
编辑
在这里,我尝试了两个片段,其中“Zero-Width Assertions”概念如下:
irb(main):002:0> "foresight".sub(/(?!s)ight/, 'ee') => "foresee"
另一个没有“零宽度断言”概念如下:
irb(main):003:0> "foresight".sub(/ight/, 'ee') => "foresee"
以上两者都产生相同的输出,现在内部两个正则regexp
由它们自己移动以产生输出 – 你能帮我形象化吗?
谢谢
正则表达式从左到右匹配,并在字符串移动时移动一种“光标”。 如果你的正则表达式包含像a
这样的常规字符,这意味着:“如果光标前面有一个字母a
,将光标向前移动一个字符,继续前进。否则,出错了;备份并尝试别的东西。” 所以你可能会说a
有一个字符的“宽度”。
“零宽度断言”就是这样:它断言某些字符串(即,如果某些条件不成立则不匹配),但它不会向前移动光标,因为它的“宽度”为零。
你可能已经熟悉了一些更简单的零宽度断言,比如^
和$
。 这些匹配字符串的开头和结尾。 如果光标在看到这些符号时不在开头或结尾,则正则表达式引擎将失败,备份并尝试其他操作。 但它们实际上并没有向前移动光标,因为它们与字符不匹配; 它们只检查光标所在的位置。
Lookahead和lookbehind以同样的方式工作。 当正则表达式引擎尝试匹配它们时,它会检查光标周围以查看正确的模式是在其前面还是后面,但是在匹配的情况下,它不会移动光标。
考虑:
/(?=foo)foo/.match 'foo'
这将匹配! 正则表达式引擎是这样的:
- 从字符串的开头开始:
|foo
。 - 正则表达式的第一部分是
(?=foo)
。 这意味着:只有在光标后出现foo
时才匹配。 可以? 嗯,是的,所以我们可以继续。 但是光标不会移动 ,因为这是零宽度。 我们还有|foo
。 - 接下来是
f
。 光标前面有f
吗? 是的,所以继续,并将光标移过f
:f|oo
。 - 接下来是
o
。 光标前面有一个o
吗? 是的,所以继续,并将光标移过o
:fo|o
。 - 同样的事情,把我们带到了
foo|
。 - 我们到了正则表达式的末尾,没有任何失败,所以模式匹配。
特别是你的四个断言:
-
(?=...)
是“向前看”; 它断言...
确实出现在光标之后。1.9.3p125 :002 > 'jump june'.gsub(/ju(?=m)/, 'slu') => "slump june"
“跳跃”中的“ju”匹配,因为接下来是“m”。 但是“六月”中的“ju”下一个没有“m”,所以它一个人留下。
由于它不会移动光标,因此在放置光标后必须小心。
(?=a)b
永远不会匹配任何东西,因为它检查下一个字符是否为a
,然后还检查相同的字符是否为b
,这是不可能的。 -
(?<=...)
是“lookbehind”; 它断言...
确实出现在光标之前 。1.9.3p125 :002 > 'four flour'.gsub(/(?<=f)our/, 'ive') => "five flour"
“我们”在“四个”比赛中因为在它之前有一个“f”,但“面粉”中的“我们”在它之前有一个“l”因此它不匹配。
如上所述,你必须小心你摆在它面前的东西。
a(?<=b)
永远不会匹配,因为它检查下一个字符是否为a
,移动光标,然后检查前一个字符是否为b
。 -
(?!...)
是“负向前瞻”; 它断言...
光标后没有出现。1.9.3p125 :003 > 'child children'.gsub(/child(?!ren)/, 'kid') => "kid children"
“孩子”匹配,因为接下来的是一个空间,而不是“仁”。 “孩子”没有。
这可能是我最常用的一个; 精细控制接下来不会发生的事情会派上用场。
-
(?是“负面的背后”; 它断言
...
没有出现在光标之前 。1.9.3p125 :004 > 'foot root'.gsub(/(? "feet root"
“脚”中的“oot”很好,因为它之前没有“r”。 “根”中的“oot”显然具有“r”。
作为额外限制,大多数正则表达式引擎要求
...
在这种情况下具有固定长度。 所以你不能用?
,+
,*
或{n,m}
。
你也可以嵌套这些,否则做各种疯狂的事情。 我把它们主要用于一次性我知道我永远不需要维护,所以我没有任何实用的应用程序的好例子; 老实说,他们很奇怪,你应该先尝试做你想要的其他方式。 🙂
事后想法:语法来自Perl正则表达式 ,它使用(?
后面跟着各种符号用于很多扩展语法因为?
本身是无效的。所以<=
并不意味着什么本身; (?<=
是一整个令牌,意思是“这是一个外观的开始”。就像+=
和++
是单独的运算符一样,即使它们都以+
开头。
它们很容易记住,但是: =
表示向前看(或者,真的,“在这里”), <
表示向后看,并且!
具有“不”的传统含义。
关于你后来的例子:
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
。 光标后会出现吗? 嗯,是的,确实如此,所以移动光标:foresight|
。 - 我们完成了!
光标移动到子串ight
,因此这是完全匹配,这就是被替换的内容。
做(?!a)b
是没用的,因为你说:下一个字符不能是a
,它必须是b
。 但这与匹配b
!
这有时很有用,但是你需要一个更复杂的模式:例如, (?!3)\d
将匹配任何不是3的数字。
这就是你想要的:
1.9.3p125 :001 > "foresight".sub(/(? "foresight"
这断言s
不会在之前出现 。
在你意识到正则表达式匹配位置和字符之前,零宽度断言很难理解。
当你看到字符串“foo”时,你自然会读到三个字符。 但是,还有四个位置 ,这里用管道标记:“| f | o | o |”。 前瞻或后瞻(aka lookarounds)匹配角色匹配表达式之前或之后的位置。
零宽度表达式与其他表达式之间的区别在于零宽度表达式仅匹配(或“消耗”)位置。 所以,例如:
/(app)apple/
将无法匹配“apple”,因为它试图匹配“app”两次。 但
/(?=app)apple/
将成功,因为前瞻只匹配“app”所在的位置 。 它实际上与“app”字符不匹配,允许下一个表达式使用它们。
查看说明
积极前瞻:
(?=s)
想象一下,你是一名训练中士,你正在进行检查。 你从行前开始,意图走过每个私人,并确保他们满足期望。 但是,在这样做之前,你一个接一个地向前看,以确保他们已经在房产订单中排队。 私人的名字是“A”,“B”,“C”,“D”和“E”。
/(?=ABCDE)...../.match('ABCDE')
。 是的,他们都在场并且占了一席之地。否定前瞻:(?!
(?!s)
你在线下进行检查,最后站在私人D.现在你要向前看,以确保来自另一家公司的“F”没有再次意外地陷入错误的阵型。
/.....(?!F)/.match('ABCDE')
。 不,他这次没有滑倒,所以一切都很顺利。积极的观察:
(?<=s)
完成检查后,中士在编队结束时。 他转身扫描,确保没有人偷偷溜走。
/.....(?<=ABCDE)/.match('ABCDE')
。 是的,每个人都在场,并且占了一席之地。负面观察:
(?
最后,训练中士最后一次检查以确保私人A和B没有再次切换位置(因为他们喜欢KP)。
/.....(? 。 不,他们没有,所以一切都很顺利。
零宽度断言的含义是在匹配时消耗零个字符的表达式。 例如,在此示例中,
"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
永远不是一个,所以替换失败了。
irb(main):003:0> "foresight".sub(/(?<=s)ight/, 'ee') => "foresee"
上面,已经解释过了。 (这是正确的。)
irb(main):004:0> "foresight".sub(/(? "foresight"
以上,现在应该清楚了。 在这种情况下,“交火”将代替“firefee”,而不是“远见”代替“预见”。