*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Perlu dijalankan dengan -ln
flag-command-line (karenanya +4 bytes). Mencetak 0
untuk angka komposit dan 1
untuk bilangan prima.
Cobalah online!
Saya pikir ini adalah program Stack Cats non-sepele pertama.
Penjelasan
Pengantar Stack Cats cepat:
- Stack Cats beroperasi pada pita tumpukan yang tak terbatas, dengan kepala pita mengarah ke tumpukan saat ini. Setiap tumpukan awalnya diisi dengan jumlah nol yang tak terbatas. Saya biasanya akan mengabaikan nol ini dalam kata-kata saya, jadi ketika saya mengatakan "bagian bawah tumpukan" yang saya maksud adalah nilai non-nol terendah dan jika saya mengatakan "tumpukan kosong" Maksud saya hanya ada nol di atasnya.
- Sebelum program dimulai, a
-1
didorong ke tumpukan awal, dan kemudian seluruh input didorong di atas itu. Dalam hal ini, karena -n
flag, input dibaca sebagai integer desimal.
- Di akhir program, tumpukan saat ini digunakan untuk output. Jika ada
-1
di bagian bawah, itu akan diabaikan. Sekali lagi, karena adanya -n
flag, nilai-nilai dari stack hanya dicetak sebagai bilangan bulat desimal yang dipisahkan oleh linefeed.
- Stack Cats adalah bahasa program yang dapat dibalik: setiap bagian dari kode dapat dibatalkan (tanpa Stack Cats mencatat riwayat yang eksplisit). Lebih khusus lagi, untuk membalikkan setiap kode, Anda cukup mem-mirror-nya, misalnya
<<(\-_)
menjadi (_-/)>>
. Tujuan desain ini menempatkan pembatasan yang cukup parah pada operator dan konstruksi aliran seperti apa yang ada dalam bahasa tersebut, dan fungsi apa yang dapat Anda hitung pada status memori global.
Untuk melengkapi semua ini, setiap program Stack Cats harus simetris sendiri. Anda mungkin memperhatikan bahwa ini bukan kasus untuk kode sumber di atas. Inilah -l
gunanya flag: itu secara implisit mencerminkan kode ke kiri, menggunakan karakter pertama untuk pusat. Maka program yang sebenarnya adalah:
[<(*>=*(:)*[(>*{[[>[:<[>>_(_-<<(-!>)>(>-)):]<^:>!->}<*)*[^:<)*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Pemrograman secara efektif dengan seluruh kode sangat non-sepele dan tidak intuitif dan belum benar-benar mengetahui bagaimana manusia dapat melakukannya. Kami telah dengan kasar memaksa program semacam itu untuk tugas-tugas yang lebih sederhana, tetapi tidak akan bisa mendekati itu dengan tangan. Untungnya, kami telah menemukan pola dasar yang memungkinkan Anda mengabaikan setengah dari program. Meskipun ini pasti suboptimal, saat ini satu-satunya cara yang dikenal untuk memprogram secara efektif di Stack Cats.
Jadi dalam jawaban ini, templat pola kata adalah ini (ada beberapa variabilitas dalam cara dijalankannya):
[<(...)*(...)>]
Ketika program dimulai, stack tape terlihat seperti ini (untuk input 4
, katakanlah):
4
... -1 ...
0
^
The [
bergerak atas tumpukan ke kiri (dan kepala rekaman bersama) - kita sebut ini "mendorong". Dan <
memindahkan kepala kaset itu sendiri. Jadi setelah dua perintah pertama, kita punya situasi ini:
... 4 -1 ...
0 0 0
^
Sekarang (...)
adalah loop yang dapat digunakan dengan mudah sebagai kondisional: loop dimasukkan dan dibiarkan hanya ketika bagian atas tumpukan saat ini positif. Karena saat ini nol, kami melewatkan seluruh paruh pertama program. Sekarang perintah pusat adalah *
. Ini sederhana XOR 1
, yaitu mengubah sedikit paling tidak signifikan dari bagian atas tumpukan, dan dalam hal ini mengubah 0
menjadi 1
:
... 1 4 -1 ...
0 0 0
^
Sekarang kita menemukan gambar cermin dari (...)
. Kali ini atas tumpukan positif dan kami melakukan memasukkan kode. Sebelum kita melihat apa yang terjadi di dalam tanda kurung, izinkan saya menjelaskan bagaimana kita akan menyelesaikannya di akhir: kita ingin memastikan bahwa pada akhir blok ini, kita memiliki tape head pada nilai positif lagi (sehingga lingkaran berakhir setelah iterasi tunggal dan digunakan hanya sebagai bersyarat linear), bahwa tumpukan ke kanan memegang output dan tumpukan kanan yang memegang -1
. Jika itu masalahnya, kita biarkan loop, >
bergerak ke nilai output dan ]
mendorongnya ke -1
sehingga kita memiliki tumpukan bersih untuk output.
Itu itu. Sekarang di dalam tanda kurung kita dapat melakukan apa pun yang kita inginkan untuk memeriksa keaslian selama kita memastikan bahwa kita mengatur hal-hal seperti yang dijelaskan dalam paragraf sebelumnya di akhir (yang dapat dengan mudah dilakukan dengan beberapa mendorong dan kepala pita bergerak). Saya pertama kali mencoba menyelesaikan masalah dengan teorema Wilson tetapi berakhir lebih dari 100 byte, karena perhitungan faktorial kuadrat sebenarnya cukup mahal di Stack Cats (setidaknya saya belum menemukan jalan pintas). Jadi saya pergi dengan pembagian percobaan dan ternyata memang lebih sederhana. Mari kita lihat bit linear pertama:
>:^]
Anda sudah melihat dua perintah itu. Selain itu, :
menukar dua nilai teratas dari tumpukan saat ini dan ^
XORs nilai kedua menjadi nilai teratas. Ini membuat :^
pola umum untuk menduplikasi nilai pada tumpukan kosong (kami menarik nol di atas nilai dan kemudian mengubah nol menjadi 0 XOR x = x
). Jadi setelah ini, bagian rekaman kami terlihat seperti ini:
4
... 1 4 -1 ...
0 0 0
^
Algoritma pembagian percobaan yang saya terapkan tidak berfungsi untuk input 1
, jadi kita harus melewatkan kode dalam kasus itu. Kita dapat dengan mudah memetakan 1
ke 0
dan segala sesuatu yang lain untuk nilai-nilai positif dengan *
, jadi di sini adalah bagaimana kita melakukannya:
*(*...)
Itulah kita berubah 1
menjadi 0
, lewati sebagian besar kode jika kita memang benar 0
, tetapi di dalam kita segera membatalkan *
sehingga kita mendapatkan kembali nilai input kita. Kita hanya perlu memastikan lagi bahwa kita mengakhiri nilai positif pada akhir tanda kurung agar tidak mulai berulang. Di dalam bersyarat, kita bergerak satu tumpukan yang tepat dengan >
dan kemudian mulai loop divisi sidang utama:
{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}
Kawat gigi (sebagai lawan dari tanda kurung) mendefinisikan jenis loop yang berbeda: itu adalah loop do-while, yang berarti selalu dijalankan untuk setidaknya satu iterasi. Perbedaan lainnya adalah kondisi terminasi: ketika memasuki loop Stack Cat mengingat nilai teratas dari stack saat ini ( 0
dalam kasus kami). Pengulangan akan berjalan hingga nilai yang sama ini terlihat lagi di akhir iterasi. Ini nyaman bagi kami: dalam setiap iterasi kami cukup menghitung sisa pembagi potensial berikutnya dan memindahkannya ke tumpukan ini kita mulai loop pada. Ketika kami menemukan pembagi, sisanya adalah 0
dan loop berhenti. Kami akan mencoba pembagi mulai dari n-1
dan kemudian turunkan ke 1
. Itu berarti a) kita tahu ini akan berakhir ketika kita mencapai1
paling lambat dan b) kita kemudian dapat menentukan apakah angka itu prima atau tidak dengan memeriksa pembagi terakhir yang kita coba (jika itu 1
, itu prima, kalau tidak, itu bukan).
Mari kita mulai. Ada bagian linear pendek di awal:
<-!<:^>[:
Anda tahu apa yang sebagian besar hal itu lakukan sekarang. Perintah baru adalah -
dan !
. Stack Cats tidak memiliki operator increment atau decrement. Namun ia memiliki -
(negasi, yaitu kalikan dengan -1
) dan !
(bitwise BUKAN, yaitu kalikan dengan -1
dan penurunan). Ini dapat digabungkan menjadi kenaikan !-
, atau penurunan -!
. Jadi kami mengurangi salinan n
di atas -1
, lalu membuat salinan lain n
di tumpukan ke kiri, lalu mengambil pembagi uji coba baru dan meletakkannya di bawah n
. Jadi pada iterasi pertama, kita mendapatkan ini:
4
3
... 1 4 -1 ...
0 0 0
^
Pada iterasi lebih lanjut, 3
akan diganti dengan pembagi tes berikutnya dan seterusnya (sedangkan dua salinan n
akan selalu bernilai sama pada saat ini).
((-<)<(<!-)>>-_)
Ini adalah perhitungan modulo. Karena loop berakhir pada nilai-nilai positif, idenya adalah mulai dari -n
dan berulang kali menambahkan pembagi uji coba d
hingga kami mendapatkan nilai positif. Setelah kami melakukannya, kami kurangi hasilnya d
dan ini memberi kita sisanya. Agak rumit di sini adalah bahwa kita tidak bisa hanya meletakkan -n
di atas tumpukan dan memulai loop yang menambahkan d
: jika bagian atas tumpukan negatif, loop tidak akan dimasukkan. Tersebut adalah keterbatasan bahasa pemrograman reversibel.
Jadi untuk menghindari masalah ini, kita mulai dengan n
di atas tumpukan, tetapi meniadakannya hanya pada iterasi pertama. Sekali lagi, itu terdengar lebih sederhana daripada ternyata ...
(-<)
Ketika bagian atas tumpukan positif (yaitu hanya pada iterasi pertama), kami meniadakannya -
. Namun, kita tidak bisa hanya melakukannya (-)
karena kita tidak akan meninggalkan loop sampai -
diterapkan dua kali. Jadi kami memindahkan satu sel ke kiri <
karena kami tahu ada nilai positif di sana 1
. Oke, jadi sekarang kita sudah bisa diandalkan meniadakan n
iterasi pertama. Tetapi kami memiliki masalah baru: kepala kaset sekarang dalam posisi yang berbeda pada iterasi pertama daripada yang lainnya. Kita perlu mengkonsolidasikan ini sebelum kita melanjutkan. Selanjutnya <
memindahkan kepala kaset ke kiri. Situasi pada iterasi pertama:
-4
3
... 1 4 -1 ...
0 0 0 0
^
Dan pada iterasi kedua (ingat kita telah menambahkan d
sekali ke -n
sekarang):
-1
3
... 1 4 -1 ...
0 0 0
^
Persyaratan berikutnya menggabungkan jalur ini lagi:
(<!-)
Pada iterasi pertama head tape menunjuk pada nol, jadi ini dilewati seluruhnya. Pada iterasi lebih lanjut, kepala kaset menunjuk pada satu, jadi kami melakukan ini, pindah ke kiri dan sel meningkat di sana. Karena kita tahu sel dimulai dari nol, sekarang akan selalu positif sehingga kita dapat meninggalkan loop. Ini memastikan kita selalu berakhir dengan dua tumpukan yang tersisa dari tumpukan utama dan sekarang dapat kembali >>
. Kemudian pada akhir modulo loop kita lakukan -_
. Anda sudah tahu -
. _
adalah untuk mengurangi apa ^
yang harus XOR: jika bagian atas tumpukan a
dan nilai di bawahnya b
diganti a
dengan b-a
. Sejak kami pertama kali dinegasikan a
, -_
diganti a
dengan b+a
, dengan demikian menambahkand
ke total berjalan kami.
Setelah nilai loop berakhir (kami telah mencapai nilai positif), rekaman itu terlihat seperti ini:
2
3
... 1 1 4 -1 ...
0 0 0 0
^
Nilai paling kiri bisa berupa angka positif. Bahkan, itu jumlah iterasi minus satu. Ada bit linear pendek lain sekarang:
_<<]>:]<]]
Seperti yang saya katakan sebelumnya, kita perlu mengurangi hasil dari d
untuk mendapatkan sisa aktual ( 3-2 = 1 = 4 % 3
), jadi kita lakukan _
sekali lagi. Selanjutnya, kita perlu membersihkan tumpukan yang telah kita tambahkan di sebelah kiri: ketika kita mencoba pembagi berikutnya, itu harus nol lagi, agar iterasi pertama berfungsi. Jadi kami pindah ke sana dan mendorong nilai positif itu ke tumpukan pembantu lain <<]
dan kemudian pindah ke tumpukan operasional kami dengan yang lain >
. Kami menarik d
dengan :
dan mendorongnya kembali ke -1
dengan ]
dan kemudian kami memindahkan sisanya ke tumpukan bersyarat kami dengan <]]
. Itulah akhir dari loop pembagian percobaan: ini berlanjut sampai kita mendapatkan sisa nol, dalam hal ini tumpukan di sebelah kiri berisin
Pembagi terbesar (selain n
).
Setelah loop berakhir, ada beberapa saat *<
sebelum kita bergabung dengan input 1
lagi. The *
hanya mengubah nol menjadi 1
, yang kita perlukan sedikit, dan kemudian kami pindah ke pembagi dengan <
(sehingga kita berada di tumpukan yang sama seperti untuk input 1
).
Pada titik ini membantu untuk membandingkan tiga jenis input yang berbeda. Pertama, kasus khusus di n = 1
mana kami belum melakukan hal-hal pembagian sidang itu:
0
... 1 1 -1 ...
0 0 0
^
Kemudian, contoh kita sebelumnya n = 4
, angka gabungan:
2
1 2 1
... 1 4 -1 1 ...
0 0 0 0
^
Dan akhirnya,, n = 3
bilangan prima:
3
1 1 1
... 1 3 -1 1 ...
0 0 0 0
^
Jadi untuk bilangan prima, kita memiliki 1
pada tumpukan ini, dan untuk bilangan komposit kita memiliki 0
bilangan positif atau lebih besar dari 2
. Kami mengubah situasi ini menjadi 0
atau 1
kita perlu dengan potongan kode berikut:
]*(:)*=<*
]
hanya mendorong nilai ini ke kanan. Kemudian *
digunakan untuk menyederhanakan situasi kondisional: dengan mengubah bit paling tidak signifikan, kita mengubah 1
(prime) menjadi 0
,0
(komposit) menjadi nilai positif 1
, dan semua nilai positif lainnya masih akan tetap positif. Sekarang kita hanya perlu membedakan 0
dan positif. Di situlah kita menggunakan yang lain (:)
. Jika bagian atas tumpukan adalah 0
(dan inputnya adalah yang utama), ini hanya dilewati. Tetapi jika bagian atas tumpukan positif (dan inputnya adalah angka komposit) ini menukar dengan 1
, sehingga kita sekarang memiliki 0
untuk komposit dan1
untuk bilangan prima - hanya dua nilai yang berbeda. Tentu saja, itu kebalikan dari apa yang ingin kita hasilkan, tetapi itu mudah diperbaiki dengan yang lain *
.
Sekarang yang tersisa adalah mengembalikan pola tumpukan yang diharapkan oleh kerangka kerja kita di sekitarnya: selotip pada nilai positif, hasil di atas tumpukan di sebelah kanan, dan satu -1
di tumpukan di sebelah kanan itu . Ini untuk apa =<*
. =
menukar bagian atas dua tumpukan yang berdekatan, dengan demikian memindahkan-1
ke kanan hasil, misalnya untuk input 4
lagi:
2 0
1 3
... 1 4 1 -1 ...
0 0 0 0 0
^
Kemudian kita hanya bergerak ke kiri <
dan mengubah nol itu menjadi satu dengan *
. Dan itu saja.
Jika Anda ingin menggali lebih dalam tentang cara kerja program, Anda dapat menggunakan opsi debug. Tambahkan -d
bendera dan sisipkan ke "
mana pun Anda ingin melihat status memori saat ini, misalnya seperti ini , atau gunakan -D
bendera untuk mendapatkan jejak lengkap dari seluruh program . Atau, Anda dapat menggunakan EsotericIDE Timwi yang mencakup juru bahasa Stack Cats dengan debugger langkah-demi-langkah.