Mini-Flak Quine tercepat

26

Mini-Flak adalah bagian dari bahasa Brain-Flak , di mana <>, <...>dan []operasi tidak diizinkan. Sebenarnya itu tidak harus cocok dengan regex berikut:

.*(<|>|\[])

Mini-Flak adalah subset lengkap Turing terkecil yang diketahui dari Brain-Flak.


Beberapa saat yang lalu saya bisa membuat Quine di Mini-Flak , tetapi terlalu lambat untuk dijalankan di masa kehidupan semesta.

Jadi tantangan saya kepada Anda adalah membuat Quine lebih cepat.


Mencetak gol

Untuk @cymenilai kode Anda, letakkan bendera di akhir kode Anda dan jalankan di penerjemah Ruby ( Cobalah secara online menggunakan interpreter ruby) menggunakan -dbendera. Skor Anda harus dicetak ke STDERR sebagai berikut:

@cy <score>

Ini adalah jumlah siklus yang diambil program Anda sebelum diakhiri dan sama di antara proses yang berjalan. Karena setiap siklus membutuhkan jumlah waktu yang sama untuk dijalankan, skor Anda harus berkorelasi langsung dengan waktu yang diperlukan untuk menjalankan program Anda.

Jika Quine Anda terlalu panjang untuk dijalankan di komputer, Anda dapat menghitung jumlah siklus dengan tangan.

Menghitung jumlah siklus tidak terlalu sulit. Jumlah siklus setara dengan 2 kali jumlah run monad ditambah jumlah run nilads. Ini sama dengan mengganti setiap nilad dengan satu karakter dan menghitung jumlah karakter yang dijalankan secara total.

Contoh pemberian skor

  • (()()()) skor 5 karena memiliki 1 monad dan 3 nilad.

  • (()()()){({}[()])} skor 29 karena bagian pertama sama dengan sebelumnya dan skor 5 sedangkan loop berisi 6 monads dan 2 nilad mencetak 8. Loop dijalankan 3 kali jadi kami menghitung skornya 3 kali. 1*5 + 3*8 = 29


Persyaratan

Program Anda harus ...

  • Paling tidak 2 byte

  • Cetak kode sumbernya ketika dieksekusi di Brain-Flak menggunakan -Abendera

  • Tidak cocok dengan regex .*(<|>|\[])


Kiat

  • The Bangau-Flak interpreter adalah kategoris lebih cepat dari interpreter ruby tetapi tidak memiliki beberapa fitur. Saya akan merekomendasikan untuk menguji kode Anda menggunakan Crane-Flak terlebih dahulu dan kemudian skor di interpreter ruby ​​ketika Anda tahu itu berfungsi. Saya juga sangat merekomendasikan untuk tidak menjalankan program Anda di TIO. TIO tidak hanya lebih lambat dari penerjemah desktop, tetapi juga akan habis dalam waktu sekitar satu menit. Akan sangat mengesankan jika seseorang berhasil mencetak skor cukup rendah untuk menjalankan program mereka sebelum TIO kehabisan waktu.

  • [(...)]{}dan (...)[{}]bekerja sama <...>tetapi tidak melanggar persyaratan sumber terbatas

  • Anda dapat memeriksa Brain-Flak dan Mini-Flak Quines jika Anda menginginkan ide untuk mendekati tantangan ini.

Wisaya Gandum
sumber
1
"current best" -> "current only"
HyperNeutrino

Jawaban:

33

Mini-Flak, 6851113 siklus

Program (secara harfiah)

Saya tahu kebanyakan orang sepertinya tidak mengharapkan Mini-Flak quine menggunakan karakter yang tidak patut dan bahkan karakter multi-byte (membuat pengkodean menjadi relevan). Namun, quine ini, dan unsintables, dikombinasikan dengan ukuran quine (93919 karakter yang dikodekan sebagai 102646 bytes UTF-8), membuatnya cukup sulit untuk menempatkan program ke dalam postingan ini.

Namun, program ini sangat berulang, dan dengan demikian, kompres sangat baik. Agar seluruh program tersedia secara harfiah dari Stack Exchange, ada xxdhexdump yang dapat dibalik dari gzipversi lengkap dari quine lengkap yang tersembunyi di belakang yang dapat dilipat di bawah ini:

(Ya, itu sangat berulang sehingga Anda bahkan dapat melihat pengulangan setelah dikompresi).

Pertanyaannya mengatakan, "Saya juga sangat merekomendasikan untuk tidak menjalankan program Anda di TIO. Bukan hanya TIO lebih lambat dari penerjemah desktop, tetapi juga akan habis dalam waktu sekitar satu menit. Akan sangat mengesankan jika seseorang berhasil mencetak skor yang cukup rendah untuk dijalankan. program mereka sebelum TIO habis. " Saya bisa melakukan itu! Dibutuhkan sekitar 20 detik untuk berjalan di TIO, menggunakan interpreter Ruby: Cobalah online!

Program (dapat dibaca)

Sekarang saya telah memberikan versi program yang dapat dibaca komputer, mari kita coba versi yang dapat dibaca manusia. Saya telah mengkonversi byte yang membentuk quine menjadi codepage 437 (jika mereka memiliki bit set tinggi) atau gambar kontrol Unicode (jika mereka kode kontrol ASCII), menambahkan spasi putih (spasi yang sudah ada sebelumnya dikonversi ke gambar kontrol ), run-length-encoded menggunakan sintaks «string×length», dan beberapa bit data-berat elided:

␠
(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                («()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
                … much more data encoded the same way …
                («()×117»(«()×115»(«()×117»
                «000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
                )[({})(
                    ([({})]({}{}))
                    {
                        ((()[()]))
                    }{}
                    {
                        {
                            ({}(((({}())[()])))[{}()])
                        }{}
                        (({}))
                        ((()[()]))
                    }{}
                )]{}
                %Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
                ,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

("Hampir 241" adalah karena salinan ke-241 tidak ada yang tertinggal ', tetapi sebaliknya identik dengan 240 lainnya.)

Penjelasan

Tentang komentar

Hal pertama yang harus dijelaskan adalah, ada apa dengan karakter yang tidak patut dicetak dan sampah lain yang bukan perintah Mini-Flak? Anda mungkin berpikir bahwa menambahkan komentar ke quine hanya membuat segalanya lebih sulit, tetapi ini adalah kompetisi kecepatan (bukan kompetisi ukuran), yang berarti bahwa komentar tidak merusak kecepatan program. Sementara itu, Brain-Flak, dan dengan demikian Mini-Flak, hanya membuang isi stack ke output standar; jika Anda harus memastikan bahwa tumpukan itu hanya berisikarakter yang membentuk perintah program Anda, Anda harus menghabiskan siklus membersihkan tumpukan. Karena itu, Brain-Flak mengabaikan sebagian besar karakter, jadi selama kami memastikan bahwa elemen junk stack tidak valid perintah Brain-Flak (menjadikan ini polyglot Brain-Flak / Mini-Flak), dan tidak negatif atau di luar kisaran Unicode, kita bisa membiarkannya di stack, membiarkannya menjadi output, dan meletakkan karakter yang sama di program kita di tempat yang sama untuk mempertahankan properti quine.

Ada satu cara yang sangat penting untuk memanfaatkan ini. Quine bekerja dengan menggunakan string data yang panjang, dan pada dasarnya semua output dari quine diproduksi dengan memformat string data dengan berbagai cara. Hanya ada satu string data, meskipun faktanya program ini memiliki banyak bagian; jadi kita harus dapat menggunakan string data yang sama untuk mencetak berbagai bagian program. Trik "data sampah tidak penting" memungkinkan kita melakukan ini dengan cara yang sangat sederhana; kami menyimpan karakter yang membentuk program dalam string data dengan menambahkan atau mengurangi nilai ke atau dari kode ASCII mereka. Secara khusus, karakter yang membentuk awal program disimpan sebagai kode ASCII mereka + 4, karakter yang membentuk bagian yang diulang hampir 241 kali sebagai kode ASCII mereka - 4,setiap karakter string data dengan offset; jika, misalnya, kami mencetaknya dengan 4 ditambahkan ke setiap kode karakter, kami mendapatkan satu pengulangan dari bagian yang diulang, dengan beberapa komentar sebelum dan sesudah. (Komentar itu hanyalah bagian lain dari program, dengan kode karakter digeser sehingga mereka tidak membentuk perintah Brain-Flak yang valid, karena offset yang salah telah ditambahkan. Kita harus menghindari perintah Brain-Flak, bukan hanya Mini- Perintah Flak, untuk menghindari pelanggaran bagian dari pertanyaan; pilihan offset dirancang untuk memastikan hal ini.)

Karena trik komentar ini, kita sebenarnya hanya perlu dapat menampilkan string data yang diformat dalam dua cara berbeda: a) disandikan dengan cara yang sama seperti pada sumber, b) sebagai kode karakter dengan offset tertentu ditambahkan ke setiap kode. Itu penyederhanaan besar yang membuat panjang tambahan benar-benar berharga.

Struktur program

Program ini terdiri dari empat bagian: intro, string data, formatter string data, dan outro. Intro dan outro pada dasarnya bertanggung jawab untuk menjalankan string data dan pemformatnya dalam satu lingkaran, menentukan format yang sesuai setiap waktu (yaitu apakah akan menyandikan atau mengimbangi, dan apa yang digunakan untuk mengimbangi). Data string hanyalah data, dan merupakan satu-satunya bagian dari quine di mana karakter yang menyusunnya tidak ditentukan secara harfiah dalam string data (melakukan hal itu jelas tidak mungkin, karena harus lebih lama dari itu sendiri); dengan demikian ditulis dengan cara yang sangat mudah dibuat kembali dari dirinya sendiri. Pemformat string data terbuat dari 241 bagian yang hampir identik, yang masing-masing memformat datum spesifik dari 241 dalam string data.

Setiap bagian dari program dapat diproduksi melalui data string dan pemformatnya sebagai berikut:

  • Untuk menghasilkan outro, format string data dengan offset +8
  • Untuk menghasilkan pemformat string data, format string data dengan offset +4, 241 kali
  • Untuk menghasilkan string data, format string data melalui penyandian ke dalam format sumber
  • Untuk menghasilkan intro, format string data dengan offset -4

Jadi yang harus kita lakukan adalah melihat bagaimana bagian-bagian dari program ini bekerja.

String data

(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …

Kita memerlukan pengodean sederhana untuk string data karena kita harus dapat membalikkan pengodean dalam kode Mini-Flak. Anda tidak bisa menjadi lebih sederhana dari ini!

Gagasan kunci di balik quine ini (terlepas dari trik komentar) adalah untuk mencatat bahwa pada dasarnya hanya ada satu tempat kita dapat menyimpan data dalam jumlah besar: "jumlah nilai pengembalian perintah" dalam berbagai tingkat bersarang dari sumber program. (Ini umumnya dikenal sebagai tumpukan ketiga, meskipun Mini-Flak tidak memiliki tumpukan kedua, jadi "tumpukan kerja" kemungkinan adalah nama yang lebih baik dalam konteks Mini-Flak.) Kemungkinan lain untuk menyimpan data adalah tumpukan utama / pertama (yang tidak berfungsi karena di situlah output kita harus pergi, dan kita tidak bisa memindahkan output melewati penyimpanan dengan cara yang jauh efisien), dan dikodekan ke dalam bignum dalam elemen tumpukan tunggal (yang tidak cocok untuk masalah ini karena butuh waktu eksponensial untuk ekstrak data dari itu); ketika Anda menghilangkannya, tumpukan kerja adalah satu-satunya lokasi yang tersisa.

Untuk "menyimpan" data pada tumpukan ini, kami menggunakan perintah yang tidak seimbang (dalam hal ini, bagian pertama dari suatu (…)perintah), yang akan diseimbangkan dalam pemformat string data nanti. Setiap kali kita menutup salah satu dari perintah ini di dalam formatter, itu akan mendorong jumlah dari datum yang diambil dari string data, dan mengembalikan nilai semua perintah pada level yang bersarang di dalam formatter; kita dapat memastikan bahwa yang terakhir ditambahkan ke nol, sehingga formatter hanya melihat nilai tunggal yang diambil dari string data.

Formatnya sangat sederhana (:, diikuti oleh n salinan (), di mana n adalah nomor yang ingin kita simpan. (Perhatikan bahwa ini berarti kami hanya dapat menyimpan angka non-negatif, dan elemen terakhir dari string data harus positif.)

Satu poin yang sedikit tidak intuitif tentang string data adalah urutan urutannya. "Awal" dari string data adalah akhir yang lebih dekat dengan awal program, yaitu level bersarang terluar; bagian ini diformat terakhir (karena formatter berjalan dari tingkat sarang paling dalam ke terluar). Namun, meskipun diformat terakhir, ia akan dicetak terlebih dahulu, karena nilai yang didorong ke tumpukan pertama dicetak terakhir oleh juru bahasa Mini-Flak. Prinsip yang sama berlaku untuk program secara keseluruhan; kita perlu memformat outro terlebih dahulu, kemudian formatter data string, kemudian data string, kemudian intro, yaitu kebalikan dari urutan penyimpanannya dalam program.

Pemformat string data

)[({})(
    ([({})]({}{}))
    {
        ((()[()]))
    }{}
    {
        {
            ({}(((({}())[()])))[{}()])
        }{}
        (({}))
        ((()[()]))
    }{}
)]{}

Pemformat string data terdiri dari 241 bagian yang masing-masing memiliki kode identik (satu bagian memiliki komentar yang sedikit berbeda), masing-masing memformat satu karakter spesifik dari string data. (Kami tidak dapat menggunakan loop di sini: kami membutuhkan yang tidak seimbang )untuk membaca string data melalui pencocokan yang tidak seimbang (, dan kami tidak dapat menempatkan salah satu dari mereka di dalam satu {…}loop, satu-satunya bentuk loop yang ada. Jadi alih-alih, kita " membuka gulungan formatter, dan cukup dapatkan intro / outro untuk menampilkan string data dengan offset formatter 241 kali.)

)[({})( … )]{}

Bagian terluar dari elemen formatter membaca satu elemen dari string data; kesederhanaan pengkodean string data menyebabkan sedikit kerumitan dalam membacanya. Kita mulai dengan menutup yang tidak cocok (…)dalam string data, kemudian meniadakan ( […]) dua nilai: datum yang baru saja kita baca dari string data ( ({})) dan nilai kembali dari sisa program. Kami menyalin nilai pengembalian sisa elemen formatter dengan (…)dan menambahkan salinan ke versi yang dinegasikan {}. Hasil akhirnya adalah bahwa nilai kembali elemen string data dan elemen formatter bersama-sama adalah datum dikurangi datum dikurangi nilai balik ditambah nilai balik, atau 0; ini diperlukan untuk membuat elemen string data berikutnya menghasilkan nilai yang benar.

([({})]({}{}))

Pemformat menggunakan elemen tumpukan atas untuk mengetahui mode yang ada di dalamnya (0 = format dalam pemformatan string data, nilai lain = offset dengan output dengan). Namun, hanya setelah membaca string data, datum berada di atas format pada stack, dan kami ingin mereka sebaliknya. Kode ini adalah varian yang lebih pendek dari kode Swap Brain-Flak, mengambil sebuah atas b ke b di atas sebuah  +  b ; tidak hanya itu pendek, itu juga (dalam kasus khusus ini) lebih berguna, karena efek samping menambahkan b untuk sebuah tidak bermasalah ketika b adalah 0, dan ketika b tidak 0, itu perhitungan offset untuk kita.

{
    ((()[()]))
}{}
{
    …
    ((()[()]))
}{}

Brain-Flak hanya memiliki satu struktur aliran kontrol, jadi jika kita menginginkan yang lain selain whileloop, itu akan membutuhkan sedikit kerja. Ini adalah struktur "negasi"; jika ada 0 di atas tumpukan, itu menghilangkannya, jika tidak, tempatkan 0 di atas tumpukan. (Ini bekerja cukup sederhana: selama tidak ada 0 di atas tumpukan, tekan 1 - 1 ke tumpukan dua kali; ketika Anda selesai, pop elemen tumpukan atas.)

Dimungkinkan untuk menempatkan kode di dalam struktur negate, seperti terlihat di sini. Kode hanya akan berjalan jika bagian atas tumpukan bukan nol; jadi jika kita memiliki dua struktur peniadaan, dengan asumsi dua elemen tumpukan teratas tidak sama - sama nol, mereka akan membatalkan satu sama lain, tetapi kode apa pun di dalam struktur pertama akan berjalan hanya jika elemen tumpukan atas adalah nol, dan kode di dalamnya struktur kedua hanya akan berjalan jika elemen tumpukan atas adalah nol. Dengan kata lain, ini setara dengan pernyataan if-then-else.

Di klausa "lalu", yang berjalan jika formatnya bukan nol, sebenarnya kita tidak ada hubungannya; apa yang kita inginkan adalah mendorong data + ofset ke tumpukan utama (sehingga dapat menjadi output di akhir program), tetapi sudah ada di sana. Jadi kita hanya harus berurusan dengan kasus pengkodean elemen string data dalam bentuk sumber.

{
    ({}(((({}())[()])))[{}()])
}{}
(({}))

Begini cara kami melakukannya. The {({}( … )[{}()])}{}Struktur harus akrab sebagai loop dengan jumlah tertentu iterasi (yang bekerja dengan memindahkan loop counter untuk tumpukan bekerja dan memegang di sana, itu akan aman dari kode lainnya, karena akses ke tumpukan bekerja terikat tingkat program bersarang). Tubuh loop adalah ((({}())[()])), yang membuat tiga salinan elemen tumpukan atas dan menambahkan 1 ke terendah. Dengan kata lain, itu mengubah 40 di atas tumpukan menjadi 40 di atas 40 di atas 41, atau dipandang sebagai ASCII, (menjadi ((); menjalankan ini berulang kali akan membuat (ke (()dalam (()()ke dalam (()()()dan seterusnya, dan dengan demikian adalah cara sederhana untuk menghasilkan data string kami (dengan asumsi bahwa ada (di atas tumpukan sudah).

Setelah kita selesai dengan loop, (({}))duplikat bagian atas tumpukan (sehingga sekarang mulai ((()…daripada (()…. Pimpinan (akan digunakan oleh salinan berikutnya dari pemformat string data untuk memformat karakter berikutnya (itu akan memperluasnya menjadi (()(()…kemudian (()()(()…, dan seterusnya, jadi ini menghasilkan pemisahan (dalam string data).

%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'

Ada sedikit minat terakhir dalam formatter data string. OK, jadi kebanyakan ini hanya outro yang menggeser 4 codepoint ke bawah; Namun, apostrof pada akhirnya mungkin terlihat tidak pada tempatnya. '(codepoint 39) akan berubah menjadi +(codepoint 43), yang bukan perintah Brain-Flak, jadi Anda mungkin menduga bahwa itu ada di sana untuk tujuan lain.

Alasan ini ada di sini adalah karena pemformat string data mengharapkan sudah ada (pada stack (tidak mengandung literal 40 di mana pun). Itu'sebenarnya di awal blok yang diulang untuk membuat formatter data string, bukan akhir, jadi setelah karakter formatter data string telah didorong ke stack (dan kode akan pindah ke mencetak string data) itu sendiri), outro menyesuaikan 39 di atas tumpukan menjadi 40, siap untuk formatter (formatter berjalan sendiri kali ini, bukan representasi di sumbernya) untuk memanfaatkannya. Itu sebabnya kami memiliki "hampir 241" salinan formatter; salinan pertama tidak ada karakter pertamanya. Dan karakter itu, tanda kutip, adalah satu dari hanya tiga karakter dalam string data yang tidak sesuai dengan kode Mini-Flak di suatu tempat dalam program; itu ada di sana murni sebagai metode menyediakan konstanta.

Intro dan outro

(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                …
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

Intro dan outro secara konseptual adalah bagian yang sama dari program; satu-satunya alasan kami menarik perbedaan adalah bahwa outro perlu di-output sebelum data string dan formatter-nya (sehingga ia mencetak setelah mereka), sedangkan intro perlu menjadi output setelah mereka (mencetak sebelum mereka).

(((()()()()){}))

Kami mulai dengan menempatkan dua salinan 8 di tumpukan. Ini adalah offset untuk iterasi pertama. Salinan kedua adalah karena loop utama mengharapkan ada elemen sampah di atas tumpukan di atas offset, tertinggal dari tes yang memutuskan apakah ada loop utama, dan jadi kita perlu meletakkan elemen sampah di sana sehingga itu tidak membuang elemen yang sebenarnya kita inginkan; salinan adalah cara tersest (dengan demikian tercepat untuk menghasilkan) untuk melakukan itu.

Ada representasi lain dari nomor 8 yang tidak lebih dari yang ini. Namun, ketika mencari kode tercepat, ini jelas merupakan pilihan terbaik. Untuk satu hal, menggunakan ()()()()lebih cepat daripada, katakanlah, (()()){}karena meskipun keduanya panjangnya 8 karakter, yang pertama adalah siklus yang lebih cepat, karena (…)dihitung sebagai 2 siklus, tetapi ()hanya sebagai satu. Menyimpan satu siklus dapat diabaikan dibandingkan dengan pertimbangan yang lebih besar untuk , meskipun: (dan )memiliki titik jauh lebih rendah daripada {dan }, sehingga menghasilkan fragmen data untuk mereka akan jauh lebih cepat (dan fragmen data akan memakan lebih sedikit ruang dalam kode, terlalu).

{{} … }{}{}

Loop utama. Ini tidak menghitung iterasi (ini adalah whileloop, bukan forloop, dan menggunakan tes untuk keluar). Setelah keluar, kami membuang dua elemen tumpukan teratas; elemen atas adalah 0 yang tidak berbahaya, tetapi elemen di bawah ini akan menjadi "format untuk digunakan pada iterasi berikutnya", yang (menjadi offset negatif) adalah angka negatif, dan jika ada angka negatif di stack ketika Mini Program -Flak keluar, penerjemah crash mencoba untuk output mereka.

Karena loop ini menggunakan tes eksplisit untuk keluar, hasil tes itu akan ditinggalkan di tumpukan, jadi kami membuangnya sebagai hal pertama yang kami lakukan (nilainya tidak berguna).

(({})[(()()()())])

Kode ini mendorong 4 dan f  - 4 di atas elemen tumpukan f , sambil membiarkan elemen itu berada di tempatnya. Kami sedang menghitung format untuk iterasi berikutnya di muka (sementara kami memiliki konstanta 4 berguna), dan secara bersamaan memasukkan tumpukan ke urutan yang benar untuk beberapa bagian berikutnya dari program: kami akan menggunakan f sebagai format untuk iterasi ini, dan 4 diperlukan sebelum itu.

(({})( … )[{}])

Ini menyimpan salinan f  - 4 pada stack yang berfungsi, sehingga kita dapat menggunakannya untuk iterasi berikutnya. (Nilai f masih akan ada pada saat itu, tetapi itu akan berada di tempat yang canggung pada tumpukan, dan bahkan jika kita bisa memindahkannya ke tempat yang benar, kita harus menghabiskan siklus dengan mengurangi 4 dari itu, dan siklus mencetak kode untuk melakukan pengurangan itu. Jauh lebih mudah untuk menyimpannya sekarang.)

{{}{}((()[()]))}{}

Tes untuk melihat apakah offsetnya 4 (yaitu f  - 4 adalah 0). Jika ya, kami sedang mencetak formatter data string, jadi kita perlu menjalankan string data dan formatter-nya 241 kali daripada hanya sekali pada offset ini. Kode ini cukup sederhana: jika f  - 4 bukan nol, ganti f  - 4 dan 4 itu sendiri dengan sepasang nol; maka dalam kedua kasus, pop elemen tumpukan atas. Kami sekarang memiliki nomor di atas f pada tumpukan, baik 4 (jika kita ingin mencetak iterasi ini 241 kali) atau 0 (jika kita ingin mencetaknya hanya sekali).

(
    ((((((({})){}){}{})){}{}){}){}
    ()
)

Ini adalah semacam konstanta Brain-Flak / Mini-Flak yang menarik; garis panjang di sini mewakili angka 60. Anda mungkin bingung karena kekurangan (), yang biasanya ada di semua tempat dalam konstanta Brain-Flak; ini bukan angka biasa, tetapi angka Gereja, yang menafsirkan angka sebagai operasi duplikasi. Misalnya, angka Gereja untuk 60, terlihat di sini, membuat 60 salinan inputnya dan menggabungkan semuanya menjadi satu nilai tunggal; di Brain-Flak, satu-satunya hal yang dapat kita gabungkan adalah angka reguler, dengan tambahan, jadi kita akhirnya menambahkan 60 salinan bagian atas tumpukan dan dengan demikian mengalikan bagian atas tumpukan dengan 60.

Sebagai catatan tambahan, Anda dapat menggunakan pencari angka Underload , yang menghasilkan angka Gereja dalam sintaks Underload, untuk menemukan nomor yang sesuai di Mini-Flak juga. Angka underload (selain nol) menggunakan operasi "duplikat elemen tumpukan atas" :dan "menggabungkan dua elemen tumpukan atas" *; kedua operasi tersebut ada di Brain-Flak, jadi Anda hanya menerjemahkan :ke ), *untuk {}, menambahkan {}, dan menambahkan cukup (di awal untuk menyeimbangkan (ini menggunakan campuran aneh dari tumpukan utama dan tumpukan kerja, tetapi berfungsi).

Fragmen kode khusus ini menggunakan angka gereja 60 (secara efektif potongan "kalikan dengan 60"), bersama dengan kenaikan, untuk menghasilkan ekspresi 60 x  + 1. Jadi jika kita memiliki 4 dari langkah sebelumnya, ini memberi kita nilai dari 241, atau jika kita memiliki 0, kita hanya mendapatkan nilai 1, yaitu ini dengan benar menghitung jumlah iterasi yang kita butuhkan.

Pilihan 241 bukanlah kebetulan; itu adalah nilai yang dipilih untuk menjadi a) kira-kira berapa lama program akan berakhir dan b) 1 lebih dari 4 kali angka bulat. Angka bulat, 60 dalam hal ini, cenderung memiliki representasi yang lebih pendek sebagai angka Gereja karena Anda memiliki lebih banyak faktor fleksibilitas untuk disalin. Program ini berisi padding kemudian untuk membawa panjang hingga 241 tepat.

{
    ({}(
        …
    )[{}()])
}{}

Ini adalah untuk loop, seperti yang terlihat sebelumnya, yang hanya menjalankan kode di dalamnya beberapa kali sama dengan bagian atas tumpukan utama (yang dikonsumsi; penghitung lingkaran itu sendiri disimpan di tumpukan kerja, tetapi visibilitas dari yang terkait dengan level nesting program dan oleh karena itu tidak mungkin untuk apa pun kecuali untuk loop itu sendiri untuk berinteraksi dengannya). Ini benar-benar menjalankan string data dan pemformatnya 1 atau 241 kali, dan seperti yang sekarang kita telah muncul semua nilai yang kami gunakan untuk perhitungan aliran kontrol kami dari tumpukan utama, kami memiliki format untuk digunakan di atasnya, siap untuk formatter untuk digunakan.

(␀␀!S␠su! … many more comment characters … oq␝qoqoq)

Komentar di sini tidak sepenuhnya tanpa minat. Untuk satu hal, ada beberapa perintah Brain-Flak; yang )pada akhirnya secara alami dihasilkan sebagai efek samping dari cara transisi antara berbagai segmen dari program kerja, sehingga (di awal secara manual ditambahkan untuk menyeimbangkan itu (dan meskipun panjang komentar dalam, menempatkan komentar dalam sebuah ()perintah masih berupa ()perintah, jadi yang dilakukannya hanyalah menambahkan 1 ke nilai balik dari string data dan pemformatnya, sesuatu yang for the loop sepenuhnya abaikan).

Lebih khusus lagi, karakter-karakter NUL di awal komentar jelas tidak diimbangi dengan apa pun (bahkan perbedaan antara +8 dan -4 tidak cukup untuk mengubah a (menjadi NUL). Itu adalah padding murni untuk menghadirkan string data 239 elemen hingga 241 elemen (yang dengan mudah membayar sendiri: dibutuhkan lebih dari dua byte untuk menghasilkan 1 vs 239, bukan 1 vs 241 saat menghitung jumlah iterasi yang diperlukan ). NUL digunakan sebagai karakter padding karena memiliki codepoint serendah mungkin (membuat kode sumber untuk string data lebih pendek dan dengan demikian lebih cepat untuk output).

{}({}())

Jatuhkan elemen tumpukan atas (format yang kami gunakan), tambahkan 1 ke yang berikutnya (karakter terakhir yang akan ditampilkan, yaitu karakter pertama yang akan dicetak, dari bagian program yang baru saja kami format). Kami tidak memerlukan format lama lagi (format baru disembunyikan di tumpukan kerja); dan peningkatannya tidak berbahaya dalam banyak kasus, dan mengubah 'representasi salah satu sumber formatter data di salah satu ujungnya ((yang diperlukan pada stack untuk kali berikutnya kita menjalankan formatter, untuk memformat string data itu sendiri). Kita memerlukan transformasi seperti itu di outro atau intro, karena memaksa setiap elemen formatter data string untuk memulai (akan membuatnya agak lebih kompleks (karena kita harus menutup (dan kemudian membatalkan efeknya nanti), danentah bagaimana kita perlu membuat tambahan di (suatu tempat karena kita hanya memiliki hampir 241 salinan formatter, tidak semua 241 (jadi yang terbaik adalah karakter yang tidak berbahaya seperti 'yang hilang).

(({})(()()()()){})

Akhirnya, tes keluar loop. Bagian atas tumpukan utama saat ini adalah format yang kita butuhkan untuk iterasi berikutnya (yang baru saja kembali dari tumpukan kerja). Ini menyalin dan menambahkan 8 ke salinan; nilai yang dihasilkan akan dibuang kali berikutnya di loop. Namun, jika kita baru saja mencetak intro, offsetnya adalah -4 sehingga offset untuk "iterasi berikutnya" akan menjadi -8; -8 + 8 adalah 0, jadi loop akan keluar daripada melanjutkan ke iterasi sesudahnya.

akun sementara ais523
sumber
16

128.673.515 siklus

Cobalah online

Penjelasan

Alasan mengapa permintaan Miniflak menjadi lambat adalah kurangnya akses acak. Untuk menyiasatinya, saya membuat blok kode yang mengambil angka dan mengembalikan datum. Setiap datum mewakili satu karakter seperti sebelumnya dan kode utama hanya menanyakan blok ini untuk masing-masing sekaligus. Ini pada dasarnya berfungsi sebagai blok memori akses acak.


Blok kode ini memiliki dua persyaratan.

  • Itu harus mengambil angka dan hanya mengeluarkan kode karakter untuk karakter itu

  • Pasti mudah untuk mereproduksi tabel pencarian sedikit demi sedikit di Brain-Flak

Untuk membangun blok ini saya benar-benar menggunakan kembali metode dari bukti saya bahwa Miniflak sudah selesai. Untuk setiap datum ada blok kode yang terlihat seperti ini:

(({}[()])[(())]()){(([({}{})]{}))}{}{(([({}{}(%s))]{}))}{}

Ini mengurangi satu dari nomor di atas tumpukan dan jika nol mendorong %sdatum di bawahnya. Karena setiap bagian mengurangi ukuran satu per satu jika Anda mulai dengan n pada tumpukan, Anda akan mendapatkan kembali datum ke-n.

Ini bagus dan modular, sehingga dapat ditulis oleh suatu program dengan mudah.


Selanjutnya kita harus mengatur mesin yang benar-benar menerjemahkan memori ini ke sumbernya. Ini terdiri dari 3 bagian seperti itu:

(([()]())())
{({}[(
  -Look up table-
 )]{})
 1. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}(([{}]))(()()()()()))]{})}{}

 2. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
      (({}[(
      ({}[()(((((()()()()()){}){}){}))]{}){({}[()(({}()))]{}){({}[()(({}((((()()()){}){}){}()){}))]{}){({}[()(({}()()))]{}){({}[()(({}(((()()()()())){}{}){}))]{}){([(({}{}()))]{})}}}}}{}
      (({}({}))[({}[{}])])
     )]{}({})[()]))
      ({[()]([({}({}[({})]))]{})}{}()()()()()[(({}({})))]{})
    )]{})}{}

 3. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
     (({}(({}({}))[({}[{}])][(
     ({}[()(
      ([()](((()()[(((((((()()()){})())){}{}){}){})]((((()()()()())){}{}){})([{}]([()()](({})(([{}](()()([()()](((((({}){}){}())){}){}{}))))))))))))
     )]{})
     {({}[()(((({})())[()]))]{})}{}
     (([(((((()()()()){}){}()))){}{}([({})]((({})){}{}))]()()([()()]({}(({})([()]([({}())](({})([({}[()])]()(({})(([()](([({}()())]()({}([()](([((((((()()()())()){}){}){}()){})]({}()(([(((((({})){}){}())){}{})]({}([((((({}())){}){}){}()){}()](([()()])(()()({}(((((({}())())){}{}){}){}([((((({}))){}()){}){}]([((({}[()])){}{}){}]([()()](((((({}())){}{}){}){})(([{}](()()([()()](()()(((((()()()()()){}){}){}()){}()(([((((((()()()())){}){}())){}{})]({}([((((({})()){}){}){}()){}()](([()()])(()()({}(((((({}){}){}())){}){}{}(({})))))))))))))))))))))))))))))))))))))))))))))))
     )]{})[()]))({()()()([({})]{})}{}())
    )]{})}{}

   ({}[()])
}{}{}{}
(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

Mesin terdiri dari empat bagian yang dijalankan secara berurutan dimulai dengan 1 dan berakhir dengan 3. Saya telah memberi label pada kode di atas. Setiap bagian juga menggunakan format tabel pencarian yang sama yang saya gunakan untuk penyandian. Ini karena seluruh program terdapat dalam satu lingkaran dan kami tidak ingin menjalankan setiap bagian setiap kali kami menjalankannya sehingga kami memasukkan struktur RA yang sama dan menanyakan bagian yang kami inginkan setiap saat.

1

Bagian 1 adalah bagian pengaturan sederhana.

Program memberitahu kueri pertama bagian 1 dan datum 0. Datum 0 tidak ada sehingga alih-alih mengembalikan nilai itu, ia hanya mengurangi kueri sekali untuk setiap datum. Ini berguna karena kita dapat menggunakan hasilnya untuk menentukan jumlah data, yang akan menjadi penting di bagian selanjutnya. Bagian 1 mencatat jumlah data dengan meniadakan hasil dan pertanyaan Bagian 2 dan datum terakhir. Satu-satunya masalah adalah kita tidak dapat meminta bagian 2 secara langsung. Karena ada pengurangan lain yang tersisa, kami perlu meminta bagian 5. yang tidak ada. Faktanya, ini akan menjadi kasus setiap kali kami meminta bagian dalam bagian lain. Saya akan mengabaikan ini dalam penjelasan saya namun jika Anda mencari kode ingat saja 5 berarti kembali bagian dan 4 berarti jalankan bagian yang sama lagi.

2

Bagian 2 menerjemahkan data menjadi karakter yang membentuk kode setelah blok data. Setiap kali ia mengharapkan tumpukan muncul seperti ini:

Previous query
Result of query
Number of data
Junk we shouldn't touch...

Ini memetakan setiap hasil yang mungkin (angka dari 1 hingga 6) ke salah satu dari enam karakter Miniflak yang valid ( (){}[]) dan menempatkannya di bawah jumlah data dengan "Sampah yang tidak boleh kita sentuh". Ini memberi kami tumpukan seperti:

Previous query
Number of data
Junk we shouldn't touch...

Dari sini kita perlu melakukan kueri datum berikutnya atau jika kita kueri mereka semua pindah ke bagian 3. Kueri sebelumnya sebenarnya bukan kueri yang dikirim, melainkan kueri dikurangi jumlah data dalam blok. Ini karena setiap datum mengurangi kueri dengan satu sehingga kueri keluar cukup berantakan. Untuk menghasilkan kueri berikutnya, kami menambahkan salinan jumlah data dan mengurangi satu. Sekarang tumpukan kami terlihat seperti:

Next query
Number of data
Junk we shouldn't touch...

Jika kueri berikutnya adalah nol, kami telah membaca semua memori yang diperlukan di bagian 3 sehingga kami menambahkan jumlah data ke kueri lagi dan menampar 4 di atas tumpukan untuk pindah ke bagian 3. Jika kueri berikutnya bukan nol, kami letakkan 5 pada tumpukan untuk menjalankan bagian 2 lagi.

3

Bagian 3 membuat blok data dengan menanyakan RAM kita seperti halnya bagian 3.

Demi singkatnya, saya akan menghilangkan sebagian besar detail tentang bagaimana bagian 3 bekerja. Ini hampir identik dengan bagian 2 kecuali alih-alih menerjemahkan setiap datum ke dalam satu karakter, ia menerjemahkan masing-masing ke dalam sepotong kode panjang yang mewakili entri dalam RAM. Ketika bagian 3 selesai, ia memberi tahu program untuk keluar dari loop.


Setelah loop dijalankan, program hanya perlu menekan bit quine pertama ([()]())(()()()()){({}[(. Saya melakukan ini dengan kode berikut menerapkan teknik standar-kompleksitas Kolmogorov.

(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

Saya harap ini jelas. Berikan komentar jika Anda bingung tentang apa pun.

Wisaya Gandum
sumber
Tentang berapa lama ini berjalan? Ini kali di TIO.
Pavel
@Pavel Saya tidak menjalankannya di TIO karena itu akan sangat lambat, saya menggunakan penerjemah yang sama yang digunakan TIO (yang ruby ). Dibutuhkan sekitar 20 menit untuk berjalan di server rak lama yang dapat saya akses. Butuh sekitar 15 menit di Crain-Flak, tetapi Crain-Flak tidak memiliki flag debug, jadi saya tidak bisa mencetaknya tanpa menjalankannya di interpreter Ruby.
Wheat Wizard
@Pavel saya menjalankannya lagi dan menghitung waktunya. Dibutuhkan 30m45.284suntuk menyelesaikan pada server yang agak rendah (kira-kira setara dengan rata-rata desktop modern) menggunakan interpreter ruby.
Wheat Wizard