Regex (sabor ECMAScript), 392 358 328 224 206 165 bytes
Las técnicas que deben entrar en juego para unir los números de Fibonacci con una expresión regular ECMAScript (en unario) están muy lejos de cómo se hace mejor en la mayoría de los otros sabores de expresiones regulares. La falta de referencias hacia atrás / anidadas o recursividad significa que es imposible contar directamente o mantener un total acumulado de cualquier cosa. La falta de mirar atrás hace que a menudo sea un desafío incluso tener suficiente espacio para trabajar.
Muchos problemas deben abordarse desde una perspectiva completamente diferente, y parecen irresolubles hasta la llegada de alguna idea clave. Te obliga a echar una red mucho más amplia para encontrar qué propiedades matemáticas de los números con los que estás trabajando podrían usarse para resolver un problema en particular.
En marzo de 2014, esto es lo que sucedió con los números de Fibonacci. Mirando la página de Wikipedia, inicialmente no pude encontrar una manera, aunque una propiedad en particular parecía tentadoramente cercana. Luego, el matemático teukon describió un método que dejó bastante claro que sería posible hacerlo, utilizando esa propiedad junto con otra. Era reacio a construir realmente la expresión regular. Su reacción cuando seguí adelante y lo hice:
¡Estás loco! ... pensé que podrías hacer esto.
Al igual que con mis otras publicaciones de expresiones regulares de matemáticas unitarias de ECMAScript, 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 que estén interesados en la teoría de números. Vea esa publicación para obtener una lista de problemas recomendados consecutivamente etiquetados con spoiler para resolver uno por uno.
Así que no sigas leyendo si no quieres que se te estropee la magia de expresiones regulares unarias . 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.
El desafío que enfrenté inicialmente: un número entero positivo x es un número de Fibonacci si y solo si 5x 2 + 4 y / o 5x 2 - 4 es un cuadrado perfecto. Pero no hay espacio para calcular esto en una expresión regular. El único espacio en el que tenemos que trabajar es el número mismo. Ni siquiera tenemos suficiente espacio para multiplicar por 5 o tomar el cuadrado, y mucho menos ambos.
La idea de teukon sobre cómo resolverlo ( originalmente publicado aquí ):
La expresión regular se presenta con una cadena de la forma ^x*$
, sea z su longitud. Compruebe si z es uno de los primeros números de Fibonacci a mano (hasta 21 deberían hacerlo). Si no es:
- Lea un par de números, a <b, de modo que b no sea mayor que 2a.
- Use las miradas hacia adelante para construir a 2 , ab y b 2 .
- Afirma que 5a 2 + 4 o 5a 2 - 4 es un cuadrado perfecto (por lo que debe ser F n-1 para algunos n).
- Afirma que 5b 2 + 4 o 5b 2 + 4 es un cuadrado perfecto (entonces b debe ser F n ).
- Verifique que z = F 2n + 3 o z = F 2n + 4 utilizando a 2 , ab y b 2 construidos anteriormente , y las identidades:
- F 2n-1 = F n 2 + F n-1 2
- F 2n = (2F n-1 + F n ) F n
En resumen: estas identidades nos permiten reducir el problema de verificar que un número dado es Fibonacci a verificar que un par de números mucho más pequeños son Fibonacci. Un poco de álgebra mostrará que para n lo suficientemente grande (n = 3 debería hacer), F 2n + 3 > F n + 5F n 2 + 4, por lo que siempre debe haber suficiente espacio.
Y aquí hay una maqueta del algoritmo en C que escribí como prueba antes de implementarlo en regex.
Entonces, sin más preámbulos, aquí está la expresión regular:
^((?=(x*).*(?=x{4}(x{5}(\2{5}))(?=\3*$)\4+$)(|x{4})(?=xx(x*)(\6x?))\5(x(x*))(?=(\8*)\9+$)(?=\8*$\10)\8*(?=(x\2\9+$))(x*)\12)\7\11(\6\11|\12)|x{0,3}|x{5}|x{8}|x{21})$
Pruébalo en línea!
Y la versión comentada y muy impresa:
^(
(?=
(x*) # \2+1 = potential number for which 5*(\2+1)^2 ± 4
# is a perfect square; this is true iff \2+1 is a Fibonacci
# number. Outside the surrounding lookahead block, \2+1 is
# guaranteed to be the largest number for which this is true
# such that \2 + 5*(\2+1)^2 + 4 fits into the main number.
.*
(?= # tail = (\2+1) * (\2+1) * 5 + 4
x{4}
( # \3 = (\2+1) * 5
x{5}
(\2{5}) # \4 = \2 * 5
)
(?=\3*$)
\4+$
)
(|x{4}) # \5 = parity - determined by whether the index of Fibonacci
# number \2+1 is odd or even
(?=xx (x*)(\6 x?)) # \6 = arithmetic mean of (\2+1) * (\2+1) * 5 and \8 * \8,
# divided by 2
# \7 = the other half, including remainder
\5
# require that the current tail is a perfect square
(x(x*)) # \8 = potential square root, which will be the square root
# outside the surrounding lookahead; \9 = \8-1
(?=(\8*)\9+$) # \10 = must be zero for \8 to be a valid square root
(?=\8*$\10)
\8*
(?=(x\2\9+$)) # \11 = result of multiplying \8 * (\2+1), where \8 is larger
(x*)\12 # \12 = \11 / 2; the remainder will always be the same as it
# is in \7, because \8 is odd iff \2+1 is odd
)
\7\11
(
\6\11
|
\12
)
|
x{0,3}|x{5}|x{8}|x{21} # The Fibonacci numbers 0, 1, 2, 3, 5, 8, 21 cannot be handled
# by our main algorithm, so match them here; note, as it so
# happens the main algorithm does match 13, so that doesn't
# need to be handled here.
)$
El algoritmo de multiplicación no se explica en esos comentarios, pero se explica brevemente en un párrafo de mis abundantes números de expresiones regulares .
Estaba manteniendo seis versiones diferentes de la expresión regular de Fibonacci: cuatro que se mueven de la longitud más corta a la velocidad más rápida y usan el algoritmo explicado anteriormente, y otras dos que usan un algoritmo diferente, mucho más rápido pero mucho más largo, que como encontré en realidad puede volver el índice de Fibonacci como una coincidencia (explicar que el algoritmo aquí está más allá del alcance de esta publicación, pero se explica en la discusión original Gist ). No creo que vuelva a mantener tantas versiones muy similares de una expresión regular, porque en ese momento estaba haciendo todas mis pruebas en PCRE y Perl, pero mi motor de expresión regular es lo suficientemente rápido como para que las preocupaciones sobre la velocidad ya no sean tan importantes (y si una construcción en particular está causando un cuello de botella, puedo agregar una optimización), aunque probablemente volvería a mantener una versión más rápida y una versión más corta, si la diferencia en velocidad eran lo suficientemente grandes.
La versión "devolver el índice de Fibonacci menos 1 como un partido" (no muy golfizado):
Pruébalo en línea!
Todas las versiones están en github con el historial completo de las optimizaciones de golf:
regex para hacer coincidir los números de Fibonacci - corto, velocidad 0.txt (el más corto pero más lento, como en esta publicación)
regex para hacer coincidir los números de Fibonacci - corto, velocidad 1.txt
regex para hacer coincidir los números de Fibonacci - corto, velocidad 2.txt
regex para números de Fibonacci coincidentes - corto, velocidad 3.txt
regex para hacer coincidir los números de Fibonacci - más rápido.txt
regex para hacer coincidir los números de Fibonacci - return index.txt