ECMAScript Regex, 733+ 690+ 158 119 118 (117🐌) bytes
Mi interés en la expresión regular se ha despertado con un vigor renovado después de más de 4 años y medio de inactividad. Como tal, fui en busca de conjuntos de números y funciones más naturales para que coincidan con expresiones regulares ECMAScript unarias, continué mejorando mi motor de expresiones regulares y comencé a repasar PCRE también.
Me fascina la extrañeza de construir funciones matemáticas en expresiones regulares ECMAScript. Los problemas deben abordarse desde una perspectiva completamente diferente, y hasta la llegada de una idea clave, se desconoce si tienen solución alguna. Obliga a echar una red mucho más amplia para encontrar qué propiedades matemáticas podrían utilizarse para resolver un problema particular.
La coincidencia de números factoriales fue un problema que ni siquiera consideré abordar en 2014, o si lo hice, solo momentáneamente, descartándolo como demasiado improbable. Pero el mes pasado, me di cuenta de que podía hacerse.
Al igual que con mis otras publicaciones de expresiones regulares de ECMA, daré una advertencia: recomiendo aprender a resolver problemas matemáticos unarios en expresiones regulares de ECMAScript. Ha sido un viaje fascinante para mí, y no quiero estropearlo para cualquiera que quiera probarlo ellos mismos, especialmente aquellos interesados en la teoría de números. Consulte esta publicación anterior para obtener una lista de problemas recomendados etiquetados consecutivamente con spoilers para resolver uno por uno.
Así que no sigas leyendo si no quieres que se te estropee un poco de magia regex unaria avanzada . Si desea intentar descubrir esta magia usted mismo, le recomiendo comenzar resolviendo algunos problemas en la expresión regular de ECMAScript como se describe en la publicación vinculada anteriormente.
Esta fue mi idea:
El problema con la coincidencia de este conjunto de números, como con la mayoría de los demás, es que en ECMA generalmente no es posible realizar un seguimiento de dos números cambiantes en un bucle. A veces se pueden multiplexar (por ejemplo, los poderes de la misma base se pueden sumar sin ambigüedad), pero depende de sus propiedades. Por lo tanto, no podría comenzar con el número de entrada y dividirlo por un dividendo incrementalmente creciente hasta llegar a 1 (o eso pensé, al menos).
Luego investigué un poco sobre la multiplicidad de factores primos en números factoriales, y aprendí que hay una fórmula para esto , ¡y es una que probablemente podría implementar en una expresión regular de ECMA!
Después de estofarlo durante un tiempo y, mientras tanto, construir otras expresiones regulares, tomé la tarea de escribir la expresión regular factorial. Tomó varias horas, pero terminó funcionando bien. Como una ventaja adicional, el algoritmo podría devolver el factorial inverso como una coincidencia. No había forma de evitarlo, incluso; Por la naturaleza misma de cómo debe implementarse en ECMA, es necesario adivinar cuál es el factorial inverso antes de hacer cualquier otra cosa.
La desventaja es que este algoritmo generó una expresión regular muy larga ... pero me complació que terminara requiriendo una técnica utilizada en mi expresión regular de multiplicación de 651 bytes (la que terminó siendo obsoleta, porque un método diferente hizo un 50 byte regex). Esperaba que surgiera un problema que requiriera este truco: operar con dos números, que son ambas potencias de la misma base, en un bucle, al sumarlos sin ambigüedad y separarlos en cada iteración.
Pero debido a la dificultad y la longitud de este algoritmo, utilicé lookaheads moleculares (de la forma (?*...)
) para implementarlo. Esa es una característica que no está en ECMAScript ni en ningún otro motor de expresiones regulares convencional, sino una que había implementado en mi motor . Sin ninguna captura dentro de una búsqueda molecular por adelantado, es funcionalmente equivalente a una búsqueda atómica, pero con las capturas puede ser muy potente. El motor retrocederá hacia adelante, y esto puede usarse para conjeturar un valor que recorre todas las posibilidades (para pruebas posteriores) sin consumir caracteres de la entrada. Usarlos puede hacer una implementación mucho más limpia. (La mirada retrospectiva de longitud variable es al menos igual en potencia a la búsqueda molecular adelantada, pero esta última tiende a hacer implementaciones más directas y elegantes).
Por lo tanto, las longitudes de 733 y 690 bytes no representan en realidad encarnaciones de la solución compatibles con ECMAScript, de ahí el "+" después de ellas; seguramente es posible portar ese algoritmo a ECMAScript puro (lo que aumentaría un poco su longitud), pero no lo logré ... ¡porque pensé en un algoritmo mucho más simple y compacto! Uno que podría implementarse fácilmente sin miradas moleculares. También es significativamente más rápido.
Este nuevo, como el anterior, debe adivinar el factorial inverso, recorrer todas las posibilidades y probarlas para un partido. Divide N entre 2 para dejar espacio para el trabajo que necesita hacer, y luego siembra un ciclo en el que dividirá repetidamente la entrada por un divisor que comienza en 3 e incrementa cada vez. (Como tal, 1! Y 2! No pueden coincidir con el algoritmo principal, y deben tratarse por separado). El divisor se mantiene al agregarlo al cociente corriente; estos dos números se pueden separar inequívocamente porque, suponiendo M! == N, el cociente corriente continuará siendo divisible por M hasta que sea igual a M.
Esta expresión regular se divide por una variable en la parte más interna del bucle. El algoritmo de división es el mismo que en mis otras expresiones regulares (y similar al algoritmo de multiplicación): para A≤B, A * B = C si existe solo si C% A = 0 y B es el número más grande que satisface B≤C y C% B = 0 y (CB- (A-1))% (B-1) = 0, donde C es el dividendo, A es el divisor y B es el cociente. (Se puede usar un algoritmo similar para el caso de que A≥B, y si no se sabe cómo A se compara con B, una prueba de divisibilidad adicional es todo lo que se necesita).
Así que me encanta que el problema se haya podido reducir a una complejidad aún menor que la expresión regular de Fibonacci optimizada para el golf , pero suspiro con decepción porque mi técnica de multiplexación de poderes de la misma base tendrá que esperar otro problema eso realmente lo requiere, porque este no. ¡Es la historia de que mi algoritmo de multiplicación de 651 bytes fue reemplazado por uno de 50 bytes, todo de nuevo!
Editar: pude soltar 1 byte (119 → 118) usando un truco encontrado por Grimy que puede acortar aún más la división en el caso de que el cociente sea mayor o igual que el divisor.
Sin más preámbulos, aquí está la expresión regular:
Versión verdadera / falsa (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?$
Pruébalo en línea!
Devuelve factorial inverso o no coincidencia (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?$
Pruébalo en línea!
Devuelve factorial inverso o no coincidencia, en ECMAScript +\K
(120 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\K\3$|^xx?$
Y la versión libre con comentarios:
^
(?= # 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
La historia completa de mis optimizaciones de golf de estas expresiones regulares está en github:
regex para hacer coincidir números factoriales: método de comparación de multiplicidad, con lookahead.txt molecular
regex para hacer coincidir números factoriales.txt (el que se muestra arriba)
((x*)x*)
((x*)+)
((x+)+)
n = 3 !\2
3 - 3 = 0
El motor .NET regex no emula este comportamiento en su modo ECMAScript y, por lo tanto, la regex de 117 bytes funciona:
Pruébalo en línea! (versión de desaceleración exponencial, con motor .NET regex + emulación ECMAScript)
1
?