Regex ECMAScript, 733+ 690+ 158 119 118 (117🐌) bytes
Meu interesse na regex foi despertado com vigor renovado após mais de 4 anos e meio de inatividade. Como tal, procurei conjuntos de números e funções mais naturais para combinar com as expressões regulares ECMAScript, retomei a melhoria do meu mecanismo de expressão regular e comecei a aprimorar o PCRE também.
Estou fascinado pela estranheza de construir funções matemáticas no regex ECMAScript. Os problemas devem ser abordados de uma perspectiva totalmente diferente e, até a chegada de um insight importante, não se sabe se eles são solucionáveis. Obriga a lançar uma rede muito mais ampla na descoberta de quais propriedades matemáticas podem ser usadas para tornar um problema específico solucionável.
Combinar números fatoriais era um problema que eu nem sequer considerava enfrentar em 2014 - ou se eu fizesse, momentaneamente, descartá-lo como improvável demais para ser possível. Mas, no mês passado, percebi que isso poderia ser feito.
Como nas minhas outras postagens de regex do ECMA, darei um aviso: eu recomendo aprender como resolver problemas matemáticos unários no regex do ECMAScript. Foi uma jornada fascinante para mim, e não quero estragá-la para quem potencialmente queira experimentá-la, especialmente aqueles com interesse em teoria dos números. Consulte esta postagem anterior para obter uma lista de problemas recomendados consecutivamente identificados por spoilers para resolver um por um.
Portanto , não leia mais se não quiser que você estrague uma mágica avançada de expressões regulares unárias . Se você quiser tentar descobrir essa mágica, recomendo começar resolvendo alguns problemas no regex ECMAScript, conforme descrito no post acima.
Essa foi a minha ideia:
O problema de corresponder esse conjunto de números, assim como a maioria dos outros, é que, na ECMA, geralmente não é possível acompanhar dois números alterados em um loop. Às vezes, eles podem ser multiplexados (por exemplo, poderes da mesma base podem ser adicionados sem ambiguidade), mas isso depende de suas propriedades. Portanto, eu não poderia simplesmente começar com o número de entrada e dividi-lo por um dividendo cada vez maior até chegar a 1 (ou pelo menos eu pensava).
Depois, pesquisei sobre as multiplicidades de fatores primos em números fatoriais e aprendi que existe uma fórmula para isso - e é uma que eu provavelmente poderia implementar em um regex do ECMA!
Depois de estufá-lo por um tempo, e construir alguns outros regexes nesse meio tempo, assumi a tarefa de escrever o regex fatorial. Demorou várias horas, mas acabou funcionando bem. Como um bônus adicional, o algoritmo pode retornar o fatorial inverso como uma correspondência. Não havia como evitá-lo; pela própria natureza de como deve ser implementado no ECMA, é necessário adivinhar qual é o fatorial inverso antes de fazer qualquer outra coisa.
A desvantagem foi que esse algoritmo resultou em um regex muito longo ... mas fiquei satisfeito porque acabou exigindo uma técnica usada no meu regex de multiplicação de 651 bytes (aquele que acabou sendo obsoleto, porque um método diferente foi usado para um valor 50) byte regex). Eu esperava que surgisse um problema que exigisse esse truque: operar em dois números, que são os dois poderes da mesma base, em um loop, adicionando-os sem ambiguidade e separando-os a cada iteração.
Mas, devido à dificuldade e extensão desse algoritmo, usei lookaheads moleculares (da forma (?*...)
) para implementá-lo. Esse é um recurso que não está no ECMAScript ou em qualquer outro mecanismo regular de regex, mas que eu havia implementado no meu mecanismo . Sem capturas dentro de um lookahead molecular, é funcionalmente equivalente a um lookahead atômico, mas com capturas pode ser muito poderoso. O mecanismo retornará ao lookahead, e isso pode ser usado para conjeturar um valor que alterna entre todas as possibilidades (para testes posteriores) sem consumir caracteres da entrada. Usá-los pode resultar em uma implementação muito mais limpa. (O comprimento variável da aparência é no mínimo igual em potência à aparência molecular, mas o último tende a criar implementações mais diretas e elegantes.)
Portanto, os comprimentos de 733 e 690 bytes não representam realmente encarnações da solução compatíveis com ECMAScript - daí o "+" após elas; certamente é possível portar esse algoritmo para ECMAScript puro (o que aumentaria bastante seu comprimento), mas eu não cheguei a ele ... porque pensei em um algoritmo muito mais simples e compacto! Um que poderia ser facilmente implementado sem olhares moleculares. Também é significativamente mais rápido.
Este novo, como o anterior, deve adivinhar o fatorial inverso, percorrendo todas as possibilidades e testando-as para uma partida. Ele divide N por 2 para liberar espaço para o trabalho que precisa fazer e, em seguida, gera um loop no qual dividirá repetidamente a entrada por um divisor que começa em 3 e aumenta a cada vez. (Como tal, 1! E 2! Não podem ser correspondidos pelo algoritmo principal e devem ser tratados separadamente.) O divisor é rastreado adicionando-o ao quociente em execução; esses dois números podem ser inequivocamente separados porque, assumindo M! == N, o quociente em execução continuará a ser divisível por M até que seja igual a M.
Esse regex faz divisão por variável na parte mais interna do loop. O algoritmo de divisão é o mesmo que em meus outros regexes (e semelhante ao algoritmo de multiplicação): para A≤B, A * B = C se houver apenas se C% A = 0 e B é o maior número que satisfaz B≤C e C% B = 0 e (CB- (A-1))% (B-1) = 0, onde C é o dividendo, A é o divisor e B é o quociente. (Um algoritmo semelhante pode ser usado para o caso A≥B e, se não se souber como A se compara a B, basta um teste extra de divisibilidade.)
Portanto, adoro que o problema possa ser reduzido a uma complexidade ainda menor do que o meu regex de Fibonacci otimizado para golfe , mas suspiro com desapontamento que minha técnica de multiplexação de poderes da mesma base tenha que esperar por outro problema isso realmente exige, porque este não. É a história do meu algoritmo de multiplicação de 651 bytes sendo suplantado por um de 50 bytes, mais uma vez!
Edit: Consegui soltar 1 byte (119 → 118) usando um truque encontrado por Grimy que pode reduzir ainda mais a divisão no caso de garantir que o quociente seja maior ou igual ao divisor.
Sem mais delongas, aqui está o regex:
Versão verdadeira / 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?$
Experimente online!
Devolva fatorial inverso ou sem correspondência (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?$
Experimente online!
Retorne fatorial inverso ou sem correspondência, em 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?$
E a versão em espaço livre com comentários:
^
(?= # 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
O histórico completo das minhas otimizações de golfe dessas regexes está no github:
regex para correspondência de números fatoriais - método de comparação de multiplicidade, com lookahead.txt molecular
regex para correspondência de números fatoriais.txt (o mostrado acima)
Observe que ((x*)x*)
pode ser alterado para ((x*)+)
, diminuindo o tamanho em 1 byte (para 117 bytes ) sem perda da funcionalidade correta - mas o regex explode exponencialmente em lentidão. No entanto, esse truque, embora funcione no PCRE e no .NET, não funciona no ECMAScript , devido ao seu comportamento ao encontrar uma correspondência de comprimento zero em um loop . ((x+)+)
funcionaria no ECMAScript, mas isso quebraria o regex, porque para n = 3 !, \2
precisa capturar um valor de 3 - 3 = 0 (e alterar o regex para ser indexado em 1 diminuiria o benefício do golfe).
O mecanismo regex .NET não emula esse comportamento no modo ECMAScript e, portanto, o regex de 117 bytes funciona:
Experimente online! (versão exponencial-lenta, com mecanismo regex .NET + emulação ECMAScript)
1
?