Consejos para jugar al golf en Prolog

16

¿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.

Fatalizar
fuente
1
La prologetiqueta es un poco inútil. A menos que tengamos un desafío Interpretar Prólogo, no lo necesitamos.
gato

Respuestas:

10

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 normalname(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 argumento A+B. O lo A-B+C*D:-...que se hace (A-B)+(C*D)así, su primer argumento coincide con el patrón A-By su segundo coincide con el patrón C*D.

Ejemplos

_+_+_.
A-B+C*D:-between(A,B,C),C+D.
\X:-X>1,X<10.
X+Y:-length(Y,X),member(X,Y).



5+[10,5,3,2,5],a+b+c,0-20+X*[2,4,6,5,40],\9.

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 phrasepredicados 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

/ -->a+b+X,X+d+e.
A+B+C-->[A],[B],[C].


X/[],member(c,X),phrase(f+o+o,Y),+(b+a,r,Z,[]).

La salida será

X = [a, b, c, c, d, e],
Y = [f, o, o],
Z = [b, a, r].

Pruébalo en línea!

Advertencias

Con los operadores unitarios +y -, Prolog interpretará +20o -20como números en lugar de una llamada a una +/1o -/1predicado. 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*Dno coincide con su entrada, Prolog intentará llamar X+Y. Esto dará lugar a un error debido a length/2requerir Yser un número entero que no será ya que será en la forma C*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.

0 '
fuente
3
Es sorprendente lo útil que es este truco para el golf de código. ¡Acabo de reducir el conteo de bytes de una respuesta en un 16% usando solo este consejo!
DLosc
6

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í:

r([],Z,Z).
r([H|T],Z,R):-r(T,[H|Z],R).

En Code-golf, podemos eliminar la primera regla y agregar un ;al final de la segunda regla para codificar el final de la recursividad:

r([H|T],Z,R):-r(T,[H|Z],R);R=[H|Z].

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.

Fatalizar
fuente
6

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.

estera
fuente
2
Esto siempre es algo que odio con SWI-Prolog cuando hago desafíos aquí. El uso de CLPFD haría que algunas cosas fueran mucho más limpias y cortas, pero debe agregar una gran cantidad de código adicional para que funcione (la inclusión en sí misma es mucho ...), lo que generalmente no hace que valga la pena. Creo que solo lo he usado en esta respuesta .
Fatalize
3
También odio eso, y seguramente llegará el momento en 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 ~/.swiplrcy simplemente indicar que está utilizando esa "variante" de SWI-Prolog.
mat
6

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(member(A,B),C).
C = [member(A,B)|_] ? ;
C = [_,member(A,B)|_] ? ;
(etc.)

member(A,B)es solo una tupla con nombre en esta situación, y el exterior member(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 membero un solo personaje a, podemos hacer algo como esto:

| ?- A = '-'('/'(1,2), '/'(3,4)).
A = 1/2-3/4

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éticos isno =; A = 1+2unificarí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 a imenudo 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:

| ?- A = '-'('-'(1,2), '-'(3,4)).
A = 1-2-(3-4)

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í:

| ?- Q = '.'('.'(A,B),'.'(C,D)).
Q = [[A|B],C|D]

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:

[1,2,3]

podría convertirse en una representación manual en el mismo número de caracteres como este:

x/3/2/1

mientras obtiene la ventaja de que [H|T]las coincidencias de patrones de estilo ahora se pueden escribir de manera más concisa como T/H, y una prueba contra la lista vacía, en xlugar de hacerlo por más tiempo []. (Por supuesto, esto viene con el inconveniente obvio que member, append, etc, no funcionará en esta representación.)


fuente
+1! No hay necesidad de comillas simples cuando se usa -y /. Estos son átomos perfectamente normales ya.
mat
Y, no hay necesidad de comillas simples ., siempre que los siguientes caracteres no sean %ni un diseño.
falso
5

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:2en 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 Mque contiene la ortografía de un dígito en inglés y francés, puede hacer algo como esto:

M=[0:'Zero':'Zéro',1:'One':'Un',2:'Two':'Deux', ... ]

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'in M, puede hacer lo siguiente:

member(Digit:Name:'Quatre',M).
Fatalizar
fuente
1
Puedes generalizar esto. Por ejemplo, puede escribir [[1,2],[3,4]]como 1*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.
mat
Las listas de comillas dobles son aún más cortas: "abc"en lugar de[a,b,c]
falso
5

Un buen truco: cuando necesite fallar , use algo que sea equivalente a  falso / 0 , pero más corto, como:

? - repite, writeln (hola), 0 = 1 .
estera
fuente
3
Obligatorio The Art of Prolog cita: " En la programación de Prolog (en contraste, tal vez, con la vida en general), nuestro objetivo es fallar lo más rápido posible ". Dato curioso: también puede usarlo \+!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.
Fatalize
También estaba pensando en esa cita cuando escribí esto ;-)
mat
2
Realmente necesitamos ambas versiones, \+!pega a la izquierda a otros personajes gráficos, mientras que 0=1pega a la izquierda para los nombres.
falso
5

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.

volcado de memoria
fuente