Esta resposta faz parte de uma colaboração entre mim e 0 '. Nós dois trabalhamos juntos nisso, a única razão pela qual estou postando é porque ganhei Pedra, Papel e Tesoura.
\Q-->{Q=1};"(",\N,")",\B,{findnsols(N,I,(between(2,inf,I),\+ (between(3,I,U),0=:=I mod(U-1))),L)->append(_,[Y],L),Q is Y*B}.
Experimente online!
Explicação
Esta resposta é um exemplo perfeito do que torna o golfe em prólogo divertido.
Esta resposta usa o poderoso sistema Prologs para gramáticas de cláusulas definidas. Aqui está a nossa gramática pouco usada.
head(1)-->[].
head(Q)-->"(",head(N),")",head(B),{prime(N,Y),Q is Y*B}.
isprime(I):- \+ (between(3,I,U),0 =:= I mod(U-1)).
prime(N,Y):-
findnsols(N,I,(
between(2,inf,I),
isprime(I)
),L),
append(_,[Y],L),!.
A primeira regra de construção é:
head(1)-->[].
Isso informa ao Prolog que a sequência vazia corresponde a 1.
Nossa segunda regra de construção é um pouco mais complexa.
head(Q)-->"(",head(N),")",head(B),{prime(N,Y),Q is Y*B}.
Isso nos diz que qualquer sequência não vazia contém parênteses em torno de uma cláusula com essas mesmas regras, à direita de uma cláusula com essas mesmas regras.
Também nos diz que o valor desta cláusula ( Q
) segue a regra:
{prime(N,Y),Q is Y*B}
Quebrando isso, Q
é o produto de 2 números Y
e B
. B
é apenas o valor da cláusula à esquerda e Y
é o N
primeiro primo, onde N
está o valor da cláusula entre parênteses.
Esta regra abrange as duas regras de formação da árvore de fatores
- A concatenação se multiplica
- Gabinete é o enésimo nono primo
Agora, para as definições de predicado. Na versão sem golfe, existem dois predicados em jogo (no meu código atual, encaminhei os predicados para a frente). Os dois predicados relevantes aqui são isprime/1
: o que corresponde a um número primo e o prime/2
qual, dado N
e Y
, corresponde a iff Y
é o N
th primo. Primeiro nós temos
isprime(I):- \+ (between(3,I,U),0 =:= I mod(U-1)).
Isso funciona com uma definição bastante padrão de primalidade, insistimos em que não há número entre 2 e I
, incluindo 2, mas não I
que divide I
.
O próximo predicado também é bastante simples
prime(N,Y):-
findnsols(N,I,(
between(2,inf,I),
isprime(I)
),L),
append(_,[Y],L),!.
Usamos findnsols
para encontrar os primeiros N
números primos e, em seguida, retornamos o último. O truque aqui é que, embora findnsols
não seja garantido encontrar os primos menores N
, por causa da maneira como o SWI lida com between
ele, ele sempre encontrará primos menores mais cedo. No entanto, isso significa que temos que cortar para impedir que encontre mais números primos.
Os golfe
Podemos encaminhar a razão em nosso código duas vezes. Uma vez que isprime
é usado apenas uma vez que sua definição pode ser movida para dentro de prime
. O próximo é mover-se prime
diretamente para dentro do DCG, no entanto, como usamos um corte prime
para impedir a findnsols
produção de números primos demais, temos um problema. O corte corta todo o DCG, em vez de apenas o pouco que queremos. Após um pouco de pesquisa na documentação, descobrimos que once/1
poderia ser usado para cortar apenas essa parte, mas não todo o DCG. No entanto, mais escavações na documentação revelaram que o ->
operador também poderia ser usado para executar uma tarefa semelhante. O ->
operador é aproximadamente equivalente a, ,!,
então movemos nosso corte para o outro lado append/3
e o substituímos por ->
.
No SWI-Prolog, os predicados (e regras) podem receber nomes de operadores como nomes, o que nos permite descartar os parênteses normalmente necessários. Assim, podemos economizar 6 bytes chamando a regra \
.