Tips untuk bermain golf di Prolog

16

Apa tips umum yang Anda miliki untuk bermain golf di Prolog? Saya mencari ide yang dapat diterapkan pada masalah kode golf secara umum yang setidaknya agak spesifik untuk Prolog (mis. Satu variabel huruf tidak spesifik untuk Prolog untuk mengurangi ukuran program).

Harap tunjukkan dalam tip Anda jika itu khusus untuk implementasi Prolog (mis. SWI-Prolog khusus bawaan)

Harap posting hanya satu tip per jawaban, atau daftar tip yang semuanya terkait erat dengan ide utama yang sama.

Fatalisasi
sumber
1
The prologtag agak sia-sia. Kecuali jika kita memiliki tantangan Menafsirkan Prolog, kita tidak membutuhkannya.
kucing

Jawaban:

10

Gunakan Operator untuk Nama Predikat

Dimungkinkan untuk memberikan predikat operator sebagai nama selama operator merupakan salah satu operator yang telah ditentukan (tercantum di sini ) dan belum didefinisikan sebagai predikat. Ini menghemat beberapa byte baik ketika mendefinisikan dan memanggil predikat karena predikat operator tidak perlu ditulis dalam name(arg1,arg2,etc..)bentuk normal dan dapat dipanggil seperti yang diharapkan oleh operator.

Untuk satu dan dua predikat argumen, mereka dapat memberikan nama masing-masing operator unary dan binary. Untuk predikat arity yang lebih tinggi, kita masih bisa menghindari tanda kurung menggunakan pencocokan pola. Sebagai contoh jika kita memiliki predikat A+B+C:-..., Prolog akan menggunakan aturan presedensi dan asosiatif operator untuk mengubahnya menjadi (A+B)+C:-...predikat operator di mana argumen pertama cocok dengan pola A+B. Atau A-B+C*D:-...yang menjadi (A-B)+(C*D)argumen pertama adalah cocok dengan pola A-Bdan yang kedua adalah cocok dengan pola C*D.

Contohnya

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

Outputnya adalah X = 5.

Cobalah secara Online!

Akibat wajar

Karena DCG adalah gula sintaksis untuk predikat, mereka juga dapat diberi nama operator. Ini berfungsi seperti yang diharapkan ketika memanggil mereka sebagai DCG baik dari DCG atau menggunakan phrasepredikat atau orang lain yang dirancang untuk bekerja dengan DCG. Ketika memanggil mereka sebagai tanda kurung diperlukan (mis. A+B-->...Harus dipanggil seperti +(A,B,...)) karena predikat DCG mengambil dua argumen tambahan untuk daftar perbedaan mereka. Untuk operator yang bernama DCG dengan lebih dari dua argumen menggunakan pencocokan pola operator maka penting untuk memastikan saat memanggilnya sebagai predikat bahwa pola yang cocok operator didistribusikan dengan benar.

Memberi nama operator ke DCG yang tidak menggunakan argumen tambahan dapat berguna jika Anda perlu memanggilnya dalam program Anda sejak saat itu sehingga Anda dapat melakukannya tanpa menggunakan tanda kurung. Perhatian diperlukan karena bisa jadi apa yang Anda simpan dalam tanda kurung Anda dapat kehilangan karena penambahan jarak yang diperlukan untuk mengurai operator yang berdekatan.

Contohnya

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

Output akan menjadi

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

Cobalah online!

Peringatan

Dengan operator unary +dan -, Prolog akan mengartikan +20atau -20sebagai nomor alih-alih panggilan ke +/1atau -/1predikat. Predikat yang diberikan unary +atau -sebagai nama masih dapat dipanggil nomor dengan menggunakan tanda kurung ( +(20), -(20)). Jika menghindari byte ekstra dari kurung diinginkan operator unary lain seperti \, $, dll dapat digunakan sebagai nama sebagai gantinya.

Kombinasi predikat pencocokan pola dan nama operator tidak sepenuhnya tanpa cacat. Jika Anda memiliki dua predikat yang memiliki operator yang sama dengan nama mereka dan dengan pencocokan pola satu secara umum lebih umum daripada yang lain maka yang lebih umum dapat dipanggil terlebih dahulu atau jika yang kurang umum gagal (tergantung pada urutan mereka pada sumbernya) . Misalnya dalam contoh di atas jika A-B+C*Dgagal mencocokkan inputnya maka Prolog akan mencoba menelepon X+Y. Ini akan menghasilkan kesalahan karena length/2mengharuskanY menjadi integer yang tidak akan karena akan dalam bentuk C*D. Ini dapat dihindari hanya dengan memastikan tidak ada dua predikat yang memiliki operator yang sama dengan nama mereka atau jika gagal maka gunakan pemotongan dan pemesanan sumber dengan hati-hati.

0 '
sumber
3
Mengherankan betapa bermanfaatnya trik ini untuk kode golf. Saya baru saja mengurangi byte-count jawaban sebesar 16% menggunakan tip ini saja!
DLosc
6

Cobalah untuk menempatkan setiap kasus yang mungkin menjadi satu aturan

Cara bersih untuk memprogram dalam Prolog adalah dengan mendeklarasikan beberapa aturan untuk predikat yang sama. Misalnya, predikat untuk membalik daftar dengan akumulator akan terlihat seperti ini:

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

Di Code-golf, kita bisa menghapus aturan pertama, dan menambahkan a ; di akhir aturan kedua untuk kode akhir rekursi:

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

Kita tahu bahwa kondisi pertama r(T,[H|Z],R)akan gagal jika T kosong, yaitu jika rekursi harus berakhir dan dengan demikian kita dapat menambahkan penghentian kami sebagai atau klausa setelahnya.

Prinsip yang sama berlaku di banyak situasi. Namun perlu dicatat bahwa kadang-kadang sebenarnya lebih pendek untuk mendeklarasikan aturan lain daripada melakukan ini.

Fatalisasi
sumber
6

Salah satu trik yang sering berguna: Gunakan batasan CLP (FD) untuk aritmatika integer untuk mendapatkan predikat yang dapat digunakan secara otomatis dalam beberapa arah, sehingga menghindari kondisi dan cabang serta varian khusus.

Gunakan B-Prolog atau GNU Prolog, di mana kendala seperti itu tersedia di luar kotak, tanpa perlu memuat pustaka apa pun.

tikar
sumber
2
Ini selalu merupakan sesuatu yang saya benci dengan SWI-Prolog ketika melakukan tantangan di sini. Menggunakan CLPFD akan membuat beberapa hal lebih bersih dan lebih pendek, tetapi Anda harus menambahkan banyak kode tambahan untuk membuatnya bekerja (termasuk dengan sendirinya banyak ...) yang biasanya tidak membuatnya sepadan. Saya pikir saya hanya pernah menggunakannya dalam jawaban ini .
Fatalkan
3
Saya juga benci itu, dan pasti saatnya akan tiba ketika library(clpfd)akan tersedia sebagai perpustakaan yang dimuat sebelumnya atau setidaknya dimuat secara otomatis juga di SWI-Prolog. Mungkin perlu beberapa tahun hingga aritmatika deklaratif dipahami dan dihargai sepenuhnya oleh semua pengguna, yang saat ini telah mengumpulkan pengalaman puluhan tahun dengan fitur-fitur tingkat rendah yang sudah ketinggalan zaman. Semakin banyak Anda menggunakan dan menganjurkan CLP (FD), semakin cepat kita akan mendapatkannya secara default. Sampai saat itu, Anda cukup memasukkannya ke :- use_module(library(clpfd)).dalam~/.swiplrc dan hanya menyatakan bahwa Anda menggunakan bahwa "varian" dari SWI-Prolog.
mat
6

Gunakan operator aritmatika sebagai tuple konstruktor dan pasangan kontra

Jika Anda perlu melewati struktur tunggal yang terdiri dari dua atau lebih nilai, hal yang paling jelas untuk digunakan adalah daftar, misalnya [A,B] . Itu benar-benar bertele-tele.

Ada sebuah alternatif. Nilai prolog dapat menyimpan struktur bersarang yang cukup banyak, yang tidak dievaluasi. Berikut ini contoh yang menunjukkan cara kerjanya:

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

member(A,B) hanya tuple bernama dalam situasi ini, dan luar member (yang merupakan panggilan fungsi) memperlakukannya seperti itu.

Meskipun tuple bernama cukup berguna dalam pemrograman Prolog non-golf, tuple ini mungkin tampak lebih bertele-tele daripada pendekatan daftar. Namun, kita dapat menggunakan cukup banyak karakter arbitrer atas nama konstruktor tuple (dengan asumsi mereka dikutip dengan benar); alih-alih sesuatu yang lucu memberatau seperti karakter tunggal a, kita dapat melakukan sesuatu seperti ini:

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

Di sini, konstruktor tuple kami adalah '-'dan '/'. Dan menarik untuk dicatat apa yang dilakukan oleh printer cantik itu; itu menggunakan notasi infiks untuk tupel. Ini benar-benar singkat, dan mem-parsing dengan cara yang sama seperti operasi aritmatika yang sebanding. (Ini juga menjelaskan mengapa penggunaan aritmatika istidak =; A = 1+2akan menyatu Adengan tuple '+'(1,2) , sehingga sintaks yang terpisah diperlukan untuk benar-benar mengevaluasi ekspresi aritmatika yang tidak dievaluasi.) Karena konstruktor tuple harus disebut sesuatu , Anda juga dapat menggunakan karakter yang memiliki karakter pendek. sintaks (dan sebagai bonus, dan adalah beberapa pilihan yang paling umum dalam kode non-golf. juga ketika mereka menginginkan pembangun tuple tuple cepat daripada sesuatu yang bermakna, dengan cara yang hampir sama-/i sering digunakan sebagai variabel loop, jadi mereka sepenuhnya masuk akal untuk digunakan dalam input dan output Anda jika Anda ingin tuple di sana karena beberapa alasan).

'-'dan '/'merupakan pilihan yang baik untuk konstruktor tuple karena konstruktornya berperilaku baik dan bermanfaat, memungkinkan Anda untuk menulis literal tuple dengan singkat. Namun, perhatikan bahwa Anda tidak perlu khawatir tentang prioritas ketika nilai-nilai antara diproduksi di dalam program. Prolog menyimpan tupel yang disimpan sebagai pohon alih-alih sebagai kode sumber, dan printer cantik dapat menampilkannya dengan jelas:

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

Karena sintaks tuple begitu singkat ( f(A,B)tidak lebih pendek darif(A-B) ), Anda dapat mengganti beberapa argumen predikat dengan tupel tanpa biaya, yang berarti bahwa jika predikat perlu meneruskan dua atau lebih argumennya ke predikat lain, Anda sering dapat membentuknya menjadi tuple dan hanya lulus tuple (meskipun ini akan membutuhkan perubahan semua panggilan ke predikat, selain predikat itu sendiri, untuk menggunakan campuran yang sesuai dari konstruktor dan koma tuple).

Keuntungan lain dari sintaks ini adalah jika Anda perlu menggunakan daftar secara internal (daripada untuk beroperasi dengan predikat standar); daftar pada dasarnya hanya satu set sel kontra bersarang, dan sel kontra hanya sebuah tuple dengan konstruktor'.' , seperti yang dapat dilihat di sini:

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

Jika kode Anda menggunakan daftar "secara manual", mungkin lebih masuk akal untuk menggunakan konstruktor tuple yang lebih tebal daripada '.'. Pilihan umum bagi saya adalah merepresentasikan sel kontra sebagai '/'(Tail,Head)(karena ini adalah yang paling mudah dibaca yang bisa Anda dapatkan di hasil debug tanpa membuang-buang karakter) Perhatikan bahwa Anda mungkin juga ingin yang []setara Anda sendiri ; Anda bisa menggunakannya[] tetapi panjangnya dua byte, dan ada banyak atom satu byte (semua huruf kecil) yang bisa Anda gunakan.

Jadi misalnya, daftar berikut:

[1,2,3]

dapat dikonversi menjadi representasi manual dalam jumlah karakter yang sama seperti ini:

x/3/2/1

sementara mendapatkan keuntungan yang [H|T]cocok dengan pola-gaya sekarang dapat ditulis lebih singkat T/H, dan ujian terhadap daftar kosong hanya sebagai xbukan lebih lama []. (Tentu saja, ini datang dengan kelemahan jelas bahwa member, append, dll, tidak akan bekerja pada representasi ini.)


sumber
+1! Tidak perlu tanda kutip tunggal saat menggunakan -dan /. Ini sudah atom-atom normal.
mat
Dan, tidak ada kebutuhan untuk tanda kutip tunggal di sekitar ., asalkan karakter berikut bukan %tata letak atau.
false
5

Sintaks yang lebih pendek untuk daftar daftar dan cara untuk mendeklarasikan peta

Anda dapat menyimpan byte pada daftar daftar. Jika Anda memiliki daftar [[1,2],[3,4]], Anda sebenarnya dapat mendeklarasikannya sebagai [1:2,3:4], yang menyimpan 4 tanda kurung = 4 byte. Perhatikan bahwa Anda dapat menggunakan hal lain selain :(misalnya, ^).

1:2sebenarnya bukan daftar dalam hal itu (padahal [1,2]dulu), itu direpresentasikan secara internal sebagai :(1,2). Oleh karena itu Anda tidak dapat menggunakan predikat yang berfungsi pada daftar pada sublists yang menggunakan titik dua.

Trik ini terutama digunakan untuk mendeklarasikan peta, yaitu daftar kunci dengan nilai yang dilampirkan padanya. Misalnya, jika Anda ingin mendeklarasikan peta Myang berisi ejaan digit dalam bahasa Inggris dan Prancis, Anda bisa melakukan sesuatu seperti ini:

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

Misalnya, Anda dapat mengambil elemen peta dengan predikat bawaan member/2. Misalnya, jika Anda ingin angka dan kata bahasa Inggris yang sesuai dengan 'Quatre'di M, Anda bisa melakukan:

member(Digit:Name:'Quatre',M).
Fatalisasi
sumber
1
Anda dapat menggeneralisasi ini. Misalnya, Anda dapat wirte [[1,2],[3,4]]as 1*2+3*4, +(*(1,2),*(3,4))dan yang dengan demikian juga hanya menggunakan satu byte di mana Anda akan memerlukan dua, untuk membuka dan menutup tanda kurung.
mat
Daftar kutipan ganda bahkan lebih pendek: "abc"sebagai pengganti[a,b,c]
false
5

Satu trik yang rapi: Ketika Anda perlu gagal , gunakan sesuatu yang setara dengan  false / 0 , tetapi lebih pendek, seperti:

? - ulangi, writeln (hai), 0 = 1 .
tikar
sumber
3
Wajib The Art of Prolog mengutip: " Dalam pemrograman Prolog (sebaliknya, mungkin, untuk kehidupan secara umum) tujuan kami adalah untuk gagal secepat mungkin ". Fakta menyenangkan: Anda juga dapat menggunakan \+!cara aneh 3 byte untuk gagal yang sebenarnya tidak memicu pemangkasan !(lihat ini untuk alasannya ). Saya rasa tidak mungkin gagal dalam waktu kurang dari 3 byte.
Fatalkan
Saya juga memikirkan kutipan itu ketika saya menulis ini ;-)
mat
2
Kami benar-benar membutuhkan kedua versi, \+!menempel ke kiri ke karakter grafis lain, sedangkan 0=1menempel ke kiri untuk nama.
false
5

Gunakan kembali predikat dengan berbagai mode panggilan

Misalnya, Anda bisa menguraikan dan mencetak struktur dengan predikat yang sama, sekali dengan argumen variabel dan lain kali dengan istilah dasar. Saya menggunakan pendekatan ini dalam Make the Snake Ciuman Melar . Ini tidak mungkin dalam semua tantangan, tentu saja.

coredump
sumber