4

そこにいる誰かからの簡単な質問でなければなりません:

この JavaScript を実行すると:

var regex = new RegExp("(?!cat)dog(?!cat)","g");
var text =  "catdogcat catdogdog catdogdogcat".replace(regex,"000");
console.log(text);

これは次のように出力します。

catdogcat cat000000 cat000dogcat

しかし、私はこれを出力する必要があります:

catdogcat cat000000 cat000000cat

の 2 番目の「犬」が にcatdogdogcat置き換えられないのはなぜ000ですか?

編集:両側に猫がいないときはいつでも「犬」を置き換えたいです。ではcatdogdogcat、両方の犬がこの要件を満たしているため、交換する必要があります。明らかに、私はこれらの否定的なルックアラウンドを理解していません...

4

2 に答える 2

7

あなたのアプローチには2つの問題があります。

  1. 最初の先読みは後読みである必要があります。を書く(?!cat)と、エンジンは次の 3 文字が正しいことを確認してcatから、開始位置にリセットし (これが先の見方です)、同じ 3 文字で一致を試みますdogしたがって、先読みは何も追加しません。dog一致できる場合、明らかにcat同じ位置で一致することはできません。あなたが望むのは(?<!cat)、前の文字がそうでないことをチェックする後読みですcat。残念ながら、JavaScript は後読みをサポートしていません。
  2. 2 つのルックアラウンドを論理ORする必要があります。あなたの場合、いずれかのルックアラウンドが失敗すると、パターンが失敗します。したがって、両方の要件 (どちらの端にもない)を満たす必要があります。しかし、実際にはそれをORcatしたいのです。後読みがサポートされている場合は、むしろ次のようになります(代替によりパターン全体が分割されることに注意してください)。しかし、私が言ったように、後読みはサポートされていません。最初のビットで 2 つのルックアラウンドを *OR* したように見える理由は、先行するものが単にチェックされていないためです (ポイント 1 を参照)。(?<!cat)dog|dog(?!cat)catdogdogcat

後読みを回避するにはどうすればよいですか?Kolink の答えは を示唆して(?!cat)...dogおり、これは a が始まる位置にルックアラウンドを配置catし、先読みを使用します。これには 2 つの新しい問題があります:dog文字列の先頭の a と一致できません (前の 3 文字が必要なためです。またdog、一致が重複できないため、2 つの連続する s と一致することはできません (最初の と一致した後dog、エンジンは 3 つの新しい文字を必要とします)。 、実際に再度一致する前に...次を消費します)。dogdog

場合によっては、パターンと文字列の両方を逆にすることで回避できるため、後読みを先読みに変えることができますが、あなたの場合は、最後の先読みを後読みに変えます。

正規表現のみのソリューション

私たちはもう少し賢くならなければなりません。一致は重複できないため、catdogcat置換せずに (したがってターゲット文字列でそれらをスキップして) 明示的に一致を試み、見つかったすべてdogの s を置換することができます。2 つのケースを交互に配置するため、文字列内のすべての位置で両方が試行されます (catdogcatオプションが優先されますが、ここではあまり重要ではありません)。問題は、条件付き置換文字列を取得する方法です。しかし、これまでに得たものを見てみましょう。

text.replace(/(catdog)(?=cat)|dog/g, "$1[or 000 if $1 didn't match]")

したがって、最初の選択肢では、a を照合catdogしてグループに取り込み、1別のcatフォローがあることを確認します。置換文字列では、単純に$1back を書きます。美しさは、2 番目の選択肢が一致した場合、最初のグループは使用されないため、代わりに空の文字列になることです。すぐcatdogに一致するのではなく、一致するだけで先読みを使用する理由は、一致が重複しているためです。catdogcatを使用した場合catdogcat、入力catdogcatdogcatで最初の一致が 2 番目までのすべてを消費するcatため、2 番目dogは最初の選択肢によって認識されませんでした。

ここで唯一の問題は、2 番目の代替案を使用した場合、どのように000代替品を入手するかということです。

残念ながら、入力文字列の一部ではない条件付き置換を呼び出すことはできません。秘訣は000、入力文字列の末尾に a を追加し、 a が見つかった場合は先読みでそれをキャプチャし、dogそれを書き戻すことです。

text.replace(/$/, "000")                            
    .replace(/(catdog)(?=cat)|dog(?=.*(000))/g, "$1$2")
    .replace(/000$/, "")

000最初の置換は文字列の末尾に追加されます。

2 番目の置換は、一致してcatdog(別のcatものが続くことを確認)、それをグループにキャプチャする1(2空のままにする) か、一致してグループdogにキャプチャ000する2(グループを空のままにする) かのいずれか1です。それから$1$2、飾り気のないものcatdog000.

0003 番目の置換は、文字列の末尾にある無関係なものを取り除きます。

コールバック ソリューション

正規表現の準備と 2 番目のオプションの先読みが苦手な場合は、代わりにコールバックを置き換えた少し単純な正規表現を使用できます。

text.replace(/(catdog)(?=cat)|dog/g, function(match, firstGroup) {
    return firstGroup ? firstGroup : "000"
})

提供された関数のバージョンではreplace、一致ごとに呼び出され、その戻り値が置換文字列として使用されます。関数の最初のパラメーターは試合全体、2 番目のパラメーターは最初のキャプチャ グループ (undefinedグループが試合に参加しない場合) などです。

したがって、置換コールバックでは、未定義の000場合(つまり、オプションが一致した場合)を自由に呼び出したり、存在する場合 (つまり、オプションが一致した場合) を返すことができます。これはもう少し簡潔で、おそらく理解しやすいでしょう。ただし、関数を呼び出すオーバーヘッドにより、処理が大幅に遅くなります(ただし、これが問題になるかどうかは、これを実行する頻度によって異なります)。あなたのお気に入りを選んでください!firstGroupdogfirstGroupcatdogcat

于 2013-06-27T11:13:48.367 に答える