Retina , 66 63 45 43 36 bytes
^()(\1(?<1>.\1))+(\1(.(?(4).\4)))*$
A pesar del título que dice Retina, esta es solo una expresión regular .NET que acepta representaciones unarias de números de Loeschian.
Las entradas 999 y 1000 toman bastante menos de un segundo.
Pruébalo en línea! (La primera línea permite un conjunto de pruebas separadas por salto de línea, y las dos siguientes se encargan de la conversión a unario por conveniencia).
Explicación
La solución se basa en la clasificación de que la entrada se puede escribir como i*i + j*(i + j)
positiva i
y no negativa j
(ya que no tenemos que manejar la entrada 0
), y eso n*n
es solo la suma de los primeros n
enteros impares. Jugar al golf fue un ejercicio interesante en referencias avanzadas.
Una "referencia hacia adelante" es cuando coloca una referencia hacia atrás dentro del grupo al que hace referencia. Por supuesto, eso no funciona cuando el grupo se usa por primera vez, ya que no hay nada a lo que hacer referencia todavía, pero si lo coloca en un bucle, la referencia inversa obtiene la captura de la iteración anterior cada vez. Esto a su vez, le permite construir una captura más grande con cada iteración. Esto se puede usar para crear patrones muy compactos para cosas como números triangulares, cuadrados y números de Fibonacci.
Como ejemplo, usando el hecho de que los cuadrados son solo sumas de los primeros n
enteros impares, podemos hacer coincidir una entrada cuadrada como esta:
(^.|..\1)+$
En la primera iteración, ..\1
no puede funcionar, porque \1
todavía no tiene un valor. Entonces comenzamos con la ^.
captura de un solo personaje en grupo 1
. En las iteraciones posteriores, ^.
ya no coincide debido al ancla, pero ahora ..\1
es válido. Coincide con dos caracteres más que la iteración anterior y actualiza la captura. De esta manera emparejamos números impares crecientes, obteniendo un cuadrado después de cada iteración.
Ahora, desafortunadamente, no podemos usar esta técnica tal como está. Después de hacer coincidir i*i
, necesitamos obtener i
también, para poder multiplicarlo por j
. Una manera simple (pero larga) de hacer esto es hacer uso del hecho de que la coincidencia i*i
requiere i
iteraciones, de modo que hemos capturado las i
cosas en grupo 1
. Ahora podríamos usar grupos de equilibrio para extraer esto i
, pero como dije, eso es costoso.
En cambio, descubrí una forma diferente de escribir esta "suma de enteros impares consecutivos" que también produce i
un grupo de captura al final. Por supuesto, el i
tercer número es justo 2i-1
. Esto nos da una manera de incrementar la referencia directa solo en 1 en cada iteración. Esa es esta parte:
^()(\1(?<1>.\1))+
Esto ()
simplemente empuja una captura vacía al grupo 1
(inicializando i
a 0
). Esto es bastante equivalente a la ^.|
solución simple anterior, pero usar |
en este caso sería un poco más complicado.
Luego tenemos el bucle principal (\1(?<1>.\1))
. \1
coincide con el anterior i
, (?<1>.\1)
luego actualiza el grupo 1
con i+1
. En términos de lo nuevo i
, acabamos de hacer coincidir los 2i-1
personajes. Exactamente lo que necesitamos.
Cuando terminamos, hemos combinado algunos cuadrados i*i
y el grupo 1
todavía tiene i
personajes.
La segunda parte está más cerca de la simple coincidencia cuadrada que mostré arriba. Ignoremos la referencia a 1
por ahora:
(.(?(4).\1))*
Esto es básicamente lo mismo que (^.|..\4)*
, excepto que no podemos usarlo ^
porque no estamos al comienzo de la cadena. En su lugar, utilizamos un condicional, para que coincida con el adicional .\1
solo cuando ya hemos usado group 4
. Pero en efecto esto es exactamente lo mismo. Esto nos da j*j
.
Lo único que falta es el j*i
término. Combinamos esto con el j*j
hecho de que el j*j
cómputo todavía toma j
iteraciones. Entonces, para cada iteración también avanzamos el cursor i
con \1
. Solo tenemos que asegurarnos de no escribir eso en el grupo 4
, porque eso podría interferir con números impares consecutivos. Así es como llegamos a:
(\1(.(?(4).\1)))*