Techioz Blog

負の先読みを使用したカンマ区切りの一意の数値の Ruby 正規表現

概要

以下は、数値のカンマ区切りリストに一致し、一意の数値のみの入力を許可する正規表現です。

^(?!.*\b(\d+)\b.*\b\1\b)(\d)(,(\d))*$

たとえば、1,2,3 は許可されますが、1,1 または 2,1,1 は許可されません。

誰かそれがどのように機能するかを簡単な言葉で説明してもらえますか?

私が混乱しているのは、否定的な先読みアサーションです。 Web で利用可能な説明では、次のような構文が示されています。

参照: https://javascript.info/regexp-lookahead-lookbehind#negative-lookahead

しかし、私が示した正規表現では、上記の構文に従っていません。それでは、どのように機能するのでしょうか?

1、2、3 と一致する場合、どのような一致プロセスが発生しますか?

2,1,1 と一致しない場合、どのようなマッチング処理が行われますか?

1,2,3 または 2,1,1 が一致する場合、最初に正規表現 ((,()* の部分に続くものと照合され、次にその一致結果が否定先読み部分 (? !.* () .* \1 ) それとも最初に否定先読み部分が実行され、次に残りの部分が実行されますか?

また、否定先読み部分の前後の .* を削除すると、1,1 または 2,1,1 との一致も開始されます。それでは、否定先読み部分で削除された .* にはどのような意味があるのでしょうか。

注: 重要な情報を伝える必要がある場合に備えて、Ruby コードで正規表現を使用したいと思います。

正規表現のソース参照は次のとおりです。

https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://stackoverflow.com/a/45946721/936494

https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://stackoverflow.com/a/45944821/936494

ありがとう。

解決策

あなたの質問の根本的な正規表現部分については他の人に説明してもらいます。ここでは、X/Y 問題について説明します。つまり、正規表現を使用して特定の操作を実行できる一方で、不必要な複雑さが生じる可能性があるという問題です。さらに、多くの場合、Array メソッドや String メソッドなどの他のメソッドの方が高速です。

優れたコードの経験則は、パターンをできるだけ単純に抽出し、他のメソッドを使用してそれを操作することです。一意の値を出力するか、文字列内に重複がない数字のみを出力するかに応じて、次の例を検討してください。

STR = "1,6,6,7,3,2,1,2,3,6"

# return only one of each number found, sorted
STR.split(?,).uniq.sort.map &:to_i
# => [1, 2, 3, 6, 7]

# return only Integer values not duplicated in
# the String
STR.split(?,).tally.select { _2.eql? 1 }.keys.map &:to_i
# => [7]

いずれにせよ、これは複雑な正規表現よりも高速かつ簡単です。

正規表現を使用したい場合は、ニーズに応じて String#split を正規表現リテラルまたは String#scan に置き換えることができます。たとえば、文字列リテラルの代わりに正規表現を使用した #split と #scan の 2 つのほぼ同等の使用法を考えてみましょう。

# scan String for one or more consecutive
# digits
STR.scan(/\d+/).uniq.sort.map &:to_i
#=> [1, 2, 3, 6, 7]

# split String on commas surround by
# optional whitespace
STR.split(/\s*,\s*/).uniq.sort.map &:to_i
#=> [1, 2, 3, 6, 7]

これは、文字列が正規でない場合には、もう少し寛容です。例:

STR = " 1,6,  6 , 7,3 ,2,1,\t2\t, 3 ,6"

ただし、String#strip をメソッド チェーンに挿入するだけで同様の結果を得ることができます。上記の標準化されていない入力値が与えられた場合でも、 String リテラルとして を分割する例を次に示します。

STR.split(?,).map(&:strip).uniq.sort.map &:to_i

ここで注意すべき主な点は次のとおりです。

6 か月後にコードを読みに戻ると、メソッド チェーンは長く複雑な正規表現よりもはるかに簡単にデバッグできるようになります。特に、コードを作成するときに理解しにくいアトムや正規表現構造を使用している場合はそうです。彼らは初めてです。未来のあなたはその優しさに感謝するでしょう!