Consigli per giocare a golf a Prolog


16

Quali consigli generali hai per giocare a golf in Prolog? Sto cercando idee che possano essere applicate ai problemi del codice golf in generale che siano almeno in qualche modo specifiche per Prolog (ad esempio, le variabili di una lettera non sono specifiche di Prolog per ridurre le dimensioni dei programmi).

Indicare nei suggerimenti se è specifico per un'implementazione di Prolog (ad esempio incorporati specifici di SWI-Prolog)

Si prega di pubblicare un solo suggerimento per risposta o un elenco di suggerimenti strettamente correlati alla stessa idea principale.


1
Il prologtag è un po 'inutile. A meno che non abbiamo una sfida Interpret Prolog, non ne abbiamo bisogno.
gatto,

Risposte:


10

Utilizzare gli operatori per i nomi dei predicati

È possibile assegnare operatori predicati come nomi purché l'operatore sia uno degli operatori predefiniti (elencati qui ) e non sia già definito come predicato. Ciò consente di risparmiare alcuni byte sia durante la definizione che la chiamata del predicato poiché i predicati dell'operatore non devono essere scritti nella name(arg1,arg2,etc..)forma normale e possono essere chiamati come ci si potrebbe aspettare con gli operatori.

Per uno e due predicati di argomenti, possono essere dati rispettivamente i nomi di operatori unari e binari. Per predicati con arità più elevate, possiamo ancora evitare le parentesi usando la corrispondenza del modello. Ad esempio, se abbiamo un predicato A+B+C:-..., Prolog userà le regole di precedenza e associatività dell'operatore per trasformarlo in (A+B)+C:-...un predicato dell'operatore a cui corrisponde il primo argomento A+B. O A-B+C*D:-...che diventa (A-B)+(C*D)così il suo primo argomento è abbinato al modello A-Be il secondo è abbinato al modello C*D.

Esempi

_+_+_.
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.

L'output sarà X = 5.

Provalo online!

Corollario

Poiché i DCG sono zucchero sintattico per i predicati, anche a loro possono essere dati operatori. Funziona come previsto quando li chiama come DCG o da un DCG o usando i phrasepredicati o altri progettati per funzionare con DCG. Quando li chiamano come predicati sono richieste parentesi (ad es. A+B-->...Devono essere chiamate simili+(A,B,...) ) poiché i predicati DCG accettano altri due argomenti per i loro elenchi di differenze. Per gli operatori di nome DCG con più di due argomenti che utilizzano la corrispondenza del modello operatore, è importante assicurarsi che quando si chiama come predicato la distribuzione corretta degli operatori con corrispondenza modello.

Dare nomi di operatori a DCG che non accettano argomenti aggiuntivi può essere utile se è necessario chiamarli all'interno del programma da allora, è possibile farlo senza utilizzare le parentesi. È necessaria cautela perché può succedere che ciò che si salva tra parentesi si possa perdere con la spaziatura aggiunta necessaria per analizzare gli operatori adiacenti.

Esempi

/ -->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,[]).

L'output sarà

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

Provalo online!

Avvertenze

Con gli operatori unari +e -, Prolog interpreterà +20o -20come numeri invece di una chiamata a una +/1o -/1predicato. I predicati dati unari +o -come nomi possono ancora essere chiamati al numero usando le parentesi ( +(20), -(20)). Se evitando i byte in più dal parentesi è auspicabile altri operatori unari quali \, $, ecc può essere utilizzato come nomi, invece.

La combinazione di pattern matching e predicati nominati dall'operatore non è del tutto priva di difetti. Se hai due predicati che hanno lo stesso operatore del loro nome e con pattern matching uno è strettamente più generale dell'altro, allora quello più generale può essere chiamato per primo o se quello meno generale fallisce (a seconda del loro ordine nella fonte) . Ad esempio nell'esempio precedente se A-B+C*Dnon riesce a far corrispondere il suo input, Prolog proverà a chiamare X+Y. Ciò comporterà un errore perché length/2richiede Ydi essere un numero intero che non sarà poiché sarà nel modulo C*D. Questo può essere evitato semplicemente assicurandosi che due predicati non abbiano lo stesso operatore del loro nome o se ciò fallisce, quindi usando tagli e ordinamento accurato della fonte.


3
È sorprendente quanto sia utile questo trucco per il golf del codice. Ho appena ridotto il conteggio dei byte di una risposta del 16% usando solo questo suggerimento!
DLosc,

6

Prova a mettere ogni possibile caso in un'unica regola

Il modo pulito di programmare in Prolog è dichiarare più regole per lo stesso predicato. Ad esempio, un predicato per invertire un elenco con un accumulatore sarebbe simile al seguente:

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

In Code-golf, possiamo rimuovere la prima regola e aggiungere un ;alla fine della seconda regola per codificare la fine della ricorsione:

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

Sappiamo che la prima condizione r(T,[H|Z],R) fallirà se T è vuota, cioè se la ricorsione deve terminare e quindi possiamo aggiungere la nostra terminazione come o clausola dopo di essa.

Lo stesso principio funziona in molte situazioni. Si noti tuttavia che a volte è effettivamente più breve dichiarare un'altra regola anziché farlo.


6

Un trucco che è spesso utile: utilizzare i vincoli CLP (FD) per l'aritmetica dei numeri interi per ottenere predicati che possono essere utilizzati automaticamente in diverse direzioni, evitando così condizioni e rami e varianti dedicati.

Utilizzare B-Prolog o GNU Prolog, dove tali vincoli sono disponibili immediatamente, senza la necessità di caricare librerie.


2
Questo è sempre qualcosa che odio con SWI-Prolog quando faccio sfide qui. L'uso di CLPFD renderebbe alcune cose molto più pulite e brevi, ma devi aggiungere un sacco di codice extra per farlo funzionare (l'inclusione di per sé è molto ...) che di solito non ne vale la pena. Penso di averlo usato solo in questa risposta .
Fatalizza il

3
Lo odio anche io, e di sicuro alla fine arriverà il momento library(clpfd)in cui sarà disponibile come libreria precaricata o almeno caricata automaticamente anche in SWI-Prolog. Potrebbero essere necessari alcuni anni prima che l'aritmetica dichiarativa sia pienamente compresa e apprezzata da tutti gli utenti, che hanno ormai accumulato decenni di esperienza con funzionalità obsolete e di basso livello. Più usi e promuovi CLP (FD), prima lo avremo per impostazione predefinita. Fino ad allora, puoi semplicemente inserire il :- use_module(library(clpfd)).tuo ~/.swiplrce semplicemente dichiarare che stai usando quella "variante" di SWI-Prolog.
mat

6

Usa operatori aritmetici come costruttori di tuple e coppie di contro

Se è necessario passare una singola struttura composta da due o più valori, la cosa più ovvia da utilizzare è un elenco, ad es [A,B]. È davvero prolisso, però.

C'è un'alternativa. I valori di Prolog possono memorizzare una struttura annidata praticamente arbitraria, che non viene valutata. Ecco un esempio che mostra come funziona:

| ?- member(member(A,B),C).
C = [member(A,B)|_] ? ;
C = [_,member(A,B)|_] ? ;
(etc.)

member(A,B)è solo una tupla nominata in questa situazione e l'esterno member(che è una chiamata di funzione) la sta trattando come tale.

Sebbene le tuple nominate siano abbastanza utili nella programmazione Prolog senza golf, potrebbero sembrare ancora più dettagliate dell'approccio dell'elenco. Tuttavia, possiamo usare praticamente dei caratteri arbitrari nel nome del costruttore della tupla (supponendo che siano correttamente citati); invece di qualcosa di carino membero di un singolo personaggio a, possiamo fare qualcosa del genere:

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

Qui sono i nostri costruttori di tuple '-' e '/'. Ed è interessante notare cosa ha fatto la bella tipografa con loro; sta usando la notazione infissa per le tuple. Questo è davvero conciso e analizza allo stesso modo dell'operazione aritmetica comparabile. (Questo spiega anche perché l'aritmetica isnon lo usa =; A = 1+2si unirebbe Acon la tupla '+'(1,2) , quindi è necessaria una sintassi separata per valutare effettivamente l'espressione aritmetica non valutata.) Perché un costruttore di tuple deve essere chiamato qualcosa , puoi anche usare un carattere che ha una terse sintassi (e come bonus, e sono alcune delle scelte più comuni nel codice non golfato anche quando vogliono un costruttore di tuple usa e getta piuttosto che qualcosa di significativo, più o meno allo stesso modo-/i viene spesso utilizzato come variabile loop, quindi sono del tutto ragionevoli da utilizzare nel tuo input e output se ti capita di volere una tupla per qualche motivo).

'-'e '/'sono buone scelte per i costruttori di tuple perché hanno una precedenza ben educata e utile, che ti consente di scrivere letteralmente tuple. Tuttavia, si noti che non è necessario preoccuparsi della precedenza quando vengono prodotti valori intermedi all'interno del programma. Prolog mantiene le tuple archiviate come albero anziché come codice sorgente e pretty-printers può produrlo in modo inequivocabile:

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

Perché la sintassi della tupla è così concisa ( f(A,B)non è più breve dif(A-B) ), è possibile sostituire gratuitamente più argomenti prediccati con tuple, il che significa che se un predicato deve passare due o più dei suoi argomenti a un altro predicato, è spesso possibile formarli in una tupla e basta passare la tupla (anche se ciò richiederà la modifica di tutte le chiamate al predicato, oltre al predicato stesso, per utilizzare un appropriato mix di costruttori e virgole di tuple).

Un altro vantaggio di questa sintassi è se è necessario utilizzare gli elenchi internamente (anziché interagire con i predicati standard); un elenco è fondamentalmente solo un insieme di celle contro nidificate e una cella contro è solo una tupla con costruttore '.', come si può vedere qui:

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

Se il tuo codice utilizza gli elenchi "manualmente", può avere molto senso usare un costruttore di tuple meno ingombrante di '.' . Una scelta comune per me è quella di rappresentare una cella di contro come '/'(Tail,Head)(perché è il più leggibile che puoi ottenere nell'output di debug senza sprecare caratteri). Nota che probabilmente vorrai anche il tuo []equivalente; si potrebbe usare[] , ma si tratta di due byte lungo, e ci sono un sacco di atomi di un byte (tutte le lettere minuscole) che è possibile utilizzare invece.

Ad esempio, il seguente elenco:

[1,2,3]

potrebbe essere convertito in una rappresentazione manuale nello stesso numero di caratteri come questo:

x/3/2/1

pur ottenendo il vantaggio che le [H|T]corrispondenze di modelli in stile ora possono essere scritte in modo più rapido T/He un test sull'elenco vuoto come solo xpiuttosto che più lungo []. (Naturalmente, questo viene fornito con lo svantaggio ovvio che member, appendecc, non funziona su questa rappresentazione.)


+1! Non sono necessarie virgolette singole quando si utilizza -e /. Questi sono già atomi perfettamente normali.
mat

Inoltre, non sono necessarie virgolette singole ., purché i seguenti caratteri non siano né uno %né un layout.
falso

5

Sintassi più breve per gli elenchi di elenchi e un modo per dichiarare le mappe

È possibile salvare i byte negli elenchi di elenchi. Se hai un elenco [[1,2],[3,4]], puoi effettivamente dichiararlo come [1:2,3:4], che salva 4 parentesi = 4 byte. Nota che puoi usare qualcosa di diverso da :(ad esempio, ^).

1:2non è in realtà un elenco in quel caso (mentre [1,2]era), è rappresentato internamente come :(1,2). Pertanto non è possibile utilizzare predicati che funzionano sugli elenchi di quelle liste secondarie che utilizzano due punti.

Questo trucco viene utilizzato principalmente per dichiarare le mappe, ovvero un elenco di chiavi con valori associati. Ad esempio, se vuoi dichiarare una mappa Mche contiene l'ortografia di una cifra sia in inglese che in francese, potresti fare qualcosa del genere:

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

È quindi possibile, ad esempio, recuperare elementi della mappa con un predicato incorporato come member/2. Ad esempio, se si desidera che la cifra e la parola inglese corrispondano a 'Quatre'in M, è possibile:

member(Digit:Name:'Quatre',M).

1
Puoi generalizzare questo. Ad esempio, è possibile eseguire [[1,2],[3,4]]il wirte come 1*2+3*4, che è +(*(1,2),*(3,4))e quindi utilizzare anche solo un byte dove altrimenti sarebbero necessari due, per aprire e chiudere le parentesi.
mat

Gli elenchi di virgolette doppie sono ancora più brevi: invece "abc"di[a,b,c]
falso

5

Un trucco accurato: quando devi fallire , usa qualcosa che equivale a  false / 0 , ma più breve, come ad esempio:

? - ripeti, writeln (hi), 0 = 1 .

3
Citazione obbligatoria di The Art of Prolog : " Nella programmazione Prolog (in contrasto, forse, con la vita in generale) il nostro obiettivo è quello di fallire il più rapidamente possibile ". Curiosità: puoi anche usare \+!un modo strano di 3 byte per fallire che in realtà non attiva il taglio !(vedi questo per il motivo ). Non penso che sia possibile fallire in meno di 3 byte.
Fatalizza il

Stavo anche pensando a quella citazione quando ho scritto questo ;-)
mat

2
Abbiamo davvero bisogno di entrambe le versioni, \+!colle a sinistra per altri personaggi grafici, mentre 0=1colle a sinistra per i nomi.
falso

5

Riutilizzare un predicato con diverse modalità di chiamata

Ad esempio, è possibile analizzare e stampare una struttura con lo stesso predicato, una volta con un argomento variabile e un'altra volta con un termine base. Ho usato questo approccio in Make the Stretchy Snakes Kiss . Questo non è possibile in tutte le sfide, ovviamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.