ECMAScript Regex, 733+ 690Plus 158 119 118 (117🐌) byte
Il mio interesse per regex è stato suscitato con rinnovato vigore dopo oltre 4 anni e mezzo di inattività. Come tale, sono andato alla ricerca di set di numeri e funzioni più naturali da abbinare alle regex ECMAScript unari, ho ripreso a migliorare il mio motore regex e ho iniziato a ripassare anche su PCRE.
Sono affascinato dall'alienità di costruire funzioni matematiche nella regex di ECMAScript. I problemi devono essere affrontati da una prospettiva completamente diversa e fino all'arrivo di una visione chiave, non si sa se siano risolvibili affatto. Costringe a lanciare una rete molto più ampia per scoprire quali proprietà matematiche potrebbero essere utilizzate per rendere risolvibile un particolare problema.
La corrispondenza dei numeri fattoriali era un problema che non avevo nemmeno considerato di affrontare nel 2014 - o se lo fossi, solo momentaneamente, respingerlo perché troppo improbabile. Ma il mese scorso mi sono reso conto che poteva essere fatto.
Come con gli altri miei post regex ECMA, darò un avvertimento: consiglio vivamente di imparare a risolvere i problemi matematici unari in regex ECMAScript. È stato un viaggio affascinante per me e non voglio rovinarlo per nessuno che potrebbe potenzialmente provarlo da solo, in particolare quelli con un interesse per la teoria dei numeri. Vedi questo post precedente per un elenco di problemi raccomandati consecutivamente con tag spoiler da risolvere uno per uno.
Quindi non leggere oltre se non vuoi che qualche magia regex unaria avanzata venga viziata per te . Se vuoi fare un tentativo per capire da solo questa magia, ti consiglio vivamente di iniziare risolvendo alcuni problemi nella regex di ECMAScript come indicato in quel post collegato sopra.
Questa era la mia idea:
Il problema con la corrispondenza di questo set di numeri, come con la maggior parte degli altri, è che in ECMA di solito non è possibile tenere traccia di due numeri che cambiano in un ciclo. A volte possono essere multiplexati (ad esempio, i poteri della stessa base possono essere sommati in modo univoco), ma dipende dalle loro proprietà. Quindi non potevo semplicemente iniziare con il numero di input e dividerlo per un dividendo che aumenta gradualmente fino a raggiungere 1 (o almeno così pensavo).
Quindi ho fatto alcune ricerche sulle molteplicità dei fattori primi nei numeri fattoriali e ho appreso che esiste una formula per questo - ed è probabilmente quella che potrei implementare in una regex ECMA!
Dopo averlo calpestato per un po 'e nel frattempo ho costruito altre regex, mi sono incaricato di scrivere la regex fattoriale. Ci sono volute alcune ore, ma alla fine ha funzionato bene. Come bonus aggiuntivo, l'algoritmo potrebbe restituire fattoriale inverso come una corrispondenza. Non c'era modo di evitarlo, anche; per la natura stessa di come deve essere implementato nell'ECMA, è necessario indovinare quale sia il fattoriale inverso prima di fare qualsiasi altra cosa.
Il rovescio della medaglia è stato che questo algoritmo ha prodotto una regex molto lunga ... ma mi ha fatto piacere che ha finito per richiedere una tecnica utilizzata nella mia regex di moltiplicazione a 651 byte (quella che è risultata obsoleta, perché un metodo diverso ha portato a un 50 byte regex). Speravo che sorgesse un problema che richiedesse questo trucco: operare su due numeri, che sono entrambi poteri della stessa base, in un ciclo, sommandoli in modo univoco e separandoli ad ogni iterazione.
Ma a causa della difficoltà e della lunghezza di questo algoritmo, ho usato lookahead molecolari (della forma (?*...)
) per implementarlo. Questa è una caratteristica non presente in ECMAScript o in qualsiasi altro motore regex tradizionale, ma che avevo implementato nel mio motore . Senza catture all'interno di uno sguardo molecolare, è funzionalmente equivalente a uno sguardo atomico, ma con catture può essere molto potente. Il motore tornerà indietro nel lookahead, e questo può essere usato per congetturare un valore che scorre attraverso tutte le possibilità (per i test successivi) senza consumare caratteri dell'input. Il loro utilizzo può rendere l'implementazione molto più pulita. (Lookbehind di lunghezza variabile è quantomeno uguale in potenza allo sguardo molecolare, ma quest'ultimo tende a rendere implementazioni più semplici ed eleganti.)
Quindi le lunghezze di 733 e 690 byte in realtà non rappresentano incarnazioni della soluzione compatibili con ECMAScript - da qui il "+" dopo di esse; è sicuramente possibile trasferire tale algoritmo su puro ECMAScript (che aumenterebbe un po 'la sua lunghezza) ma non ci sono riuscito ... perché ho pensato a un algoritmo molto più semplice e compatto! Uno che potrebbe essere facilmente implementato senza lookahead molecolari. È anche significativamente più veloce.
Questo nuovo, come il precedente, deve fare un'ipotesi sul fattoriale inverso, scorrere tutte le possibilità e testarle per una partita. Divide N per 2 per fare spazio per il lavoro che deve fare, quindi semina un ciclo in cui dividerà ripetutamente l'input per un divisore che inizia da 3 e aumenta ogni volta. (In quanto tale, 1! E 2! Non possono essere associati all'algoritmo principale e devono essere trattati separatamente.) Il divisore viene tenuto sotto controllo aggiungendolo al quoziente corrente; questi due numeri possono essere separati in modo univoco perché, supponendo M! == N, il quoziente corrente continuerà a essere divisibile per M fino a quando non sarà uguale a M.
Questa regex fa divisione per variabile nella porzione più interna del ciclo. L'algoritmo di divisione è lo stesso degli altri miei regex (e simile all'algoritmo di moltiplicazione): per A≤B, A * B = C se presente solo se C% A = 0 e B è il numero più grande che soddisfa B≤C e C% B = 0 e (CB- (A-1))% (B-1) = 0, dove C è il dividendo, A è il divisore e B è il quoziente. (Un algoritmo simile può essere usato nel caso in cui A≥B, e se non si sa come A paragona a B, è sufficiente un ulteriore test di divisibilità.)
Quindi adoro il fatto che il problema sia stato ridotto a una complessità ancora inferiore rispetto alla mia regex di Fibonacci ottimizzata per il golf , ma sospiro di delusione per il fatto che la mia tecnica multiplazione dei poteri della stessa base dovrà attendere un altro problema che in realtà lo richiede, perché questo non lo fa. È la storia del mio algoritmo di moltiplicazione a 651 byte che è stato soppiantato da uno da 50 byte, ancora una volta!
Modifica: sono stato in grado di rilasciare 1 byte (119 → 118) usando un trucco trovato da Grimy che può ridurre ulteriormente la divisione nel caso in cui il quoziente sia garantito maggiore o uguale al divisore.
Senza ulteriori indugi, ecco la regex:
Versione true / false (118 byte):
^((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?$
Provalo online!
Restituisce fattoriale inverso o nessuna corrispondenza (124 byte):
^(?=((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?$
Provalo online!
Restituisce fattoriale inverso o nessuna corrispondenza, in ECMAScript +\K
(120 byte):
^((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?$
E la versione a spaziatura libera con commenti:
^
(?= # 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 storia completa delle mie ottimizzazioni golfistiche di queste regex è su github:
regex per l'abbinamento dei numeri fattoriali - metodo di confronto di molteplicità, con lookahead.txt molecolare
regex per l'abbinamento dei numeri fattoriali.txt (quello mostrato sopra)
Si noti che ((x*)x*)
può essere modificato in ((x*)+)
, diminuendo la dimensione di 1 byte (a 117 byte ) senza perdita della funzionalità corretta, ma la regex esplode esponenzialmente con lentezza. Tuttavia, questo trucco, mentre funziona in PCRE e .NET, non funziona in ECMAScript , a causa del suo comportamento quando incontra una corrispondenza di lunghezza zero in un ciclo . ((x+)+)
funzionerebbe in ECMAScript, ma questo spezzerebbe la regex, perché per n=3!, \2
deve acquisire un valore di 3−3=0 (e la modifica del regex per essere 1-index annullerebbe il vantaggio del golf di questo).
Il motore regex .NET non emula questo comportamento nella sua modalità ECMAScript, quindi il regex a 117 byte funziona:
Provalo online! (versione esponenziale-rallentamento, con motore regex .NET + emulazione ECMAScript)
1
?