Javascript:否定的な後読み同等?


141

JavaScriptの正規表現で否定的な後読みと同等のものを実現する方法はありますか?先頭が特定の文字セットではない文字列に一致させる必要があります。

文字列の先頭に一致する部分が見つかった場合、これを失敗せずに実行する正規表現を見つけることができないようです。否定的な後読みが唯一の答えのようですが、JavaScriptにはありません。

編集:これは私が働きたい正規表現ですが、それはしません:

(?<!([abcdefg]))m

したがって、「jim」または「m」の「m」と一致しますが、「jam」とは一致しません。


否定的な後読みで見えるので、正規表現を投稿することを検討してください。これにより、対応が容易になる場合があります。
Daniel LeCheminant 2009年

1
後読みなどの採用を追跡したい方は、ECMAScript 2016+互換性テーブル
WiktorStribiżewAug

@WiktorStribiżew:後読みが2018仕様に追加されました。Chromeはそれらをサポートしていますが、Firefoxはまだ仕様を実装していません
ロニーベスト

これも後ろを見る必要がありますか?どう(?:[^abcdefg]|^)(m)ですか?のように"mango".match(/(?:[^abcdefg]|^)(m)/)[1]
slebetman

回答:


57

Lookbehindアサーションは、2018年にECMAScript仕様受け入れられました。

肯定的な後読みの使用法:

console.log(
  "$9.99  €8.47".match(/(?<=\$)\d+(\.\d*)?/) // Matches "9.99"
);

否定的な後読みの使用法:

console.log(
  "$9.99  €8.47".match(/(?<!\$)\d+(?:\.\d*)/) // Matches "8.47"
);

プラットフォームサポート:


2
ポリフィルはありますか?
キリー

1
(IEはJSでフル正規表現の実装を書いて)1は潜在的に非常に非現実的だろう作成など、@Killy私の知る限りではありません、と私はそこまで疑うだろう
Okku

babelプラグインの使用についてはどうですか?ES5またはすでにサポートされているES6にコンパイルすることは可能ですか?
Stefan J

1
私はあなたがJavaScriptでロジックを記述してpolyfillsは何をすべきかである正規表現の実装を拡張.. ....とそこの何も間違ったことを意味だと思う@IlpoOksanen
neaumusic

1
あなたは何について話していますか?ほとんどすべての提案は他の言語に触発されており、慣用的なJSおよび下位互換性のコンテキストで意味のある他の言語の構文とセマンティクスを常に一致させることを好みます。私は2017年にポジティブとネガティブの両方の後読みが2018年の仕様に受け入れられたことを明確に述べ、ソースへのリンクを提供しました。さらに、上記の仕様を実装しているプラ​​ットフォームと他のプラットフォームのステータスについて詳しく説明し、それ以降も更新しています。当然、これが最後のRegexp機能ではありません
Okku

83

2018年以降、LookbehindアサーションECMAScript言語仕様の一部です。

// positive lookbehind
(?<=...)
// negative lookbehind
(?<!...)

2018年以前の回答

JavaScriptはネガティブルックアヘッドをサポートしているため、その方法の1つは次のとおりです。

  1. 入力文字列を逆にします

  2. 逆正規表現と一致する

  3. 一致を逆にして再フォーマットする


const reverse = s => s.split('').reverse().join('');

const test = (stringToTests, reversedRegexp) => stringToTests
  .map(reverse)
  .forEach((s,i) => {
    const match = reversedRegexp.test(s);
    console.log(stringToTests[i], match, 'token:', match ? reverse(reversedRegexp.exec(s)[0]) : 'Ø');
  });

例1:

@ andrew-ensleyの質問に続く:

test(['jim', 'm', 'jam'], /m(?!([abcdefg]))/)

出力:

jim true token: m
m true token: m
jam false token: Ø

例2:

@neaumusicコメントに続く(一致するmax-heightline-height、トークンはないheight):

test(['max-height', 'line-height'], /thgieh(?!(-enil))/)

出力:

max-height true token: height
line-height false token: Ø

36
このアプローチの問題は、先読みと後読みの両方がある場合は機能しないことです
kboom

3
例を示してください。私は一致させたいが、一致させたくmax-heightないline-heightだけだと言ってくださいheight
neaumusic

タスクが、いくつかの記号が前に付いていない2つの連続した同一の記号(2つ以下)を置き換えることである場合、役に立ちません。''(?!\()は、アポストロフィを''(''test'''''''testもう一方の端から置き換えます。したがって、(''test'NNNtestではなく残し(''testNNN'testます。
WiktorStribiżew16年

60

int前にないすべてを見つけたいとしましょうunsigned

否定的な後読みのサポート:

(?<!unsigned )int

否定的な後読みをサポートしない場合:

((?!unsigned ).{9}|^.{0,8})int

基本的には、先行するn個の文字を取得し、否定先読みを使用して一致を除外するだけでなく、前にn個の文字がない場合にも一致するようにします。(nは後読みの長さです)。

問題の正規表現:

(?<!([abcdefg]))m

に変換されます:

((?!([abcdefg])).|^)m

興味のあるストリングの正確なスポットを見つけるために、または特定の部分を別のものに置き換えたい場合は、キャプチャグループを操作する必要がある場合があります。


2
これが正解です。見なさい:"So it would match the 'm' in 'jim' or 'm', but not 'jam'".replace(/(j(?!([abcdefg])).|^)m/g, "$1[MATCH]") リターン"So it would match the 'm' in 'ji[MATCH]' or 'm', but not 'jam'" それはかなり簡単で、それは働く!
Asrail 2015

41

ミジョジャの戦略はあなたの特定のケースでは機能しますが、一般的には機能しません:

js>newString = "Fall ball bill balll llama".replace(/(ba)?ll/g,
   function($0,$1){ return $1?$0:"[match]";});
Fa[match] ball bi[match] balll [match]ama

以下は、ダブルバトルに一致することを目標とするが、その前に「ba」がない場合は一致しない例です。「balll」という単語に注意してください-真の後読みは最初の2つのlを抑制しているはずですが、2番目のペアと一致しています。ただし、最初の2つのlを一致させ、その一致を誤検出として無視することにより、正規表現エンジンはその一致の最後から処理を進め、誤検出内の文字をすべて無視します。


5
ああ、あなたは正しいです。しかし、これは私が以前よりもはるかに近いです。もっと良いものが来るまで(JavaScriptが実際に後読みを実装するように)これを受け入れることができます。
Andrew Ensley 2009年

33

使用する

newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});

10
これは何もしません。newString常に等しくなりstringます。なぜそれほど多くの賛成票があるのですか?
MikeM 2013年

@MikeM:ポイントは、単にマッチング手法を示すことです。
バグ

57
@バグ。何もしないデモは奇妙なデモです。答えは、それがどのように機能するのかを理解せずに、単にコピーして貼り付けられているかのように出くわします。したがって、付随する説明の欠如と、何も一致していることを実証できないこと。
MikeM 2013年

2
@MikeM:SOのルールは、書かれた質問に答える場合、それは正しいです。OPはユースケースを指定していませんでした
バグ

7
コンセプトは正しいですが、はい、デモはあまりうまくいきません。JSコンソールでこれを実行してみてください... "Jim Jam Momm m".replace(/([abcdefg])?m/g, function($0, $1){ return $1 ? $0 : '[match]'; });。戻るはずJi[match] Jam Mo[match][match] [match]です。ただし、Jasonが以下で説明するように、特定のエッジケースで失敗する可能性があることにも注意してください。
Simon East

11

文字セットを無効にすることで、非キャプチャグループを定義できます。

(?:[^a-g])m

... これらの文字の後に続くすべてのm NOTに一致します。


2
試合は実際には先行するキャラクターもカバーすると思います。
サム

4
^これは本当です。文字クラスは...文字を表します!非キャプチャグループが行っているのは、その値を置換コンテキストで利用できるようにすることではありません。あなたの表現は、それが言っている「すべてのmはこれらの文字のいずれかによって先行しない」と言っていません「すべてのM の文字が前にこれらの文字のいずれかをされていません」
theflowersoftime

5
答えが元の問題(文字列の先頭)も解決するためには、オプションも含まれている必要があるため、結果の正規表現はになります(?:[^a-g]|^)m。実行例については、regex101.com / r / jL1iW6 / 2を参照してください。
Johny Skovdal、2016年

voidロジックを使用しても、必ずしも望ましい効果が得られるとは限りません。
GoldBishop 2017

2

これは私がstr.split(/(?<!^)@/)Node.js 8で達成した方法です(後読みをサポートしていません)。

str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()

機能しますか?はい(Unicodeはテストされていません)。不快?はい。


1

Mijojaのアイデアに従い、JasonSによって露呈された問題を利用して、私はこのアイデアを思いつきました。私は少しチェックしましたが、自分自身について確信が持てないので、js regexで私よりも専門家による検証が素晴らしいでしょう:)

var re = /(?=(..|^.?)(ll))/g
         // matches empty string position
         // whenever this position is followed by
         // a string of length equal or inferior (in case of "^")
         // to "lookbehind" value
         // + actual value we would want to match

,   str = "Fall ball bill balll llama"

,   str_done = str
,   len_difference = 0
,   doer = function (where_in_str, to_replace)
    {
        str_done = str_done.slice(0, where_in_str + len_difference)
        +   "[match]"
        +   str_done.slice(where_in_str + len_difference + to_replace.length)

        len_difference = str_done.length - str.length
            /*  if str smaller:
                    len_difference will be positive
                else will be negative
            */

    }   /*  the actual function that would do whatever we want to do
            with the matches;
            this above is only an example from Jason's */



        /*  function input of .replace(),
            only there to test the value of $behind
            and if negative, call doer() with interesting parameters */
,   checker = function ($match, $behind, $after, $where, $str)
    {
        if ($behind !== "ba")
            doer
            (
                $where + $behind.length
            ,   $after
                /*  one will choose the interesting arguments
                    to give to the doer, it's only an example */
            )
        return $match // empty string anyhow, but well
    }
str.replace(re, checker)
console.log(str_done)

私の個人的な出力:

Fa[match] ball bi[match] bal[match] [match]ama

原則はchecker、その位置が次の開始点であるときはいつでも、任意の2つの文字の間の文字列の各ポイントで呼び出すことです。

---不要なサイズの部分文字列(ここでは'ba'、したがって..)(そのサイズがわかっている場合。それ以外の場合は、おそらく実行が困難になるはずです)

--- ---または文字列の先頭の場合はそれよりも小さい: ^.?

そして、これに続き、

---実際に求められるもの(ここ'll')。

の呼び出しごとにchecker、前の値が必要な値llでないかどうかを確認するテストがあります(!== 'ba'); その場合は、別の関数を呼び出します。これdoerがstrに変更を加えるのはこれ()である必要があります。目的がこれである場合、またはより一般的には、手動で処理するために必要なデータを入力で取得しますスキャンの結果str

ここで文字列を変更するため、で指定された位置をオフセットするために、長さの違いのトレースを維持する必要がありreplace、すべてstr、でがありました。

プリミティブ文字列は不変なので、str操作全体の結果を格納するために変数を使用することもできますが、置換によってすでに複雑になっている例は、別の変数(str_done)で。

パフォーマンスの観点からはかなり厳しいと思います: ''から ''への無意味な置換、this str.length-1時間、そしてここではdoerによる手動の置換、つまり、スライスの多くを意味します...おそらくこの特定の上記のケースでは文字列を挿入[match]したい場所の周りで1回だけ切り、それ自体.join()と一緒にグループ化し[match]ます。

もう1つは、より複雑なケース、つまり偽の後読みの複雑な値を処理する方法がわからないということです...長さはおそらく取得するのに最も問題のあるデータです。

また、checker$ behindに不要な値が複数存在する可能性がある場合はchecker、同じ正規表現オブジェクトが作成されないように、外部でキャッシュ(作成)するさらに別の正規表現でテストする必要があります。の各呼び出しでchecker)をそれが私たちが避けようとしているものであるかどうかを知る必要があります。

私は明確になっていると思います。躊躇しないのであれば、私はもっと頑張ります。:)


1

大文字に変換するなど、何かに置き換えたい 場合は、m大文字と小文字を使います。Mに、キャプチャグループのセットを無効にすることができます。

一致([^a-g])m、に置き換え$1M

"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam

([^a-g])範囲内の任意のchar not(^)に 一致a-gし、最初のキャプチャグループに格納されるため、でアクセスできます$1

したがって、を見つけimjimそれをiMに変換しjiMます。


1

前述のように、JavaScriptでは後読みが可能になりました。古いブラウザでは、まだ回避策が必要です。

私は頭に、結果を正確に返す後読みなしで正規表現を見つける方法はないと思います。あなたができることはグループでの作業です あなたが正規表現を持っていると仮定します(?<!Before)Wanted。ここで、Wantedは一致させたいBefore正規表現であり、一致の前にあるべきでないものを数える正規表現です。あなたができる最善のことは、正規表現Beforeを否定し、正規表現を使用することですNotBefore(Wanted)。望ましい結果は最初のグループ$1です。

Before=[abcdefg]否定しやすいあなたの場合NotBefore=[^abcdefg]。したがって、正規表現は次のようになります[^abcdefg](m)。の位置が必要な場合はWanted、グループ化する必要NotBeforeもあります。その結果、目的の結果は2番目のグループになります。

Beforeパターンの一致が固定長nである場合、つまり、パターンに反復トークンが含まれていない場合、パターンの否定を回避しBeforeて正規表現(?!Before).{n}(Wanted)を使用できますが、最初のグループを使用するか、正規表現(?!Before)(.{n})(Wanted)を使用して2番目のグループを使用する必要がありますグループ。この例では、パターンはBefore実際には固定長、つまり1なので、正規表現(?![abcdefg]).(m)またはを使用し(?![abcdefg])(.)(m)ます。すべての一致に興味がある場合は、gフラグを追加します。私のコードスニペットを参照してください。

function TestSORegEx() {
  var s = "Donald Trump doesn't like jam, but Homer Simpson does.";
  var reg = /(?![abcdefg])(.{1})(m)/gm;
  var out = "Matches and groups of the regex " + 
            "/(?![abcdefg])(.{1})(m)/gm in \ns = \"" + s + "\"";
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "\nWhole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "\nResulting string after statement s.replace(reg, \"$1*$2*\")\n"
         + s.replace(reg, "$1*$2*");
  alert(out);
}

0

これは効果的にそれを行います

"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null

検索と置換の例

"jim jam".replace(/([^a-g])m/g, "$1M")
> "jiM jam"

これが機能するためには、負の後読みストリングが1文字でなければならないことに注意してください。


1
結構です。「ジム」では、「私」は欲しくない。「m」だけです。そして"m".match(/[^a-g]m/)yeilds nullにも。その場合も「m」が欲しいです。
Andrew Ensley

-1

/(?![abcdefg])[^abcdefg]m/gi はい、これはトリックです。


5
これらの文字が一致しないようにする役割をすでに(?![abcdefg])果たし[^abcdefg]ているため、チェックは完全に冗長です。
nhahtdh

2
これは、先行する文字がない 'm'とは一致しません。
Andrew Ensley 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.