Java on RosettaCode の場合、次のコード例を見つけました。
_public static boolean prime(int n) {
return !new String(new char[n]).matches(".?|(..+?)\\1+");
}
_
.?|(..+?)\\1+
はどのように素数と一致しますか?
この部分は理解していると言いましたが、強調するために、生成される文字列の長さは指定された数と同じです。したがって、文字列には、n == 3
。
.?
正規表現の最初の部分には、「任意の文字、0回または1回」と書かれています。基本的に、ゼロまたは1つの文字があります-または、上記で述べたように、n == 0 || n == 1
。一致する場合は、その否定を返します。これは、0と1は素数ではないという事実に対応します。
(..+?)\\1+
正規表現の2番目の部分は少し複雑で、グループと後方参照に依存しています。グループは括弧で囲まれたものであり、後で使用するために正規表現エンジンによってキャプチャおよび保存されます。後方参照は、同じ正規表現で後で使用される一致したグループです。
グループは1文字をキャプチャし、次に任意の1文字以上をキャプチャします。 (+文字は1つ以上を意味しますが、前の文字またはグループのみです。したがって、これは「2、4、6などの文字」ではなく、「2、3など」です。+?は+に似ていますが、 +可能な限り少ない文字に一致させようとします。+可能であれば、通常は文字列全体を取得しようとします。これは、後方参照部分が機能しないため、この場合は悪いことです。)
次の部分は後方参照です。同じ文字セット(2つ以上)が再び表示されます。この後方参照は1回以上表示されます。
そう。キャプチャされたグループは、キャプチャされた自然数(2以降)の文字に対応します。このグループは、自然な回数(2回目以降も)出現します。 ISが一致する場合、これは、n以上の長さの文字列に一致する2以上の2つの数の積を見つけることができることを意味します...複合nがあることを意味します。繰り返しますが、成功したマッチの否定を返します:nは素数ではありません。
一致するものが見つからない場合、2以上の2つの自然数の積を求めることはできません。また、不一致と素数の両方があるため、再び否定が返されます。マッチ結果の。
今見えますか?それは信じられないほどトリッキーです(そして計算コストが高い!)が、それを手に入れれば、それは同時に簡単なことでもあります。 :-)
正規表現の解析が実際にどのように機能するかなど、さらに質問がある場合は詳しく説明します。しかし、私は今のところ、この答えをシンプルに(または、できる限りシンプルに)しようとしています。
素数性テスト以外の正規表現の部分について説明します。_String s
_を繰り返して構成される_String t
_を指定した次の正規表現は、t
を検出します。
_ System.out.println(
"MamamiaMamamiaMamamia".replaceAll("^(.*)\\1+$", "$1")
); // prints "Mamamia"
_
動作方法は、正規表現が_(.*)
_を_\1
_にキャプチャし、その後に_\1+
_があるかどうかを確認することです。 _^
_と_$
_を使用すると、文字列全体に一致する必要があります。
そのため、ある意味では、_String s
_が与えられます。これは_String t
_の「倍数」であり、正規表現はそのようなt
(_\1
_は貪欲です)。
この正規表現が機能する理由を理解したら、(現在のOPの正規表現の最初の代替を無視して)素数テストでの使用方法を説明するのは簡単です。
n
の素数性をテストするには、最初にString
の長さn
を生成します(同じchar
で埋められます)String
(たとえばk
)を_\1
_にキャプチャし、_\1+
_をString
[.____の残りと一致させようとします。 ]n
はk
の適切な倍数であるため、n
は素数ではありません。k
を分割するn
は存在しないため、n
は素数です
.?|(..+?)\1+
は素数とどのように一致しますか?
実際、そうではありません! It matchesString
長さが素数ではありません!
.?
_:代替の最初の部分は、長さ_0
_または_1
_のString
に一致します(定義により素数ではありません)(..+?)\1+
_:上記の正規表現のバリエーションである代替の2番目の部分は、長さString
の「倍数」であるn
の長さString
に一致します_k >= 2
_(つまりn
は素数ではなく複合)。?
_は正確には実際には必要ありませんが、小さいk
を最初に試すことでプロセスを高速化するのに役立つことに注意してくださいboolean
ステートメントの_!
_ return
補演算子に注意してください。これはmatches
を否定します。正規表現DOES N'Tが一致したとき、n
は素数です!これは二重否定論理であるため、紛らわしいのも不思議ではありません!!
コードを読みやすくするための簡単なコードの書き換えを次に示します。
_public static boolean isPrime(int n) {
String lengthN = new String(new char[n]);
boolean isNotPrimeN = lengthN.matches(".?|(..+?)\\1+");
return !isNotPrimeN;
}
_
上記は、元のJavaコードと本質的に同じですが、ロジックを理解しやすくするために、ローカル変数に割り当てられた複数のステートメントに分割されています。
次のように、有限の繰り返しを使用して正規表現を単純化することもできます。
_boolean isNotPrimeN = lengthN.matches(".{0,1}|(.{2,})\\1+");
_
繰り返しますが、同じString
で満たされたn
の長さchar
を指定すると、
.{0,1}
_は、プライムではなく_n = 0,1
_かどうかをチェックします(.{2,})\1+
_は、n
が_k >= 2
_の適切な倍数であるかどうかを調べます。素数ではありません消極的な修飾子_?
_ on _\1
_(明確にするために省略)を除き、上記の正規表現は元の正規表現と同じです。
次の正規表現は同様の手法を使用しています。それは教育的でなければなりません:
_System.out.println(
"OhMyGod=MyMyMyOhGodOhGodOhGod"
.replaceAll("^(.+)(.+)(.+)=(\\1|\\2|\\3)+$", "$1! $2! $3!")
); // prints "Oh! My! God!"
_
素晴らしい正規表現のトリック(非常に非効率的ですが)... :)
正規表現では、非プライムが次のように定義されます。
N <= 1 OR NがK> 1で割り切れる場合にのみ、Nは素数ではありません。
Nの単純なデジタル表現を正規表現エンジンに渡す代わりに、繰り返し文字で構成される長さ Nのシーケンスが渡されます。選言の最初の部分はN = 0またはN = 1をチェックし、2番目の部分は後方参照を使用して約数K> 1を探します。正規表現エンジンは、シーケンスを形成するために少なくとも2回繰り返すことができる空でないサブシーケンスを見つけます。そのようなサブシーケンスが存在する場合、その長さがNを分割することを意味するため、Nは素数ではありません。