前瞻和后视概念如何在Ruby的Regex中支持这种零宽度断言概念?

Aru*_*hit 7 ruby regex ruby-1.9.3

我刚刚Zero-Width Assertions从文档中了解了这个概念.一些快速的问题进入我的脑海 -

  • 为何这样的名字Zero-Width Assertions
  • 如何Look-aheadlook-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"
Run Code Online (Sandbox Code Playgroud)

谁能帮助我在这里理解?

编辑

在这里,我尝试了两个片段,其中"Zero-Width Assertions"概念如下:

irb(main):002:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
Run Code Online (Sandbox Code Playgroud)

另一个没有"零宽度断言"概念如下:

irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"
Run Code Online (Sandbox Code Playgroud)

以上两者都产生相同的输出,现在内部如何regexp通过它们自己移动来产生输出 - 你能帮我形象化吗?

谢谢

Eev*_*vee 18

正则表达式从左到右匹配,并在字符串移动时移动一种"光标".如果你的正则表达式包含一个常规字符a,这意味着:"如果a光标前面有一个字母,将光标向前移动一个字符,继续前进.否则,出错了;备份并尝试别的东西." 所以你可能会说它a有一个字符的"宽度".

"零宽度断言"就是这样:它断言某些字符串(即,如果某些条件不成立则不匹配),但它不会向前移动光标,因为它的"宽度"为零.

你可能已经熟悉了一些更简单的零宽度断言,比如^$.这些匹配字符串的开头和结尾.如果光标在看到这些符号时不在开头或结尾,则正则表达式引擎将失败,备份并尝试其他操作.但它们实际上并没有向前移动光标,因为它们与字符不匹配; 它们只检查光标所在的位置.

Lookahead和lookbehind的工作方式相同.当正则表达式引擎尝试匹配它们时,它会检查光标周围以查看正确的模式是在其前面还是后面,但是在匹配的情况下,它不会移动光标.

考虑:

/(?=foo)foo/.match 'foo'
Run Code Online (Sandbox Code Playgroud)

这将匹配!正则表达式引擎是这样的:

  1. 从字符串的开头开始:|foo.
  2. 正则表达式的第一部分是(?=foo).这意味着:仅foo在光标后出现匹配.可以?嗯,是的,所以我们可以继续.但是光标不会移动,因为这是零宽度.我们还有|foo.
  3. 接下来是f.是否有一个f光标前面?是的,所以继续,并将光标移过f:f|oo.
  4. 接下来是o.是否有一个o光标前面?是的,所以继续,并将光标移过o:fo|o.
  5. 同样的事情,把我们带到了foo|.
  6. 我们到达正则表达式的末尾,没有任何失败,所以模式匹配.

特别是你的四个断言:

  • (?=...)是"向前看"; 它断言... 确实出现在光标之后.

    1.9.3p125 :002 > 'jump june'.gsub(/ju(?=m)/, 'slu')
     => "slump june" 
    
    Run Code Online (Sandbox Code Playgroud)

    "跳跃"中的"ju"匹配,因为接下来是"m".但是"六月"中的"ju"下一个没有"m",所以它一个人留下.

    由于它不移动光标,因此在放置光标后必须小心. (?=a)b将永远不会匹配任何东西,因为它检查下一个字符是什么a,然后检查相同的字符b,这是不可能的.

  • (?<=...)是"看守"; 它断言... 确实出现光标之前.

    1.9.3p125 :002 > 'four flour'.gsub(/(?<=f)our/, 'ive')
     => "five flour" 
    
    Run Code Online (Sandbox Code Playgroud)

    "四"中的"我们"匹配,因为它前面有一个"f",但"面粉"中的"我们"在它之前有一个"l",所以它不匹配.

    如上所述,你必须小心你摆面前的东西. a(?<=b)将永远不会匹配,因为它检查下一个字符是a,移动光标,然后检查前一个字符是什么b.

  • (?!...)是"消极的向前看"; 它声明在光标后... 没有出现.

    1.9.3p125 :003 > 'child children'.gsub(/child(?!ren)/, 'kid')
     => "kid children"
    
    Run Code Online (Sandbox Code Playgroud)

    "孩子"匹配,因为接下来的是一个空间,而不是"仁"."孩子"没有.

    这可能是我最常用的一个; 精细控制接下来不会发生的事情会派上用场.

  • (?<!...)是"消极的背后"; 它断言... 不会出现光标之前.

    1.9.3p125 :004 > 'foot root'.gsub(/(?<!r)oot/, 'eet')
     => "feet root" 
    
    Run Code Online (Sandbox Code Playgroud)

    "脚"中的"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"
Run Code Online (Sandbox Code Playgroud)

是的,这些产生相同的输出.使用前瞻这是一个棘手的问题:

  1. 正则表达式引擎已经尝试了一些东西,但是它们没有用,现在它已经完成了fores|ight.
  2. 它检查(?!s).是字符的光标s?不,是的i!因此该部分匹配并且匹配继续,但光标不移动,我们仍然有fores|ight.
  3. 它检查ight.ight光标之后会出现吗?嗯,是的,确实如此,所以移动光标:foresight|.
  4. 我们完成了!

光标移动到子字符串上ight,这样就完全匹配了,这就是被替换的内容.

这样做(?!a)b是没用的,因为你说:下一个字符不能a,它必须b.但这和匹配一样b!

这有时很有用,但是你需要一个更复杂的模式:例如,(?!3)\d匹配任何不是3的数字.

这就是你想要的:

1.9.3p125 :001 > "foresight".sub(/(?<!s)ight/, 'ee')
 => "foresight" 
Run Code Online (Sandbox Code Playgroud)

这断言以前s没有. ight


JDB*_*JDB 5

在你意识到正则表达式匹配位置和字符之前,零宽度断言很难理解.

当你看到字符串"foo"时,你自然会读到三个字符.但是,还有四个位置,这里用管道标记:"| f | o | o |".前瞻或后视(aka lookarounds)匹配角色匹配表达式之前或之后的位置.

零宽度表达式与其他表达式之间的区别在于零宽度表达式仅匹配(或"消耗")位置.所以,例如:

/(app)apple/
Run Code Online (Sandbox Code Playgroud)

将无法匹配"苹果",因为它试图匹配"应用程序"两次.但

/(?=app)apple/
Run Code Online (Sandbox Code Playgroud)

将成功,因为前瞻只匹配"app"所遵循的位置.它实际上与"app"字符不匹配,允许下一个表达式使用它们.

LOOKAROUND说明

积极前瞻: (?=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').不,他们没有,所以一切都很顺利.