Esta respuesta es parte de una colaboración entre mí y 0 '. Los dos trabajamos juntos en esto, la única razón por la que lo estoy publicando es porque gané Piedra, Papel, Tijeras.
\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}.
Pruébalo en línea!
Explicación
Esta respuesta es un ejemplo perfecto de lo que hace que jugar al golf en prolog sea divertido.
Esta respuesta utiliza el poderoso sistema Prologs para las gramáticas de cláusulas definidas. Aquí está nuestra gramática ungolfed un poco.
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),!.
La primera regla de construcción es:
head(1)-->[].
Esto le dice a Prolog que la cadena vacía corresponde a 1.
Nuestra segunda regla de construcción es un poco más compleja.
head(Q)-->"(",head(N),")",head(B),{prime(N,Y),Q is Y*B}.
Esto nos dice que cualquier cadena no vacía contiene paréntesis alrededor de una cláusula con estas mismas reglas, a la derecha de una cláusula con estas mismas reglas.
También nos dice que el valor de esta cláusula ( Q
) sigue la regla:
{prime(N,Y),Q is Y*B}
Desglosando esto, Q
es el producto de 2 números Y
y B
. B
es solo el valor de la cláusula a la izquierda y Y
es el N
primer lugar donde N
está el valor de la cláusula dentro de los paréntesis.
Esta regla cubre las dos reglas de formación del árbol de factores.
- La concatenación se multiplica
- El recinto toma la enésima prima
Ahora para las definiciones de predicados. En la versión sin golf hay dos predicados en juego (en mi código real he encadenado los predicados). Los dos predicados relevantes aquí son isprime/1
, que coincide con un número primo y prime/2
, que, dado N
y Y
, coincide con iff Y
es el N
número primo. Primero tenemos
isprime(I):- \+ (between(3,I,U),0 =:= I mod(U-1)).
Esto funciona con una definición bastante estándar de primalidad, insistimos en que no hay un número entre 2 e I
, incluido 2, pero no I
eso divide I
.
El siguiente predicado también es bastante simple
prime(N,Y):-
findnsols(N,I,(
between(2,inf,I),
isprime(I)
),L),
append(_,[Y],L),!.
Usamos findnsols
para encontrar los primeros N
números que son primos, luego devolvemos el último. El truco aquí es que, si bien findnsols
no se garantiza que encuentre los primos más pequeños N
, debido a la forma en que SWI maneja between
, siempre encontrará primos más pequeños antes. Sin embargo, esto significa que tenemos que cortar para evitar que encuentre más números primos.
Los campos de golf
Podemos reenviar la razón en nuestro código dos veces. Dado isprime
que solo se usa una vez, su definición se puede mover dentro de prime
. El siguiente es movernos prime
directamente dentro del DCG, sin embargo, dado que usamos un corte prime
para evitar findnsols
producir demasiados números primos, tenemos un pequeño problema. El corte, corta todo el DCG en lugar de solo el bit que queremos. Después de investigar un poco la documentación, descubrimos que once/1
podría usarse para cortar solo esta parte, pero no todo el DCG. Sin embargo, una mayor excavación de documentación reveló que el ->
operador también podría usarse para realizar una tarea similar. El ->
operador es más o menos equivalente a ,!,
lo que movimos nuestro corte al otro lado append/3
y lo reemplazamos con ->
.
En SWI-Prolog, los predicados (y las reglas) se pueden dar a los operadores como nombres, lo que nos permite eliminar los paréntesis normalmente requeridos. De este modo podemos ahorrar 6 bytes llamando a la regla \
.