ECMAScript Regex, 733+ 690+ 158 119 118 (117🐌) Bytes
Mein Interesse an Regex hat sich nach über 4½ Jahren Inaktivität mit neuer Kraft geweckt. Aus diesem Grund suchte ich nach natürlicheren Zahlensätzen und -funktionen, die mit unären ECMAScript-Regexen übereinstimmen, nahm die Verbesserung meiner Regex-Engine wieder auf und begann, auch PCRE auf den neuesten Stand zu bringen.
Ich bin fasziniert von der Fremdheit, mathematische Funktionen in ECMAScript Regex zu konstruieren. Probleme müssen aus einer ganz anderen Perspektive angegangen werden, und bis zu einer entscheidenden Einsicht ist nicht bekannt, ob sie überhaupt lösbar sind. Sie zwingt dazu, ein viel breiteres Netz zu werfen, um herauszufinden, welche mathematischen Eigenschaften verwendet werden können, um ein bestimmtes Problem lösbar zu machen.
Die Übereinstimmung von Fakultätszahlen war ein Problem, das ich 2014 gar nicht in Betracht gezogen habe - oder wenn ich es nur für einen Moment als zu unwahrscheinlich abgetan habe, dass es möglich ist. Aber letzten Monat wurde mir klar, dass es möglich ist.
Wie bei meinen anderen ECMA-regulären Texten gebe ich eine Warnung: Ich empfehle dringend, zu lernen, wie man unäre mathematische Probleme in ECMAScript regulären Texten löst. Es war eine faszinierende Reise für mich, und ich möchte sie keinem verderben, der sie möglicherweise selbst ausprobieren möchte, insbesondere denen, die sich für Zahlentheorie interessieren. In diesem früheren Beitrag finden Sie eine Liste der nacheinander mit Spoiler-Tags gekennzeichneten empfohlenen Probleme, die nacheinander gelöst werden müssen.
So lesen keine weiteren , wenn Sie nicht einige erweiterte einstellige regex Magie für Sie verwöhnen wollen . Wenn Sie versuchen möchten, diese Magie selbst herauszufinden, empfehle ich dringend, zunächst einige Probleme in ECMAScript regex zu lösen, wie in dem oben verlinkten Beitrag beschrieben.
Das war meine Idee:
Das Problem beim Abgleichen dieses Nummernsatzes ist, wie bei den meisten anderen, dass es in ECMA normalerweise nicht möglich ist, zwei sich ändernde Nummern in einer Schleife zu verfolgen. Manchmal können sie gemultiplext werden (z. B. können Potenzen derselben Basis eindeutig addiert werden), dies hängt jedoch von ihren Eigenschaften ab. Ich konnte also nicht einfach mit der eingegebenen Zahl beginnen und sie durch eine inkrementell ansteigende Dividende dividieren, bis ich 1 erreiche (zumindest dachte ich).
Dann habe ich einige Nachforschungen über die Vielzahl von Primfaktoren in Fakultätszahlen angestellt und herausgefunden, dass es eine Formel dafür gibt - und diese könnte ich wahrscheinlich in einen ECMA-Regex implementieren!
Nachdem ich eine Weile darüber nachgedacht und in der Zwischenzeit einige andere Regexe konstruiert hatte, begann ich, die faktorielle Regex zu schreiben. Es hat einige Stunden gedauert, hat aber gut funktioniert. Als zusätzlichen Bonus könnte der Algorithmus die inverse Fakultät als Übereinstimmung zurückgeben. Es gab nicht einmal einen Ausweg; Aufgrund der Art und Weise, wie es in ECMA implementiert werden muss, ist es erforderlich, vor allen anderen Maßnahmen eine Abschätzung der Umkehrung der Fakultät vorzunehmen.
Der Nachteil war, dass dieser Algorithmus für einen sehr langen regulären Ausdruck sorgte ... aber ich war erfreut, dass dafür eine Technik erforderlich war, die in meinem regulären Ausdruck mit 651-Byte-Multiplikation (der letztendlich veraltet war, weil eine andere Methode für einen 50-Byte-Ausdruck sorgte) verwendet wurde Byte Regex). Ich hatte gehofft, dass ein Problem auftaucht, das diesen Trick erfordert: Man operiert mit zwei Zahlen, die beide Potenzen derselben Basis sind, in einer Schleife, indem man sie eindeutig addiert und bei jeder Iteration trennt.
Aufgrund der Schwierigkeit und Länge dieses Algorithmus verwendete ich molekulare Lookaheads (der Form (?*...)
), um ihn zu implementieren. Dies ist eine Funktion, die nicht in ECMAScript oder einer anderen regulären Regex-Engine enthalten ist, sondern die ich in meiner Engine implementiert habe . Ohne Aufnahmen in einem molekularen Lookahead ist es funktional äquivalent zu einem atomaren Lookahead, aber mit Aufnahmen kann es sehr leistungsfähig sein. Die Engine kehrt in den Lookahead zurück und kann verwendet werden, um einen Wert zu erraten, der alle Möglichkeiten (für spätere Tests) durchläuft, ohne Zeichen der Eingabe zu verbrauchen. Ihre Verwendung kann zu einer viel saubereren Implementierung führen. (Lookbehind mit variabler Länge hat mindestens die gleiche Leistung wie molekularer Lookahead, aber letzterer führt tendenziell zu einfacheren und eleganteren Implementierungen.)
Die Längen von 733 und 690 Byte stellen also keine ECMAScript-kompatiblen Inkarnationen der Lösung dar - daher das "+" nach ihnen; Es ist sicherlich möglich, diesen Algorithmus auf reines ECMAScript zu portieren (was seine Länge beträchtlich erhöhen würde), aber ich bin nicht dazu gekommen ... weil ich an einen viel einfacheren und kompakteren Algorithmus gedacht habe! Eine, die ohne molekulare Lookaheads problemlos implementiert werden kann. Es ist auch deutlich schneller.
Dieses neue muss wie das vorherige die umgekehrte Fakultät erraten, alle Möglichkeiten durchgehen und sie auf ein Match testen. Sie dividiert N durch 2, um Platz für die zu erledigende Arbeit zu schaffen, und bildet dann eine Schleife, in der die Eingabe wiederholt durch einen Teiler geteilt wird, der bei 3 beginnt und jedes Mal inkrementiert wird. (Daher können 1! Und 2! Vom Hauptalgorithmus nicht abgeglichen werden und müssen separat behandelt werden.) Der Divisor wird verfolgt, indem er zum laufenden Quotienten hinzugefügt wird. Diese beiden Zahlen können eindeutig getrennt werden, da unter der Annahme von M! == N, der laufende Quotient bleibt durch M teilbar, bis er gleich M ist.
Dieser reguläre Ausdruck dividiert durch eine Variable im innersten Teil der Schleife. Der Divisionsalgorithmus ist der gleiche wie in meinen anderen Regexen (und ähnlich dem Multiplikationsalgorithmus): für A ≤ B ist A * B = C, falls überhaupt, nur wenn C% A = 0 und B die größte Zahl ist, die B ≤ C erfüllt und C% B = 0 und (CB- (A-1))% (B-1) = 0, wobei C die Dividende ist, A der Divisor ist und B der Quotient ist. (Ein ähnlicher Algorithmus kann für den Fall verwendet werden, dass A ≥ B ist, und wenn nicht bekannt ist, wie A mit B verglichen wird, ist nur ein zusätzlicher Teilbarkeitstest erforderlich.)
Ich finde es toll , dass das Problem auf noch weniger Komplexität reduziert werden konnte als mein golfoptimierter Fibonacci-Regex , aber ich seufze mit Enttäuschung, dass meine Multiplexing-Potenzen-der-gleichen-Basis-Technik auf ein anderes Problem warten muss Das erfordert es tatsächlich, weil dies nicht der Fall ist. Es ist die Geschichte meines 651-Byte-Multiplikationsalgorithmus, der durch einen 50-Byte-Algorithmus ersetzt wird, und zwar immer wieder!
Bearbeiten: Ich konnte 1 Byte (119 → 118) mit einem von Grimy gefundenen Trick löschen , der die Division für den Fall verkürzen kann, dass der Quotient garantiert größer oder gleich dem Divisor ist.
Hier ist der reguläre Ausdruck:
True / False-Version (118 Bytes):
^((x*)x*)(?=\1$)(?=(xxx\2)+$)((?=\2\3*(x(?!\3)xx(x*)))\6(?=\5+$)(?=((x*)(?=\5(\8*$))x)\7*$)x\9(?=x\6\3+$))*\2\3$|^xx?$
Probieren Sie es online!
Rückgabe inverser Fakultäten oder Nichtübereinstimmung (124 Bytes):
^(?=((x*)x*)(?=\1$)(?=(xxx\2)+$)((?=\2\3*(x(?!\3)xx(x*)))\6(?=\5+$)(?=((x*)(?=\5(\8*$))x)\7*$)x\9(?=x\6\3+$))*\2\3$)\3|^xx?$
Probieren Sie es online!
Rückgabe inverser Fakultäten oder Nichtübereinstimmung\K
in ECMAScript + (120 Byte):
^((x*)x*)(?=\1$)(?=(xxx\2)+$)((?=\2\3*(x(?!\3)xx(x*)))\6(?=\5+$)(?=((x*)(?=\5(\8*$))x)\7*$)x\9(?=x\6\3+$))*\2\K\3$|^xx?$
Und die freie Version mit Kommentaren:
^
(?= # Remove this lookahead and the \3 following it, while
# preserving its contents unchanged, to get a 119 byte
# regex that only returns match / no-match.
((x*)x*)(?=\1$) # Assert that tail is even; \1 = tail / 2;
# \2 = (conjectured N for which tail == N!)-3; tail = \1
(?=(xxx\2)+$) # \3 = \2+3 == N; Assert that tail is divisible by \3
# The loop is seeded: X = \1; I = 3; tail = X + I-3
(
(?=\2\3*(x(?!\3)xx(x*))) # \5 = I; \6 = I-3; Assert that \5 <= \3
\6 # tail = X
(?=\5+$) # Assert that tail is divisible by \5
(?=
( # \7 = tail / \5
(x*) # \8 = \7-1
(?=\5(\8*$)) # \9 = tool for making tail = \5\8
x
)
\7*$
)
x\9 # Prepare the next iteration of the loop: X = \7; I += 1;
# tail = X + I-3
(?=x\6\3+$) # Assert that \7 is divisible by \3
)*
\2\3$
)
\3 # Return N, the inverse factorial, as a match
|
^xx?$ # Match 1 and 2, which the main algorithm can't handle
Die vollständige Geschichte meiner Golfoptimierungen dieser Regexes ist auf Github:
Regex für die Anpassung von Fakultätszahlen - Multiplizitätsvergleichsmethode mit molekularer lookahead.txt
Regex für die Anpassung von Fakultätszahlen.txt (die oben gezeigte)
((x*)x*)
((x*)+)
((x+)+)
n=3!\2
3−3=0
Die .NET-Regex-Engine emuliert dieses Verhalten in ihrem ECMAScript-Modus nicht, und daher funktioniert die 117-Byte-Regex:
Probieren Sie es online! (Exponential-Slow-Down-Version, mit .NET-Regex-Engine + ECMAScript-Emulation)
1
?