ECMAScript Regex, 733+ 690+ 158 119 118 (117🐌) octets
Mon intérêt pour regex a été suscité avec une vigueur renouvelée après plus de quatre ans et demi d'inactivité. En tant que tel, je suis allé à la recherche d'ensembles de nombres et de fonctions plus naturels à assortir à des expressions rationnelles unaires ECMAScript, j'ai repris l'amélioration de mon moteur d'expression régulière et ai commencé à mettre à jour PCRE.
Je suis fasciné par l'aliénation de la construction de fonctions mathématiques dans les expressions rationnelles ECMAScript. Les problèmes doivent être abordés sous un angle totalement différent et, jusqu'à l'arrivée d'une idée clé, on ne sait pas s'ils sont résolus. Cela oblige à utiliser un réseau beaucoup plus large pour déterminer quelles propriétés mathématiques pourraient être utilisées pour résoudre un problème particulier.
La correspondance des nombres factoriels était un problème que je n'avais même pas envisagé de résoudre en 2014 - ou, le cas échéant, momentanément, le rejetant comme étant trop improbable pour être possible. Mais le mois dernier, j'ai réalisé que cela pourrait être fait.
Comme pour mes autres posts sur les expressions rationnelles ECMA, je tiens à vous avertir: je vous recommande vivement d'apprendre à résoudre des problèmes mathématiques unaires dans ECMAScript regex. Ce voyage a été fascinant pour moi et je ne veux pas le gâter pour ceux qui voudraient éventuellement l'essayer eux-mêmes, en particulier ceux qui s'intéressent à la théorie des nombres. Voir ce message précédent pour une liste de problèmes recommandés consécutivement étiquetés spoiler à résoudre un par un.
Donc , ne lisez pas plus loin si vous ne voulez pas que la magie avancée des regex unaires soit gâtée . Si vous voulez vraiment essayer de découvrir cette magie vous-même, je vous recommande vivement de commencer par résoudre certains problèmes de la regex ECMAScript, comme indiqué dans l'article ci-dessus.
C'était mon idée:
Le problème de la correspondance de cet ensemble de numéros, comme de la plupart des autres, est que dans ECMA, il est généralement impossible de garder trace de deux numéros changeants dans une boucle. Parfois, ils peuvent être multiplexés (par exemple, des puissances de la même base peuvent être additionnées sans ambiguïté), mais cela dépend de leurs propriétés. Je ne pouvais donc pas simplement commencer par le nombre entré et le diviser par un dividende croissant progressivement jusqu'à atteindre 1 (ou du moins je le pensais, au moins).
Ensuite, j'ai fait des recherches sur les multiplicités des facteurs premiers en nombres factoriels et appris qu'il existe une formule pour cela - et que je pourrais probablement la mettre en œuvre dans une regex ECMA!
Après avoir cuit dessus pendant un certain temps et construit entre-temps d'autres expressions rationnelles, je me suis lancé dans la tâche d'écrire l'expression rationnelle factorielle. Cela a pris plusieurs heures, mais a fini par bien fonctionner. En prime, l'algorithme pourrait renvoyer la factorielle inverse sous forme de correspondance. Il n'y avait pas moyen de l'éviter, même; De par la nature même de la manière dont il doit être mis en œuvre dans ECMA, il est nécessaire de deviner ce qu'est la factorielle inverse avant de faire autre chose.
L’inconvénient est que cet algorithme créait une très longue expression régulière ... regex octet). J'espérais qu'un problème nécessitant cette astuce se présenterait: Opérer sur deux nombres, qui sont tous deux des puissances de la même base, en boucle, en les additionnant sans ambiguïté et en les séparant à chaque itération.
Mais à cause de la difficulté et de la longueur de cet algorithme, j'ai utilisé des propriétés d'anticipation moléculaires (de la forme (?*...)
) pour le mettre en œuvre. Il s’agit d’une fonctionnalité qui ne figure pas dans ECMAScript ni dans aucun autre moteur regex traditionnel, mais que j’ai implémentée dans mon moteur . Sans capture à l'intérieur d'un look moléculaire moléculaire, son fonctionnement est équivalent à un look atomique, mais avec des captures, il peut être très puissant. Le moteur fera un retour en arrière dans la vue anticipée, ce qui peut être utilisé pour conjecturer une valeur qui parcourt toutes les possibilités (pour des tests ultérieurs) sans consommer les caractères de l'entrée. Leur utilisation peut permettre une implémentation beaucoup plus propre. (La recherche de longueur variable est à tout le moins égale en puissance à la présentation moléculaire, mais cette dernière a tendance à donner lieu à des implémentations plus simples et élégantes.)
Ainsi, les longueurs de 733 et 690 octets ne représentent pas réellement des incarnations de la solution compatibles avec ECMAScript - d'où le "+" après celles-ci; il est sûrement possible de porter cet algorithme en ECMAScript pur (ce qui augmenterait sa longueur un peu), mais je ne l'ai pas compris… parce que j'ai pensé à un algorithme beaucoup plus simple et plus compact! Une solution qui pourrait facilement être mise en œuvre sans avoir une apparence moléculaire. C'est aussi beaucoup plus rapide.
Ce nouveau, comme le précédent, doit deviner la factorielle inverse, en passant en revue toutes les possibilités et en les testant pour une correspondance. Il divise N par 2 pour faire de la place pour le travail à effectuer, puis amorce une boucle dans laquelle il divise à plusieurs reprises l'entrée par un diviseur qui commence à 3 et s'incrémente à chaque fois. (En tant que tel, 1! Et 2! Ne peuvent pas être comparés par l'algorithme principal et doivent être traités séparément.) Le diviseur est conservé en l'ajoutant au quotient en cours d'exécution; ces deux nombres peuvent être séparés sans ambiguïté car, en supposant que M! == N, le quotient courant continuera d'être divisible par M jusqu'à ce qu'il soit égal à M.
Cette expression rationnelle effectue une division par une variable dans la partie la plus interne de la boucle. L'algorithme de division est le même que dans mes autres expressions rationnelles (et similaire à l'algorithme de multiplication): pour A≤B, A * B = C s'il n'y en a que si C% A = 0 et B est le plus grand nombre qui satisfait B≤C et C% B = 0 et (CB- (A-1))% (B-1) = 0, où C est le dividende, A le diviseur et B le quotient. (Un algorithme similaire peut être utilisé dans le cas où A≥B, et si on ne sait pas comment A se compare à B, un test de divisibilité supplémentaire suffit.)
J'adore donc que le problème ait pu être réduit à un niveau de complexité encore inférieur à celui de mon regex de Fibonacci optimisé pour le golf , mais je soupire avec déception que ma technique de multiplexage des pouvoirs de la même base devra attendre un autre problème. cela l'exige réellement, parce que celui-ci n'en a pas. C'est l'histoire de mon algorithme de multiplication de 651 octets remplacé par un algorithme de 50 octets, encore et encore!
Edit: J'ai pu supprimer 1 octet (119 → 118) en utilisant une astuce trouvée par Grimy qui peut encore raccourcir la division dans le cas où le quotient est garanti supérieur ou égal au diviseur.
Sans plus tarder, voici la regex:
Version vraie / fausse (118 octets):
^((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?$
Essayez-le en ligne!
Renvoyer une factorielle inverse ou une absence de correspondance (124 octets):
^(?=((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?$
Essayez-le en ligne!
Renvoie une factorielle inverse ou une absence de correspondance, en ECMAScript +\K
(120 octets):
^((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?$
Et la version libre avec commentaires:
^
(?= # 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
L'historique complet de mes optimisations de golf de ces regex est sur github:
regex pour les nombres factoriels correspondants - méthode de comparaison de multiplicité, avec
regex moléculaire lookahead.txt pour les nombres factoriels correspondants.txt (celui présenté ci-dessus)
Notez que vous ((x*)x*)
pouvez changer pour((x*)+)
((x+)+)
n = 3 !\2
3 - 3 = 0
Le moteur de regex .NET n'émule pas ce comportement en mode ECMAScript, et le regex de 117 octets fonctionne:
Essayez-le en ligne! (version à ralentissement exponentiel, avec moteur de régression .NET + émulation ECMAScript)
1
?