数が正規表現で素数であるかどうかを判断する方法は?


128

RosettaCodeでJavaの次のコード例を見つけました。

public static boolean prime(int n) {
  return !new String(new char[n]).matches(".?|(..+?)\\1+");
}
  • 私は特にJavaは知りませんが、正規表現自体を除いて、このスニペットのすべての側面を理解しています
  • 組み込みのPHP関数で見つかるように、Regexの基本的な知識から基本的な高度な知識があります。

.?|(..+?)\\1+素数をどのように照合しますか?


9
@Amir Rachum:!new String(new char[n]).matches(".?|(..+?)\\1+")と同等!((new String(new char[n])).matches(".?|(..+?)\\1+"))です。
ガンボ

14
これは計算コストが高いだけでなく、メモリを大幅に消費する可能性もあります。誰かがこのアプローチを使用することを選択した場合、素数を見つけるためのアルゴリズムは非常に単純であるため(私はこのアプローチを使用しないことをお勧めします(なぜ、世界でそれが複雑になり、非常に無駄になるのか))、「新しい文字[n ] "が妥当なしきい値を下回っていることを確認します。たとえば、 "prime(Integer.MAX_VALUE)"を呼び出し、OutOfMemoryErrorがスローされたときにバグを報告します。
nicerobot

28
@nicerobot:明るくなった?
Cam

6
@nicerobot:実際、私はそれを取り戻します。私はもともと、この質問の学問的な性質が学習目的での使用のみを意味し、あなたが不愉快な嫌なことをしていると考えました。しかし、そうではないと考え直した。正規表現が学習目的のみであるという質問には、言及も暗示もされていません。実際、私の第一印象は、コードスニペットに関しては非常にシンプルに見えるため、初心者は実際に使用できると想定しているかもしれません。+1。
Cam

7
@incrediman心配ありません。私はあなたがそれをどう思うか見ることができます。これを使用した結果について警告するのは私の意図であり、それがどのように機能するかを学ぶのをやめさせるためではありませんでした。単純な「これをデプロイしないでください」。私のコメントの残りの部分の前に、それはあなたの最初の観点からそれを軽視することを少なくしたかもしれません。
nicerobot 2010

回答:


120

あなたはこの部分を理解したと言いましたが、強調するために、生成された文字列の長さは提供された数と同じです。したがって、文字列は3文字ですn == 3

.?

正規表現の最初の部分は、「任意の文字、0回または1回」とあります。つまり、基本的に、ゼロまたは1つの文字が存在します-または、前述のとおりですn == 0 || n == 1。一致した場合は、その否定を返します。これは、ゼロと1が素数ではないという事実に対応しています。

(..+?)\\1+

正規表現の2番目の部分は少しトリッキーで、グループと後方参照に依存しています。グループは括弧で囲まれたものであり、後で使用するために正規表現エンジンによってキャプチャおよび保存されます。後方参照は、後で同じ正規表現で使用される一致したグループです。

グループは1文字をキャプチャし、次に任意の文字を1つ以上キャプチャします。(+文字は1つ以上を意味しますが、前の文字またはグループのみを意味します。したがって、これは「2、4、6などの文字」ではなく、「2、3など」です。+?は+に似ていますが、 +は可能な限り少ない文字数に一致しようとします。通常、可能な場合は文字列全体をぐちゃぐちゃにしようとしますが、これは後方参照部分が機能しなくなるため、この場合は好ましくありません。)

次の部分は後方参照です。同じ文字のセット(2つ以上)が再び表示されます。この後方参照は1回以​​上出現します。

そう。キャプチャされたグループは、キャプチャされた(2以降の)自然数の文字に対応します。その後、このグループは自然な回数だけ出現します(これも2回以降)。一致する場合、これは、n以上の長さの文字列と一致する2以上の2つの数値の積を見つけることが可能であることを意味します...つまり、複合nがあることを意味します。したがって、もう一度、成功した一致の否定を返します。nは素数ではありません。

一致が見つからない場合、2以上の2つの自然数の積を計算することはできません...そして、不一致と素数の両方があるため、再び否定が返されます。試合結果の。

今見えますか?それは信じられないほどトリッキーです(そして計算的に高価です!)が、それを手に入れると、それは同時に一種の単純さでもあります。:-)

正規表現解析が実際にどのように機能するかなど、さらに質問がある場合は詳しく説明します。しかし、私は今のところ、この答えを単純に(または、可能な限り単純に)保つようにしています。


10
私はクロムの開発コンソールでJSを使用してこのロジックを試しました。ウェブページで。チェックするために5を渡しました。ページがクラッシュしました!
Amogh Talpallikar 2013年

以下のコメントはより良い説明を与えます。次に進む前に必ずお読みください。
Ivan Davidov

「より良い」とは主観的なものです。別の角度から問題に取り組み、この答えを素晴らしい形で補完すると思います。:-)
プラチナAzure

1
私は実際にこれをより詳細に説明するブログ投稿を書きました:数値が素数であるかどうかをチェックする正規表現の謎を解く
Illya Gerasymchuk

73

素数性テスト以外の正規表現の部分について説明します。次の正規表現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長さのを生成しますnchar
  • 正規表現はString、ある長さ(たとえばk)をに取り込み\1\1+残りの部分と一致させようとしますString
    • 一致する場合、nはの適切な倍数であるkため、n素数ではありません。
    • 一致するものがない場合には、そのようなは、kその分裂を存在しないn、とnので素数であります

.?|(..+?)\1+素数をどのように照合しますか?

実際にはそうではありません!長さが素数ではない人と一致し Stringます!

  • .?String長さの代替一致の最初の部分0または1(定義により素数ではない)
  • (..+?)\1+:交互の第2の部分は、正規表現のバリエーションは、上記説明し、一致Stringnの「複数」であるString長さのk >= 2(すなわちn、複合、NOT素数です)。
    • 消極的修飾子がいることを注意?実際正しさは必要ありませんが、それは小さな試みることによってプロセスのスピードアップを助けるかもしれないk最初の

ステートメントの! boolean補数演算子に注意してreturnください。これはを否定しmatchesます。それは正規表現が一致しないときです、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 reluctant modifier on \1(明確化のため省略)を除いて、上記の正規表現は元の正規表現と同じです。


より楽しい正規表現

次の正規表現は同様の手法を使用しています。それは教育的であるべきです:

System.out.println(
    "OhMyGod=MyMyMyOhGodOhGodOhGod"
        .replaceAll("^(.+)(.+)(.+)=(\\1|\\2|\\3)+$", "$1! $2! $3!")
); // prints "Oh! My! God!"

こちらもご覧ください


6
+1:あなたのアプローチはおそらく私のものよりも優れていると思います。なぜ私がこれほど多くの賛成票やチェックマークを取得したのかはわかりません。:-(申し訳ありません
プラチナアズール

@プラチナ:うわー、あなたがそれを公に言って一周するなんて思ってもみなかった!ご支援ありがとうございます。たぶん、これから[Populist]一日を過ごすでしょう。
polygenelubricants

2
まあ、それは真実です(私が認識しているように)...それほど大きな問題ではありません。私は担当者のためにここにいるわけではありません(ただし、それは常にボーナスであり、嬉しい驚きです)...可能な場合は、質問に答えるためにここにいます。したがって、誰かが私が特定の質問で持っているよりも上手くやったときに、私が認めることができるのは当然のことです。
Platinum Azure

25

ニース正規表現のトリック(非常に非効率的ですが)... :)

正規表現では、非素数を次のように定義しています。

N <= 1またはNがK> 1で割り切れる場合に限り、Nは素数ではありません。

Nの単純なデジタル表現を正規表現エンジンに渡す代わりに、繰り返し文字で構成される長さ Nのシーケンスが与えられます。選言の最初の部分はN = 0またはN = 1をチェックし、2番目の部分は後方参照を使用してK> 1の除数を探します。これは、正規表現エンジンに、シーケンスを形成するために少なくとも2回繰り返すことができるいくつかの空でないサブシーケンスを検出するように強制します。そのようなサブシーケンスが存在する場合、その長さがNを分割することを意味します。したがって、Nは素数ではありません。


2
奇妙なことに、他のより長くより技術的な説明を繰り返し読んだ後でも、私はこの説明が私の頭の中で「クリック」した原因であることがわかりました。
Eight-Bit Guru

2
/^1?$|^(11+?)\1+$/

基数1に変換した後の数値に適用します(1 = 1、2 = 11、3 = 111、...)。非プライムはこれと一致します。一致しない場合は素数です。

説明はこちら

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.