我刚刚经历了这个概念 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
他们自己移动以产生输出 - 你能帮我想象一下吗?谢谢
最佳答案
正则表达式从左到右匹配,并在它们移动时沿字符串移动某种“光标”。如果您的正则表达式包含像 a
这样的常规字符,这意味着:“如果光标前面有一个字母 a
,请将光标向前移动一个字符,然后继续。否则,出现问题;备份并尝试其他操作。”所以你可能会说 a
有一个字符的“宽度”。
“零宽度断言”就是这样:它断言关于字符串的某些内容(即,如果某些条件不成立则不匹配),但它不会向前移动光标,因为它的“宽度”为零.
您可能已经熟悉一些更简单的零宽度断言,例如 ^
和 $
.这些匹配字符串的开头和结尾。如果光标在看到这些符号时不在开头或结尾,则正则表达式引擎将失败、备份并尝试其他操作。但它们实际上并没有向前移动光标,因为它们不匹配字符;他们只检查光标在哪里。
前瞻和后视的工作方式相同。当正则表达式引擎尝试匹配它们时,它会围绕光标检查正确的模式是在它的前面还是后面,但在匹配的情况下,它不会移动光标。
考虑:
/(?=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"
“jump”中的“ju”匹配,因为接下来是“m”。但是“june”中的“ju”下一个没有“m”,所以它是单独存在的。
由于它不会移动光标,因此在其后放置任何内容时必须小心。
(?=a)b
永远不会匹配任何东西,因为它检查下一个字符是 a
,然后还要检查相同的字符是否为 b
,这是不可能的。 (?<=...)
是“后视”;它断言 ...
确实出现在光标之前。1.9.3p125 :002 > 'four flour'.gsub(/(?<=f)our/, 'ive')
=> "five flour"
“four”中的“our”匹配,因为在它之前有一个“f”,但“flour”中的“our”在它之前有一个“l”,所以它不匹配。
像上面一样,您必须小心放置在它之前的内容。
a(?<=b)
永远不会匹配,因为它检查下一个字符是 a
, 移动光标,然后检查前一个字符是否为 b
. (?!...)
是“负前瞻”;它断言 ...
不会出现在光标之后。1.9.3p125 :003 > 'child children'.gsub(/child(?!ren)/, 'kid')
=> "kid children"
“child”匹配,因为接下来是一个空格,而不是“ren”。 “ child ”没有。
这可能是我最常用的一种;精细控制接下来不能发生的事情就派上用场了。
(?<!...)
是“负面回顾”;它断言 ...
不会出现在光标之前。1.9.3p125 :004 > 'foot root'.gsub(/(?<!r)oot/, 'eet')
=> "feet root"
“foot”中的“oot”很好,因为它前面没有“r”。 “root”中的“oot”显然有一个“r”。
作为额外的限制,大多数正则表达式引擎要求
...
在这种情况下具有固定长度。所以你不能使用 ?
, +
, *
, 或 {n,m}
. 你也可以嵌套这些,否则做各种疯狂的事情。我主要将它们用于我知道永远不必维护的一次性使用,因此我没有任何实用的实际应用程序示例;老实说,它们很奇怪,您应该先尝试以其他方式做您想做的事。 :)
事后思考:语法来自 Perl regular expressions ,其中使用了
(?
后面是许多扩展语法的各种符号,因为 ?
本身是无效的。所以<=
本身没有任何意义; (?<=
是一个完整的标记,意思是“这是回顾的开始”。就像如何+=
和 ++
是独立的运算符,即使它们都以 +
开头.不过,它们很容易记住:
=
表示向前看(或者,实际上,“这里”),<
表示向后看,!
有“不”的传统含义。关于你后面的例子:
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
,而且必须是 b
.但这与仅匹配 b
相同!这有时很有用,但您需要更复杂的模式:例如,
(?!3)\d
将匹配任何不是 3 的数字。这就是你想要的:
1.9.3p125 :001 > "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"
这断言
s
之前不来ight
.
关于ruby - 在 Ruby 的正则表达式中,前瞻和后视概念如何支持这种零宽度断言概念?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14387631/