8

Zero-Width Assertionsドキュメントからコンセプトを確認しました。そして、いくつかの簡単な質問が私の頭に浮かびます-

  • なぜそのような名前Zero-Width Assertions
  • Look-aheadandlook-behindコンセプトはそのような コンセプトをどのようにサポートしZero-Width Assertionsますか?
  • そのような?<=s、、、<!s- 4つのシンボルがパターン内で指示していますか=s<=sここで私が実際に何が起こっているのかを理解するために集中するのを手伝ってくれませんか

また、ロジックを理解するためにいくつかの小さなコードを試しましたが、それらの出力にはそれほど自信がありませんでした。

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"

誰かが私がここで理解するのを手伝ってもらえますか?

編集

ここでは、以下のように「ゼロ幅アサーション」の概念を持つ2つのスニペットを試しました。

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

もう1つは、以下のような「ゼロ幅アサーション」の概念がない場合です。

irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"

上記の両方が同じ出力を生成しますが、内部的には両方がどのようにregexp動き、出力を生成しますか?視覚化するのを手伝ってもらえますか?

ありがとう

4

3 に答える 3

18

正規表現は左から右に一致し、文字列に沿って一種の「カーソル」を移動します。正規表現に のような通常の文字が含まれている場合a、これは次のことを意味します。「aカーソルの前に文字がある場合は、カーソルを 1 文字前に移動して続行します。それ以外の場合は、何か問題があります。戻って別のことを試してください。」aしたがって、1文字の「幅」があると言うかもしれません。

「ゼロ幅アサーション」はまさにそれです:文字列について何かをアサートします (つまり、何らかの条件が満たされない場合は一致しません) が、「幅」がゼロであるため、カーソルを前方に移動しません。 .

^やのような単純なゼロ幅アサーションについては、おそらく既によく知っているでしょう$。これらは、文字列の開始と終了に一致します。これらのシンボルが表示されたときにカーソルが最初または最後にない場合、正規表現エンジンは失敗し、バックアップして、別のことを試みます。ただし、文字と一致しないため、実際にはカーソルを前方に移動しません。カーソルがどこにあるかを確認するだけです。

先読みと後読みは同じように機能します。正規表現エンジンがそれらを一致させようとすると、カーソルの周りをチェックして正しいパターンがその前後にあるかどうかを確認しますが、一致する場合はカーソルを移動しません。

検討:

/(?=foo)foo/.match 'foo'

これなら合う!正規表現エンジンは次のようになります。

  1. 文字列の先頭から開始します: |foo.
  2. 正規表現の最初の部分は(?=foo). これはfoo、カーソルの後にある場合にのみ一致することを意味します。そうですか?ええ、はい、それで先に進みましょう。しかし、これはゼロ幅であるため、カーソルは移動しません。まだあります|foo
  3. 次はffカーソルの前に がありますか? はい、続行して、カーソルを : の先に移動しfますf|oo
  4. 次はooカーソルの前に がありますか? はい、続行して、カーソルを : の先に移動しoますfo|o
  5. 再び同じことが起こりfoo|ます。
  6. 正規表現の最後に到達し、何も失敗しなかったため、パターンが一致しました。

特にあなたの4つの主張について:

  • (?=...)「先読み」です。カーソルの後に表示されることをアサートし... ます。

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

    「jump」の「じゅ」は、次に「m」が来るのでマッチします。しかし、「june」の「ju」には次の「m」がないのでそのままです。

    カーソルを動かさないので、後ろに何かを入れるときは注意が必要です。 は、次の文字が であることを確認し、次に同じ文字がであること確認する(?=a)bため、何にも一致しません。これは不可能です。ab

  • (?<=...)「後読み」です。がカーソルの前にあると主張し... ます

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

    "four" の "our" は直前に "f" があるので一致しますが、"flour" の "our" は直前に "l" があるので一致しません。

    上記のように、前に置くものには注意する必要があります。 次の文字がであることを確認し、カーソルを移動してから、前の文字が でa(?<=b)あることを確認するため、一致することはありません。ab

  • (?!...)「否定先読み」です。カーソルの後に表示され... ないことをアサートします。

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

    "child" は一致します。これは、次に来るのが "ren" ではなくスペースであるためです。「子供」はそうではありません。

    これはおそらく私が最もよく利用するものです。次に来ることができないものを細かく制御すると便利です。

  • (?<!...)「否定後読み」です。がカーソルのに現れ... ないことを表明します。

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

    "foot" の "oot" は、前に "r" がないので問題ありません。「root」の「oot」には、明らかに「r」が含まれています。

    追加の制限として、...この場合、ほとんどの正規表現エンジンは固定長を必要とします。?したがって、 、+*、またはは使用できません{n,m}

これらをネストして、あらゆる種類のクレイジーなことを行うこともできます。私はそれらを主に 1 回限りの使用に使用しており、メンテナンスする必要がないとわかっているため、実世界のアプリケーションの優れた例が手元にありません。正直なところ、それらは十分に奇妙であるため、最初に別の方法でやりたいことを試みるべきです. :)


後付け: 構文はPerl 正規表現(?から来ています。これは、多くの拡張構文のさまざまな記号が後に続いて使用されます。これ?は、単独では無効であるためです。それ<=だけでは何の意味もありません。(?<=は 1 つの完全なトークンであり、「これは後読みの開始です」を意味します。どちらも で始まりますが、how+=とは別の演算子のようです。+++

ただし、覚えやすい:=前向き (または、実際には「ここ」) を<示し、後ろ向きであることを示し!、「ない」という伝統的な意味を持ちます。


後の例について:

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

irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"

はい、これらは同じ出力を生成します。これは、先読みを使用する際のトリッキーなビットです。

  1. 正規表現エンジンはいくつかのことを試しましたが、うまくいきませんでした。現在はfores|ight.
  2. チェックします(?!s)。カーソルのの文字はs? いいえ、そうiです!その部分が一致し、一致が続行されますが、カーソルは移動せず、まだ がありfores|ightます。
  3. チェックしますightightカーソルの後に来ますか?はい、そうです。カーソルを移動しますforesight|
  4. 終わったね!

カーソルが部分文字列の上に移動したightため、それが完全一致であり、それが置き換えられます。

(?!a)b次の文字は であってはならずa、 でなければならないと言っているので、何をしても無駄ですb。しかし、それはただのマッチングと同じbです!

これは便利な場合もありますが、より複雑なパターンが必要です。たとえば、(?!3)\dは 3 以外の任意の数字に一致します。

これはあなたが望むものです:

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

これは前にs来ていないことを主張します。 ight

于 2013-01-17T21:00:54.727 に答える
5

ゼロ幅のアサーションは、正規表現が文字だけでなく位置にも一致することに気付くまでは理解が困難です。

文字列 "foo" を見れば、当然 3 文字を読むことになります。しかし、パイプでマークされた 4 つの位置もあります: "|f|o|o|"。先読みまたは後読み (別名、ルックアラウンド) は、式の前または後の文字が一致する位置に一致します。

ゼロ幅の式と他の式の違いは、ゼロ幅の式は位置のみに一致する (または「消費する」) ことです。たとえば、次のようになります。

/(app)apple/

「app」との一致を 2 回試みているため、「apple」との一致に失敗します。しかし

/(?=app)apple/

先読みは「app」が続く位置にのみ一致するため、成功します。実際には「app」文字と一致しないため、次の式でそれらを消費できます。

ルックアラウンドの説明

前向き先読み:(?=s)

あなたが訓練軍曹であり、検査を行っていると想像してください。あなたは、各プライベートを通り過ぎて、彼らが期待に応えられるようにすることを意図して、列の最前線から始めます。ただし、その前に、1 つずつ先を見て、プロパティの順序で並んでいることを確認します。二等兵の名前は「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'). いいえ、そうではありません。すべて問題ありません。

于 2013-01-17T21:00:17.307 に答える
2

ゼロ幅アサーションの意味は、マッチング中にゼロ文字を消費する式です。たとえば、この例では、

"foresight".sub(/sight/, 'ee')

一致するのは

foresight
    ^^^^^

したがって、結果は次のようになります

foreee

ただし、この例では、

"foresight".sub(/(?<=s)ight/, 'ee')

一致するのは

foresight
     ^^^^

したがって、結果は次のようになります

foresee

ゼロ幅アサーションのもう 1 つの例は、単語境界文字 です\bたとえば、単語全体を一致させるには、単語をスペースで囲みます。

"flight light plight".sub(/\slight\s/, 'dark')

取得するため

flightdarkplight

しかし、スペースを一致させると、置換中にそれがどのように削除されるかがわかりますか? 単語境界を使用すると、この問題を回避できます。

"flight light plight".sub(/\blight\b/, 'dark')

\b単語の先頭または末尾に一致しますが、実際には文字には一致しません: これはzero-widthです。

おそらく、あなたの質問に対する最も簡潔な答えは次のとおりです。先読みアサーションと後読みアサーションは、ゼロ幅アサーションの一種です。すべての先読みおよび後読みアサーションは、ゼロ幅のアサーションです。


あなたの例の説明は次のとおりです。

irb(main):001:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"

上記では、「次の文字が ではないところに一致し、s次に. に一致する」と言っていますi。これは常にan に当てはまります。これはi、 aniが になることはないsため、置換は成功します。

irb(main):002:0> "foresight".sub(/(?=s)ight/, 'ee')
=> "foresight"

上記では、「次の文字でありs、次にi.である場所に一致する」と言っています。anは決して ではないため、これは決してtrue ではないため、置換は失敗します。is

irb(main):003:0> "foresight".sub(/(?<=s)ight/, 'ee')
=> "foresee"

上記、すでに説明しました。(これが正解です。)

irb(main):004:0> "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"

上記は、今では明らかなはずです。この場合、"firefight" は "firefee" に置き換えられますが、"foresight" は "foresee" に置き換えられません。

于 2013-01-17T20:51:45.030 に答える