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 Ye B. Bé apenas o valor da cláusula à esquerda e Yé o Nprimeiro primo, onde Nestá 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/2qual, dado Ne Y, corresponde a iff Yé o Nth 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 Ique 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 findnsolspara encontrar os primeiros Nnúmeros primos e, em seguida, retornamos o último. O truque aqui é que, embora findnsolsnão seja garantido encontrar os primos menores N , por causa da maneira como o SWI lida com betweenele, 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 primediretamente para dentro do DCG, no entanto, como usamos um corte primepara impedir a findnsolsproduçã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/1poderia 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/3e 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 \.