長さが4乗の文字列に一致


28

この質問の範囲内で、文字xを任意の回数繰り返した文字列のみを考えてみましょう。

例えば:

<empty>
x
xx
xxxxxxxxxxxxxxxx

(まあ、実際にはそうである必要はありませんx-文字列全体が1種類の文字を持っている限り、どんな文字でも構いません)

任意の正規表現フレーバーで正規表現を記述して、非負整数n(n> = 0)の長さがn 4であるすべての文字列に一致させます。たとえば、長さ0、1、16、81などの文字列は有効です。残りは無効です。

技術的な制限により、128より大きいnの値をテストするのは困難です。ただし、正規表現は論理的には正しく動作するはずです。

(Perlユーザーに対して)正規表現で任意のコードを実行することは許可されていないことに注意してください。その他の構文(ルックアラウンド、後方参照など)は許可されます。

問題へのアプローチについての簡単な説明も含めてください。

(自動生成された正規表現構文の説明は役に立たないので貼り付けないでください)


「xx」は無効ですよね?
ケンドールフレイ14年

@KendallFrey:いいえ。無効です。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 14年

@nhahtdhそれに対する可能な答えがあると思いますか?
XEM

1
@Timwi:はい。Java、PCRE(おそらくPerlですが、テストできません)、. NET。ただし、Ruby / JSでは機能しません。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 14年

1
この質問は、「Advanced Regex-Fu」の下のStack Overflow Regular Expression FAQに追加されました。
aliteralmind

回答:


21

この(ir)正規表現は機能しているようです。

^((?(1)((?(2)\2((?(3)\3((?(4)\4x{24}|x{60}))|x{50}))|x{15}))|x))*$

この正規表現は、PCRE、Perl、.NETフレーバーと互換性があります。

これは基本的に「差分ツリー」に従います(適切な名前があるかどうかはわかりません)。次の4乗に一致するxの数を正規表現に伝えます。

1     16    81    256   625   1296  2401 ...
   15    65    175   369   671   1105 ...
      50    110   194   302   434 ...
         60    84    108   132 ...
            24    24    24 ...  # the differences level out to 24 on the 4th iteration

\2\3\4店舗それぞれ、第2、第3および第4行に示されるように差分更新。

この構成は、より高い出力に簡単に拡張できます。

確かにエレガントなソリューションではありませんが、機能します。


+1。素晴らしい答え。この答えは私のものとは異なりますが(条件付き正規表現を使用しますが、私のものではありません)、私のソリューションと同じ精神を持っています(差分ツリーを利用し、いくつかの正規表現エンジンの前方宣言された後方参照を使用します)。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 14年

きちんとしたアイデアの違いツリー。正方形の場合、ツリーは1 4 9 16 ... 3 5 7 ... 2 2 2です。
スパー

@Sparrありがとう、はい
ボラティリティ

24

別の解決策

これは、私の意見では、このサイトで最も興味深い問題の1つです。トップに戻してくれたデッドコードに感謝する必要があります。

^((^|xx)(^|\3\4\4)(^|\4x{12})(^x|\1))*$

39バイト、条件またはアサーションなし...並べ替え。使用されている代替(^|)は、ある意味で「最初の反復」と「最初の反復ではない」を選択するための条件の一種です。

この正規表現は、http//regex101.com/r/qA5pK3/1で機能することが確認できます

PCREとPythonの両方が正しく正規表現を解釈し、それはまた、アップにPerlで試験されたN = 128を含む、N 4 -1、及びN 4 +1


定義

一般的な手法は、既に投稿されている他のソリューションと同じです:後続の各反復で、前方差分関数の次の項D fに等しい長さに無制限の量指定子(*)で一致する自己参照式を定義します。前方差分関数の正式な定義:

定義1:前方差分関数

さらに、高階差分関数も定義できます。

定義2:2番目の前方差分関数

または、より一般的に:

定義3:k番目の前方差分関数

前方差分関数には多くの興味深い特性があります。それは、連続関数に対する微分が何であるかをシーケンスすることです。たとえば、n次多項式のD fは常にn-1次多項式であり、任意のiについて、D f i = D f i + 1の場合、関数fはほぼ同じように指数関数になります。e xの導関数がそれ自体に等しいこと。f = D f2 nである最も単純な離散関数。


f(n)= n 2

上記の解決策を検討する前に、少し簡単なものから始めましょう。長さが完全な正方形である文字列に一致する正規表現です。前方差分関数を調べる:

FDF:n ^ 2

つまり、最初の反復は長さ1のストリングと一致し、2番目は長さ3のストリング、3番目は長さ5のストリングと一致する必要がありますなどに一致する必要があります。一般に、各反復は前より2つ長い文字列に一致する必要があります。対応する正規表現は、このステートメントからほぼ直接続きます。

^(^x|\1xx)*$

最初の反復は1つだけに一致することがわかります xに一致し、後続の各反復は、指定されたとおりに、前の反復よりも2つ長い文字列に一致することがわかります。これは、perlでの驚くほど短い完全な正方形テストも意味します。

(1x$_)=~/^(^1|11\1)*$/

この正規表現は、任意のn角の長さに一致するようにさらに一般化できます。

三角数字:
^(^x|\1x{1})*$

平方数:
^(^x|\1x{2})*$

五角形の数字:
^(^x|\1x{3})*$

六角形の数字:
^(^x|\1x{4})*$


f(n)= n 3

移動のn 3再び前進差分機能を調べ、:

FDF:n ^ 3

これを実装する方法はすぐには明らかではない可能性があるため、2番目の差分関数も調べます。

FDF ^ 2:n ^ 3

したがって、前方差分関数は定数ではなく、線形値で増加します。D f 2の初期( ' -1 th')値がゼロであると便利です。これにより、2回目の反復で初期化が保存されます。結果の正規表現は次のとおりです。

^((^|\2x{6})(^x|\1))*$

前と同じように、最初の反復は1に一致し、2番目は6長い文字列(7)に一致し、3番目は12長い文字列(19)に一致します)に。


f(n)= n 4

n 4の前方差分関数:

FDF:n ^ 4

2番目の前方差分関数:

FDF ^ 2:n ^ 4

3番目の前方差分関数:

FDF ^ 3:n ^ 4

今ではthatいです。D f 2およびD f 3の初期値は両方ともゼロではなく、それぞれ2および12であり、これを考慮する必要があります。おそらく、正規表現は次のパターンに従うことになるでしょう。

^((^|\2\3{b})(^|\3x{a})(^x|\1))*$

D f 312の長さに一致する必要があるため第2の反復では、必然的である12。しかし、それは各項ごとに24ずつ増加するため、次のより深いネストは、以前の値を2回使用する必要があり、b = 2を意味します。最後に行うことは、D f 2を初期化することです。D f 2は最終的に一致させるD fに直接影響するため、この場合、適切なアトムを正規表現に直接挿入することでその値を初期化できます。最終的な正規表現は次のようになります。(^|xx)

^((^|xx)(^|\3\4{2})(^|\4x{12})(^x|\1))*$

高次

5次多項式は、次の正規表現と照合できます。
^((^|\2\3{c})(^|\3\4{b})(^|\4x{a})(^x|\1))*$

f(n)= n 5は、2番目と4番目の前方差分関数の両方の初期値がゼロであるため、かなり簡単な練習です。

^((^|\2\3)(^|\3\4{4})(^|\4x{30})(^x|\1))*$

6次多項式の場合:
^((^|\2\3{d})(^|\3\4{c})(^|\4\5{b})(^|\5x{a})(^x|\1))*$

7次多項式の場合:
^((^|\2\3{e})(^|\3\4{d})(^|\4\5{c})(^|\5\6{b})(^|\6x{a})(^x|\1))*$

必要な係数のいずれかが非整数である場合、すべての多項式がこの方法で正確に一致するわけではないことに注意してください。たとえば、n 6では、a = 60b = 8、およびc = 3/2が必要です。この場合、これを回避できます:

^((^|xx)(^|\3\6\7{2})(^|\4\5)(^|\5\6{2})(^|\6\7{6})(^|\7x{60})(^x|\1))*$

ここで、b6に変更し、c2に変更しました。これらの値は上記の値と同じです。a・b・c・…が一定の差関数を制御するため、積が変化しないことが重要です。これは、6次多項式ではD f 6です。初期化アトムが2つあります:1つは初期化するn 4のようにD f2は5番目の差分関数を360に初期化すると同時にbから欠落している2つを追加します。


どのエンジンでこれをテストしましたか?
n̴̖̋h̷͉a̷̭̿h̸̡̅ẗ̵̨d̷̰ĥ̷̳

私は最終的に何が起こっているのか理解しています。実際、必要なのは前方参照のサポートだけです。+1
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 14

@nhahtdhああ、あなたは正しい。前方参照も必ずしも汎用的な機能ではありません。
primo 14

1
優れた!これがどれほど短く、シンプルで、理解しやすいかが大好きです。ネストが浅いため、動作を手動で簡単に計算できます。また、これはVolatilityおよびnhahtdhのソリューションと同等に高速です。そして、これが多項式にまで拡張できることを示すデモンストレーションを含む、あなたの詳細な説明が大好きです。できればボーナスポイントを与えます。
デッドコード14

@Lynnありがとう!それを期待していなかった...
primo

13

以下に、条件、前方宣言またはネストされた後方参照、後読み、グループのバランス、または正規表現の再帰を使用しないソリューションを示します。先読みと標準の後方参照のみを使用します。これらは非常に広くサポートされています。ECMAScript正規表現エンジンを使用するRegex Golfにより、これらの制限の下で動作するようになりました。

この50バイトの正規表現は、概念的にはかなり単純であり、このパズルに対して提出された他のすべてのソリューションとはまったく異なります。この種の数学的論理が正規表現で表現できることを発見したのは驚きでした。

      \2                     \4  \5
^((?=(xx+?)\2+$)((?=\2+$)(?=(x+)(\4+)$)\5){4})*x?$

(キャプチャグループには正規表現の上にラベルが付けられます)

正規表現は、 4 in {4}を目的の累乗に。

オンラインでお試しください!

現在の値が割り切れる素数の最小の4乗を繰り返し分割することで機能します。したがって、元の値が4乗の場合、各ステップでの商は常に4乗になります。最終商1は、元の値が実際に4乗であることを示します。これで試合が完了します。ゼロも一致します。

最初に、レイジーキャプチャグループ\2を使用して、1より大きい数値の最小係数をキャプチャします。したがって、この係数は素数であることが保証されます。たとえば、1296(6 ^ 4)では、最初にキャプチャします\2 = 2をます。

その後、4回繰り返されるループの最初で、それは現在の数がで割り切れるかどうかをテスト\2して、(?=\2+$)。このループを初めて使用する場合、このテストは役に立ちませんが、その目的は後で明らかになります。

次に、この内部ループ内で、貪欲なキャプチャグループ\4を使用して、それ自体よりも小さい数の最大係数をキャプチャします(?=(x+)(\4+)$)。事実上、これは最小の素因数で数を除算し\2ます。たとえば、1296は最初は\4= 1296/2 = 648 としてキャプチャされます。現在の数値の除算は\2ます。は暗黙的。現在の数字をキャプチャグループに含まれる数字で明示的に除算することは可能ですが(この回答を投稿してから4日後に発見した)、これを行うと、遅くて理解しにくい正規表現になり、そうではありません必要なのは、1より大きい数値の最小係数は常に、それよりも小さい最大係数と一致するためです(その積は数値自体に等しくなるため)。

この種の正規表現は、文字列の末尾に結果を残すことによってのみ文字列から「食い尽くす」(小さくする)ことができるため、除算の結果を文字列の末尾に「移動」する必要があります。これは、減算の結果(現在の数値- \4)をキャプチャグループ\5にキャプチャし、次に先読みの外側で、に対応する現在の数値の先頭の部分と一致させることによって行われ\5ます。これにより、\4長さが一致する最後の残りの未処理の文字列が残ります。

ここで、ループが内側のループの先頭に戻ります。ここで、なぜ素因数による可分性のテストがあるのか​​が明らかになります。数の最小の素因数で割ったところです。数値がその係数で割り切れる場合、元の数値はその係数の4乗で割り切れる可能性があることを意味します。このテストが最初に実行されたときは役に立たないが、次の3回では、暗黙的に除算した結果がで\2割り切れるかどうかを判断し\2ます。\2ループの各反復の開始時にそれでも割り切れる場合、これは各反復が数値をで割ったことを証明します\2

この例では、入力が1296の場合、次のようにループします。

\2= 2
\4= 1296/2 = 648
\4= 648/2 = 324
\4= 324/2 = 162
\4= 162/2 = 81

これで、正規表現は最初のステップにループバックできます。これが決勝戦の*目的です。この例では、81が新しい番号になります。次のループは次のようになります。

\2= 3
\4= 81/3 = 27
\4= 27/3 = 9
\4= 9/3 = 3
\4= 3/3 = 1

1を新しい番号として、最初のステップに再びループバックします。

数値1は、任意の素数で割ることができないため、で不一致(?=(xx+?)\2+$)になるため、最上位のループ(*最後にあるループ)を終了します。今に達しますx?$。これは0または1にのみ一致します。この時点での現在の数値は、元の数値が完全な4乗の場合に限り、0または1になります。この時点で0である場合、トップレベルループは何にも一致しないことを意味し、1である場合、トップレベルループが完全に4のべき乗を分割できることを意味します。そもそも1でした。つまり、トップレベルループは何にも一致しませんでした。

また、明示的な除算を繰り返し行うことでこれを49バイトで解決することもできます(これはすべてのべき乗に対して一般化されます-目的のべき乗から1を引いたものをに置き換えます{3})が、この方法ははるかに遅く、使用するアルゴリズムの説明ですこの回答の範囲外です:

^((x+)((\2(x+))(?=(\4*)\2*$)\4*(?=\5$\6)){3})?x?$

オンラインでお試しください!


私のテスト(最大1024)から、それは正しいようです。ただし、正規表現は遅すぎます-長さ16 ^ 4に一致するのに時間がかかるため、多数の場合に検証するのは非常に困難です。ただし、パフォーマンスは必要ないため、正規表現を理解したときに賛成します。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 14

1
あなたの正規表現とボラティリティは素晴らしいです。それらの速度と簡潔さは私を驚かせます、どちらも私のi7-2600kで7.5秒で100000000に一致し、正規表現が予想されるよりもはるかに高速でした。ここでの私のソリューションは、50625に一致するのに12秒かかるため、規模はまったく異なります。しかし、私の目標は速度ではなく、はるかに限られた操作セットを使用して最小限のコード長でジョブを達成することでした。
デッドコード

バックトラッキングをほとんど行わないため、回答は高速です。あなたがで多くのバックトラックをし((((x+)\5+)\4+)\3+)\2+$ます。前方宣言された後方参照なしで平方数を一致させる方法を考えることさえできないので、あなた自身も驚くべきものです。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

ところで、この質問はコードゴルフではなく、パズルです。コードの長さで解決策を判断しません。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 14

ああ。それがあなたが使用した理由を説明します(?:)。最適化されたバージョンを主要バージョンにするために回答を編集する必要がありますか?
デッドコード14

8

溶液

^(?:(?=(^|(?<=^x)x|xx\1))(?=(^|\1\2))(^x|\3\2{12}xx))*$

この正規表現は、Java、Perl、PCRE、および.NETフレーバーと互換性があります。この正規表現は、先読み、後読み、および前方宣言された後方参照というかなりの範囲の機能を使用します。前方宣言された後方参照の種類は、この正規表現の互換性をいくつかのエンジンに制限します。

説明

このソリューションは、次の派生を利用します。

合計を完全に展開することにより、次の等式を証明できます。

\ sum \ limits_ {i = 1} ^ n(i + 1)^ 4-\ sum \ limits_ {i = 1} ^ ni ^ 4 =(n + 1)^ 4-1
\ sum \ limits_ {i = 1} ^ ni ^ 4-\ sum \ limits_ {i = 1} ^ n(i-1)^ 4 = n ^ 4

左側の合計を組み合わせてみましょう。

\ sum \ limits_ {i = 1} ^ n(4(i + 1)^ 3-6(i + 1)^ 2 + 4(i + 1)-1)=(n + 1)^ 4-1
\ sum \ limits_ {i = 1} ^ n(4i ^ 3-6i ^ 2 + 4i-1)= n ^ 4

2つの方程式(上の方程式から下の方程式を引いたもの)を減算し、左側の合計を結合してから単純化します。

\ sum \ limits_ {i = 1} ^ n(12i ^ 2 + 2)=(n + 1)^ 4-n ^ 4-1

連続する4乗の差を累乗の合計として取得します。

(n + 1)^ 4-n ^ 4 = \ sum \ limits_ {i = 1} ^ n(12i ^ 2 + 2)+ 1

これは、連続する4乗の(12n 2 + 2)増加ます。

考えやすくするために、 ボラティリティの答えの差分ツリー

  • 最終的な方程式の右側は、差分ツリーの2行目です。
  • 増分(12n 2 + 2)は、差分ツリーの3行目です。

十分な数学。上記のソリューションに戻ります。

  • 最初のキャプチャグループは、iを計算するために一連の奇数を維持しますは、方程式に見られるように 2ます。

    正確に言えば、最初のキャプチャグループの長さは、ループが繰り返されるときに0(未使用)、1、3、5、7、...になります。

    (?<=^x)x奇数シリーズの初期値を設定します。の^は、最初の反復で先読みが満たされるようにするためのものです。

    xx\1 2を加算し、次の奇数に進みます。

  • 2番目のキャプチャグループは、iの平方数シリーズを維持します 2のます。

    正確に言えば、2番目のキャプチャグループの長さは、ループが繰り返されるときに0、1、4、9、...になります。

    ^in (^|\1\2)は、2進数シリーズの初期値を設定します。そして\1\2、奇数を現在の平方数に加算して、次の平方数に進めます。

  • 3番目のキャプチャグループ(先読みの外で実際にテキストを消費する)は、上で導出した方程式の右辺全体に一致します。

    ^xin は、方程式の右辺(^x|\3\2{12}xx)である初期値を設定します+ 1

    \3\2{12}xxキャプチャグループ2のn 2を使用して差の増加(12n 2 + 2)を追加し、同時に差を一致させます。

この配置は、各反復で一致するテキストの量が、先読みを実行してn 2を構築するのに必要なテキストの量以上であるために可能です。

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