1. ホーム
  2. regex

[解決済み] Regex パターンでマッチング、除外する場合 / との間を除く

2022-10-12 05:13:51

質問

--編集--。 現在の回答は、いくつかの有用なアイデアを持っていますが、私は100%理解し、再利用できる、より完全なものが欲しいです。また、どこでも動作するアイデアは、私にとって、以下のような標準的な構文でないよりも優れています。 \K

この質問は、いくつかの状況s1 s2 s3を除いてパターンに一致させる方法についてです。私は私の意味を示すために具体的な例を与えるが、私は他の状況でそれを再利用できるように、100%理解することができる一般的な答えが好ましい。

5桁の数字にマッチさせるには \b\d{5}\b を使って5桁の数字にマッチさせたいのですが、3つの状況 s1 s2 s3 ではマッチしません。

s1: この文のようにピリオドで終わる行ではダメです。

s2: 括弧の中のどこにもない。

s3: で始まるブロック内ではありません。 if( で始まり //endif

s1 s2 s3のどれかをルックヘッドとルックビハインドで解決する方法を知っています。特にC#のルックビハインドや \K をPHPで使っています。

例えば

s1 (?m)(?!\d+.*?\.$)\d+

s3(C#ルックビハインド付き (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

s3とPHPの連携 (?:(?:if\(.*?//endif)\D*)*\K\d+

しかし、条件が混在しているため、頭が爆発しそうです。さらに悪いことに、他の条件s4 s5を別の機会に追加する必要があるかもしれません。

良いニュースは、PHP、C#、Python、または近所の洗濯機のような最も一般的な言語を使ってファイルを処理しても構わないということです :) 私はかなり Python & Java の初心者ですが、ソリューションがあるかどうかを学ぶことに興味があります。

だから、私は誰かが柔軟なレシピを考えるかどうかを見るためにここに来ました。

ヒントは大丈夫です:私に完全なコードを与える必要はありません :)

ありがとうございます。

どのように解決するのですか?

ハンス、お言葉に甘えて、先ほどの回答をさらに詳しく説明させていただきます。あなたはより完全なものを求めていると言っているので、長い答えは気にしないでください。まず、いくつかの背景から説明しましょう。

まず最初に、これは素晴らしい質問です。特定のコンテキスト (たとえば、コード ブロック内や括弧内) 以外での特定のパターンのマッチングについて、しばしば質問があります。これらの質問は、しばしばかなり厄介な解決策を生み出します。ですから、あなたの質問である 複数のコンテキスト は特別な挑戦です。

サプライズ

驚くことに、一般的で、実装が簡単で、維持するのが楽しい効率的なソリューションが少なくとも1つはあります。それは すべての正規表現で動作する で動作し、コード内のキャプチャグループを検査することができます。そして、最初はあなたとは異なると思われる多くの一般的な質問に答えることができます: "match everything except Donuts", "replace all but...", "match all words except those on my mom's black list", "ignore tags", "match temperature unless italicized"...

悲しいことに、このテクニックはあまり知られていません。私は、これを使用できる 20 の SO の質問で、これに言及する回答が 1 つだけあると見積もっています(つまり、50 か 60 の回答のうちの 1 つということです)。コメント欄でのKobiとのやりとりを見てください。このテクニックは、以下のサイトで詳しく説明されています。 この記事 には、このテクニックが(楽観的に)史上最高の正規表現のトリックと呼ばれています。この記事では、このテクニックがどのように機能するかをしっかりと把握できるよう、詳細には触れません。より詳細な情報やさまざまな言語でのコードサンプルについては、そのリソースを参照することをお勧めします。

よりよく知られたバリエーション

PerlとPHPに特有の構文を使用した、同じことを達成するバリエーションがあります。SO では、次のような正規表現の達人の手によって、これを目にすることができます。 CasimiretHippolyte ハムザ . これについては後で詳しく説明しますが、ここで私が注目するのは、すべての正規表現のフレーバーで動作する一般的なソリューションです (コードでキャプチャ グループを検査できる限り)。

zx81、すべての背景をありがとう... しかし、レシピは何ですか?

キーファクト

<ブロッククオート

このメソッドは、グループ1キャプチャのマッチを返します。これは を全く気にしません。

実は を必要としない様々なコンテキストにマッチさせるのがコツです。 にマッチさせることです (これらのコンテキストは | OR / alternation) で、中和する。 不要な文脈をすべてマッチさせた後、交互配列の最後の部分は、私たちの する にマッチし、グループ 1 に取り込まれます。

一般的なレシピは

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

これは Not_this_context にマッチしますが、ある意味でそのマッチはゴミ箱行きです。なぜなら、私たちは全体のマッチを見ず、グループ1のキャプチャだけを見るからです。

あなたの場合、あなたの数字と無視する3つのコンテキストで、私たちは行うことができます。

s1|s2|s3|(\b\d+\b)

s1、s2、s3を回避しようとするのではなく、実際にマッチさせるので、s1、s2、s3の個々の式は明瞭なままでよいことに注意してください。(のそれぞれの側の部分式です)。 | )

式全体はこのように書くことができます。

(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)

これを見る デモ をご覧ください (ただし、右下のペインにあるキャプチャグループに注目してください)。

もしあなたが精神的にこの正規表現を各 | の区切り文字で分割してみると、実際には 4 つの非常に単純な表現が並んでいるに過ぎません。

フリースペースをサポートするフレーバーでは、これは特によく読まれています。

(?mx)
      ### s1: Match line that ends with a period ###
^.*\.$  
|     ### OR s2: Match anything between parentheses ###
\([^\)]*\)  
|     ### OR s3: Match any if(...//endif block ###
if\(.*?//endif  
|     ### OR capture digits to Group 1 ###
(\b\d+\b)

これは例外的に読みやすく、保守しやすい。

正規表現を拡張する

より多くの状況s4とs5を無視したいときは、左側の交互に追加していきます。

s4|s5|s1|s2|s3|(\b\d+\b)

これはどのように機能するのですか?

不要なコンテキストは左側の代替リストに追加されます。これらはマッチしますが、これらの全体的なマッチは決して検査されないので、マッチングはそれらを "ゴミ箱" に入れる方法です。

しかし、必要なコンテンツはグループ 1 にキャプチャされます。 そこで、グループ 1 が設定されていて、空ではないことをプログラムで確認する必要があります。これは些細なプログラミング作業です (どのように行うかは後で説明します)。特に、一目で理解でき、必要に応じて修正または拡張できる単純な正規表現を残すことを考えると、これは簡単です。

私はいつも可視化が好きというわけではありませんが、この方法はいかにシンプルであるかを示す良い仕事です。各行が一致する可能性がありますが、一番下の行だけがグループ 1 に捕捉されます。

デバッギクスデモ

Perl/PCREのバリエーション

上記の一般的な解決策とは対照的に、少なくとも@CasimiretHippolyteや@HamZaのような正規表現の神の手によって、SOでしばしば見られるPerlとPCREのためのバリエーションが存在します。それは

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

あなたの場合

(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b

このバリエーションは、コンテキスト s1、s2、s3 でマッチしたコンテンツは単にスキップされるので、グループ 1 のキャプチャを検査する必要がないため、少し使いやすくなっています (括弧がなくなっていることに注意してください)。マッチした内容には whatYouWant

ただし (*F) , (*FAIL)(?!) はすべて同じものです。もっと曖昧にしたければ (*SKIP)(?!)

デモ このバージョンでは

アプリケーション

ここでは、このテクニックで簡単に解決できる一般的な問題をいくつか紹介します。言葉の選択によって、これらの問題のいくつかが異なって聞こえることがありますが、実際にはほとんど同じであることにお気づきでしょう。

  1. のようなタグの任意の場所を除いて、どのように foo にマッチさせることができますか? <a stuff...>...</a> ?
  2. を除いてfooにマッチするにはどうすればよいですか? <i> タグまたは javascript スニペット (より多くの条件) を除いて foo に一致させることができますか?
  3. このブラックリストに載っていないすべての単語にマッチさせるにはどうすればよいですか?
  4. SUB... END SUB ブロックの内部を無視するにはどうすればよいですか? END SUB ブロックの中のものを無視するにはどうすればよいですか。
  5. s1 s2 s3 以外はどうすれば一致するのですか?

グループ 1 キャプチャーをプログラムする方法

グループ

あなたはコードについてはしませんでしたが、補完のために... グループ 1 を検査するコードは、選択した言語に依存することは明らかです。いずれにせよ、match を検査するために使用するコードに 2 行以上追加されることはないでしょう。

疑問があれば、私は コードサンプル セクション を参照することをお勧めします。

代替品

質問の複雑さや使用する正規表現エンジンに応じて、いくつかの選択肢があります。ここでは、複数の条件を含む、ほとんどの状況に適用できる 2 つの方法を紹介します。私の見解では、どちらも s1|s2|s3|(whatYouWant) レシピほど魅力的ではありませんが、明確さが常に勝るからです。

1. 置換してから一致させる。

面倒に聞こえるかもしれませんが、多くの環境でうまく機能する良い解決策は、2つのステップで作業することです。最初の正規表現は、潜在的に競合する文字列を置き換えることによって、無視したいコンテキストを無効にします。マッチさせたいだけなら、空の文字列に置き換えてから二番目のステップでマッチを実行すればいい。置換したい場合は、まず無視したい文字列を何か特徴的なものに置き換えます。 例えば、数字を固定幅のチェーンで囲み、その周りを @@@ . この置換の後、本当に欲しかったものに自由に置換することができます。その場合、特徴的な @@@ の文字列になります。

2. ルックアラウンド。

あなたの元の投稿は、あなたがルックアラウンドを使用して単一の条件を除外する方法を理解していることを示しました。C# はこれに最適だとおっしゃいましたが、そのとおりですが、これが唯一の選択肢ではありません。たとえば、C#、VB.NET、Visual C++ で見られる .NET 正規表現のフレーバーや、まだ実験的である regex モジュールで置き換えることができます。 re は、私が知っている限り、無限幅のlookbehindをサポートする唯一の2つのエンジンです。これらのツールを使えば、1つのlookbehindの中の1つの条件が、後ろだけでなく、マッチとその先を見ることを引き受け、lookaheadと調整する必要性を回避することができるのです。条件を増やす?ルックアラウンドを増やせます。

s3の正規表現をC#で再利用すると、パターン全体は次のようになります。

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

しかし、もうお分かりだと思いますが、これを推奨しているわけではありませんよね?

削除

HamZa と @Jerry は、単に削除したい場合の追加のトリックについて言及することを提案してくれました。 WhatYouWant . をマッチさせるレシピを覚えていますか? WhatYouWant にマッチさせる (グループ 1 に取り込む) レシピは s1|s2|s3|(WhatYouWant) でしたよね?のインスタンスをすべて削除するには WhatYouWant を削除するには、正規表現を

(s1|s2|s3)|WhatYouWant

置換文字列には $1 . ここで起こることは、各インスタンスの s1|s2|s3 にマッチした場合、置換された $1 はそのインスタンスを自分自身に置き換えます (参照元は $1 ). 一方 WhatYouWant がマッチした場合、それは空のグループに置き換えられ、それ以外のものはない - したがって、削除されます。これを見てください デモ この素晴らしい追加を提案してくれた @HamZa と @Jerry に感謝します。

置換

ここで、置き換えについて、簡単に触れておきます。

  1. 何もない状態で置換する場合は、上記の "Deletions" のトリックを参照してください。
  2. 置換するとき、Perl や PCRE を使っている場合は (*SKIP)(*F) を使用して、正確に一致させ、ストレートに置き換えることができます。
  3. 他の方法としては、置換関数の呼び出しの中で、コールバックやラムダを使ってマッチを検査し、グループ1が設定されていれば置換します。これに関して助けが必要な場合は、すでに参照した記事でさまざまな言語でのコードを提供しています。

楽しんでください。

いや、待てよ、まだある!

あ、いや、それは来春発売予定の全20巻の回顧録のためにとっておくよ。