Retina , 66 63 45 43 36 byte
^()(\1(?<1>.\1))+(\1(.(?(4).\4)))*$
Nonostante il titolo che dice Retina, questa è solo una semplice regex .NET che accetta rappresentazioni unarie di numeri loeschiani.
Gli input 999 e 1000 richiedono ben meno di un secondo.
Provalo online!(La prima riga abilita una suite di test separata da avanzamento riga e le due successive si occupano della conversione in unario per comodità.)
Spiegazione
La soluzione si basa sulla classificazione secondo cui l'input può essere scritto come i*i + j*(i + j)
positivo i
e non negativo j
(poiché non dobbiamo gestire l'input 0
), e questa n*n
è solo la somma dei primi n
numeri dispari. Giocare a golf è stato un esercizio interessante nei riferimenti futuri.
Un "riferimento in avanti" è quando si inserisce un backreference all'interno del gruppo a cui fa riferimento. Ovviamente ciò non funziona quando il gruppo viene utilizzato per la prima volta, poiché non c'è ancora nulla di cui fare riferimento, ma se lo si inserisce in un ciclo, il backreference ottiene la cattura dell'iterazione precedente ogni volta. Questo a sua volta, creiamo una cattura più ampia con ogni iterazione. Questo può essere usato per creare modelli molto compatti per cose come numeri triangolari, quadrati e numeri di Fibonacci.
Ad esempio, usando il fatto che i quadrati sono solo somme dei primi n
numeri dispari, possiamo abbinare un input quadrato come questo:
(^.|..\1)+$
Nella prima iterazione, ..\1
non può funzionare, perché \1
non ha ancora un valore. Quindi iniziamo con ^.
, catturando un singolo personaggio in gruppo 1
. Nelle iterazioni successive, ^.
non corrisponde più a causa dell'ancoraggio, ma ora ..\1
è valido. Corrisponde a due caratteri in più rispetto alla precedente iterazione e aggiorna la cattura. In questo modo abbiniamo numeri dispari crescenti, ottenendo un quadrato dopo ogni iterazione.
Ora, sfortunatamente, non possiamo usare questa tecnica così com'è. Dopo l'abbinamento i*i
, dobbiamo anche ottenere i
, in modo da poterlo moltiplicare per j
. Un modo semplice (ma lungo) per farlo è quello di utilizzare il fatto che la corrispondenza i*i
richiede i
iterazioni, in modo che abbiamo catturato le i
cose in gruppo 1
. Ora possiamo usare i gruppi di bilanciamento per estrarre questoi
, ma come ho detto è costoso.
Invece, ho trovato un modo diverso di scrivere questa "somma di numeri dispari consecutivi" che i
alla fine cede anche in un gruppo di acquisizione. Ovviamente il i
numero dispari è giusto 2i-1
. Questo ci dà un modo per incrementare il riferimento diretto solo di 1 su ogni iterazione. Questa è questa parte:
^()(\1(?<1>.\1))+
Questo porta ()
semplicemente una cattura vuota sul gruppo 1
(inizializzazione i
a 0
). Questo è praticamente equivalente a quello ^.|
della soluzione semplice sopra, ma usando|
in this case would be a bit trickier.
Quindi abbiamo il ciclo principale (\1(?<1>.\1))
. \1
corrisponde al precedente i
, (?<1>.\1)
quindi aggiorna il gruppo 1
con i+1
. In termini di novità i
, abbiamo appena abbinato 2i-1
personaggi. Esattamente quello che ci serve.
When we're done, we've matched some square i*i
and group 1
still holds i
characters.
The second part is closer to the simple square matching I showed above. Let's ignore the backreference to 1
for now:
(.(?(4).\1))*
This is basically the same as (^.|..\4)*
, except that we can't make use of ^
because we're not at the start of the string. Instead we make use of a conditional, to match the additional .\1
only when we've already used group 4
. But in effect this is exactly the same. This gives us j*j
.
The only thing that's missing is the j*i
term. We combine this with the j*j
by making use of the fact that the j*j
computation still takes j
iterations. So for each iteration we also advance the cursor by i
with \1
. We just need to make sure not to write that into group 4
, because that would mess with matching consecutive odd numbers. That's how we arrive at the:
(\1(.(?(4).\1)))*