¿Qué consejos generales tienes para jugar al golf en Prolog? Estoy buscando ideas que se puedan aplicar a los problemas de golf de código en general que sean al menos algo específicos de Prolog (por ejemplo, las variables de una letra no son específicas de Prolog para reducir el tamaño de los programas).
Indique en sus consejos si es específico de una implementación de Prolog (p. Ej., Integraciones específicas de SWI-Prolog)
Publique solo un consejo por respuesta, o una lista de consejos que estén estrechamente relacionados con la misma idea principal.
prolog
etiqueta es un poco inútil. A menos que tengamos un desafío Interpretar Prólogo, no lo necesitamos.Respuestas:
Usar operadores para nombres predicados
Es posible dar operadores de predicados como nombres siempre que el operador sea uno de los operadores predefinidos (enumerados aquí ) y no esté definido como un predicado. Esto ahorra unos pocos bytes al definir y llamar al predicado, ya que los predicados del operador no necesitan escribirse en el normal
name(arg1,arg2,etc..)
forma y pueden llamarse como cabría esperar con los operadores.Para predicados de uno y dos argumentos, se les puede dar los nombres de operadores unarios y binarios, respectivamente. Para predicados de aridad más altos, aún podemos evitar paréntesis usando la coincidencia de patrones. Por ejemplo, si tenemos un predicado
A+B+C:-...
, Prolog usará sus reglas de precedencia de operador y asociatividad para transformarlo en(A+B)+C:-...
un predicado de operador en el que coincida el primer argumentoA+B
. O loA-B+C*D:-...
que se hace(A-B)+(C*D)
así, su primer argumento coincide con el patrónA-B
y su segundo coincide con el patrónC*D
.Ejemplos
La salida será
X = 5.
Pruébalo en línea!
Corolario
Dado que los DCG son azúcar sintáctica para predicados, también se les puede dar operadores para nombres. Esto funciona como se espera al llamarlos como DCG, ya sea desde un DCG o utilizando los
phrase
predicados u otros diseñados para trabajar con DCG. Al llamarlos como predicados, se requieren paréntesis (por ejemplo,A+B-->...
deben llamarse como+(A,B,...)
), ya que los predicados DCG toman dos argumentos adicionales para sus listas de diferencias. Para los operadores denominados DCG con más de dos argumentos que utilizan la coincidencia de patrones de operador, es importante asegurarse, al llamarlo como predicado, de que los operadores coincidentes de patrón se distribuyen correctamente.Dar nombres de operadores a DCG que no toman argumentos adicionales puede ser útil si necesita llamarlos dentro de su programa, ya que puede hacerlo sin usar paréntesis. Se requiere precaución porque puede darse el caso de que lo que guarde entre paréntesis se pueda perder con el espacio adicional requerido para analizar los operadores adyacentes.
Ejemplos
La salida será
Pruébalo en línea!
Advertencias
Con los operadores unitarios
+
y-
, Prolog interpretará+20
o-20
como números en lugar de una llamada a una+/1
o-/1
predicado. Los predicados dados como unarios+
o-
como nombres todavía se pueden invocar con números usando paréntesis (+(20)
,-(20)
). Si evitar los bytes adicionales de paréntesis, es deseable otros operadores unitarios tales como\
,$
, etc se puede utilizar como nombres de lugar.La combinación de coincidencia de patrones y predicados con nombre de operador no carece por completo de defectos. Si tiene dos predicados que tienen el mismo operador que su nombre y con coincidencia de patrones, uno es estrictamente más general que el otro, entonces se puede llamar primero al más general o si falla el menos general (dependiendo de su orden en la fuente) . Por ejemplo, en el ejemplo anterior, si
A-B+C*D
no coincide con su entrada, Prolog intentará llamarX+Y
. Esto dará lugar a un error debido alength/2
requerirY
ser un número entero que no será ya que será en la formaC*D
. Esto se puede evitar simplemente asegurándose de que no haya dos predicados que tengan el mismo operador que su nombre o, si eso falla, utilizando cortes y ordenando cuidadosamente la fuente.fuente
Intenta poner todos los casos posibles en una sola regla
La manera limpia de programar en Prolog es declarar múltiples reglas para el mismo predicado. Por ejemplo, un predicado para revertir una lista con un acumulador se vería así:
En Code-golf, podemos eliminar la primera regla y agregar un
;
al final de la segunda regla para codificar el final de la recursividad:Sabemos que la primera condición
r(T,[H|Z],R)
fallará si T está vacía, es decir, si la recursión necesita terminar y, por lo tanto, podemos agregar nuestra terminación como una o cláusula después.El mismo principio funciona en muchas situaciones. Sin embargo, tenga en cuenta que a veces es más corto declarar otra regla en lugar de hacerlo.
fuente
Un truco que suele ser útil: usar restricciones CLP (FD) para la aritmética de enteros para obtener predicados que se puedan usar automáticamente en varias direcciones, evitando así condiciones y ramas y variantes dedicadas.
Utilice B-Prolog o GNU Prolog, donde tales restricciones están disponibles de forma inmediata, sin necesidad de cargar ninguna biblioteca.
fuente
library(clpfd)
que estará disponible como una biblioteca precargada o al menos autocargada también en SWI-Prolog. Pueden pasar algunos años hasta que la aritmética declarativa sea completamente comprendida y apreciada por todos los usuarios, que ya han acumulado décadas de experiencia con funciones obsoletas de bajo nivel. Cuanto más use y defienda CLP (FD), antes lo obtendremos de manera predeterminada. Hasta entonces, simplemente puede poner:- use_module(library(clpfd)).
su~/.swiplrc
y simplemente indicar que está utilizando esa "variante" de SWI-Prolog.Utilice operadores aritméticos como constructores de tuplas y pares de contras.
Si necesita pasar una sola estructura que consta de dos o más valores, lo más obvio es usar una lista, por ejemplo
[A,B]
. Sin embargo, eso es realmente detallado.Hay una alternativa Los valores de Prolog pueden almacenar una estructura anidada bastante arbitraria, que no se evalúa. Aquí hay un ejemplo que muestra cómo funciona:
member(A,B)
es solo una tupla con nombre en esta situación, y el exteriormember
(que es una llamada de función) lo trata como tal.Aunque las tuplas con nombre son bastante útiles en la programación de Prolog sin golf, pueden parecer aún más detalladas que el enfoque de lista. Sin embargo, podemos usar caracteres bastante arbitrarios en el nombre del constructor de tuplas (suponiendo que se citan correctamente); en lugar de algo lindo
member
o un solo personajea
, podemos hacer algo como esto:Aquí, nuestros constructores de tuplas son
'-'
y'/'
. Y es interesante notar lo que hizo la bonita impresora con ellos; usa notación infija para las tuplas. Esto es realmente breve y analiza de la misma manera que lo haría la operación aritmética comparable. (Esto también explica por qué los usos aritméticosis
no=
;A = 1+2
unificaríaA
con la tupla'+'(1,2)
, por lo que se necesita una sintaxis separada para evaluar realmente la expresión aritmética no evaluada). Debido a que un constructor de tuplas debe llamarse algo , también puede usar un carácter que tenga un término breve sintaxis (y como bonificación,-
y/
son algunas de las opciones más comunes en el código no golfizado también cuando quieren un constructor de tuplas desechable rápido en lugar de algo significativo, de la misma manera que ai
menudo se usa como una variable de bucle, por lo que son completamente razonables para usar en su entrada y salida si por casualidad quieres una tupla allí).'-'
y'/'
son buenas opciones para los constructores de tuplas porque tienen una buena conducta y una precedencia útil, lo que le permite escribir tuples literales de manera clara. Sin embargo, tenga en cuenta que no necesita preocuparse por la precedencia cuando se producen valores intermedios dentro del programa. Prolog mantiene las tuplas almacenadas como un árbol en lugar de como código fuente, y las impresoras bonitas pueden enviarlo sin ambigüedades:Debido a que la sintaxis de tupla es muy breve (
f(A,B)
no es más corta quef(A-B)
), puede reemplazar múltiples argumentos de predicción con tuplas sin costo, lo que significa que si un predicado necesita pasar dos o más de sus argumentos a otro predicado, a menudo puede formarlos en una tupla y simplemente pasar la tupla (aunque esto requerirá cambiar todas las llamadas al predicado, además del predicado mismo, para usar una combinación adecuada de constructores de tuplas y comas).Otra ventaja de esta sintaxis es si necesita usar listas internamente (en lugar de interoperar con predicados estándar); una lista es básicamente solo un conjunto de celdas anidadas, y una celda cons es solo una tupla con constructor
'.'
, como se puede ver aquí:Si su código usa listas "manualmente", puede tener mucho sentido usar un constructor de tuplas menos voluminoso que
'.'
. Una opción común para mí es representar una celda de contras como'/'(Tail,Head)
(porque se trata de lo más legible que puede obtener en la salida de depuración sin desperdiciar caracteres). Tenga en cuenta que probablemente también querrá su propio[]
equivalente; usted podría utilizar[]
, pero es dos bytes de longitud, y hay un montón de átomos de un byte (todas las letras minúsculas) que se pueden utilizar en su lugar.Entonces, por ejemplo, la siguiente lista:
podría convertirse en una representación manual en el mismo número de caracteres como este:
mientras obtiene la ventaja de que
[H|T]
las coincidencias de patrones de estilo ahora se pueden escribir de manera más concisa comoT/H
, y una prueba contra la lista vacía, enx
lugar de hacerlo por más tiempo[]
. (Por supuesto, esto viene con el inconveniente obvio quemember
,append
, etc, no funcionará en esta representación.)fuente
-
y/
. Estos son átomos perfectamente normales ya..
, siempre que los siguientes caracteres no sean%
ni un diseño.Sintaxis más corta para listas de listas y una forma de declarar mapas
Puede guardar bytes en listas de listas. Si tiene una lista
[[1,2],[3,4]]
, en realidad puede declararla como[1:2,3:4]
, lo que ahorra 4 corchetes = 4 bytes. Tenga en cuenta que puede usar algo más que:
(por ejemplo,^
).1:2
en realidad no es una lista en ese caso (mientras que[1,2]
era), se representa internamente como:(1,2)
. Por lo tanto, no puede usar predicados que funcionen en listas en esas sublistas que usan dos puntos.Este truco se usa principalmente para declarar mapas, es decir, una lista de claves con valores adjuntos. Por ejemplo, si desea declarar un mapa
M
que contiene la ortografía de un dígito en inglés y francés, puede hacer algo como esto:Luego, por ejemplo, puede recuperar elementos del mapa con un predicado incorporado como
member/2
. Por ejemplo, si desea el dígito y la palabra en inglés correspondientes a'Quatre'
inM
, puede hacer lo siguiente:fuente
[[1,2],[3,4]]
como1*2+3*4
, que es+(*(1,2),*(3,4))
y, por lo tanto, usar solo un byte donde de lo contrario necesitaría dos para abrir y cerrar paréntesis."abc"
en lugar de[a,b,c]
Un buen truco: cuando necesite fallar , use algo que sea equivalente a falso / 0 , pero más corto, como:
fuente
\+!
como una forma extraña de fallar de 3 bytes que en realidad no desencadena el corte!
(vea esto por qué ). No creo que sea posible fallar en menos de 3 bytes.\+!
pega a la izquierda a otros personajes gráficos, mientras que0=1
pega a la izquierda para los nombres.Reutilice un predicado con diferentes modos de llamada
Por ejemplo, puede analizar e imprimir una estructura con el mismo predicado, una vez con un argumento variable y otra vez con un término básico. Utilicé este enfoque en Make the Stretchy Snakes Kiss . Esto no es posible en todos los desafíos, por supuesto.
fuente