Latar Belakang
Di situs ini, kami kadang-kadang memiliki pertanyaan yang memerlukan program untuk "radiasi diperkeras"; ini berarti bahwa program tersebut harus mampu bertahan dari penghapusan satu atau lebih byte, tidak peduli byte mana yang dihapus.
Seperti biasa untuk tugas-tugas yang sering diatur dalam tantangan pemrograman, itu wajar untuk ingin membuat bahasa yang sangat baik pada tantangan ini. Mengingat bahwa cara alami untuk membuat ini adalah dengan menambahkan beberapa metadata yang memungkinkan untuk membalikkan korupsi, itu sebenarnya bukan bahasa yang perlu dirancang, tetapi suatu pengkodean; idenya adalah untuk mengubah setiap input ke urutan byte, sedemikian rupa sehingga bahkan jika urutannya sedikit diiradiasi, dimungkinkan untuk mengekstrak input asli.
Tugas
Tulis dua program atau fungsi, E (sebuah encoder) dan D (sebuah decoder), sehingga:
- E mengambil dua argumen, urutan oktet (yang akan kita sebut " input " dalam spesifikasi ini) dan integer non-negatif " radiasi ", dan menghasilkan urutan oktet " encoding ";
- D mengambil satu argumen, urutan oktet (" encdng "), dan menghasilkan urutan oktet " rekonstruksi ";
- Jika Anda menjalankan E dan D (dengan encdng , input ke D, dipilih dengan menghapus tidak lebih dari elemen radiasi dari encoding (tidak harus bersamaan)), maka rekonstruksi akan sama dengan input tidak peduli karakter mana yang dihapus untuk membentuk encdng .
Klarifikasi
- Jika Anda mengirim fungsi, Anda tidak harus memanggilnya
E
danD
; Anda dapat memilih nama apa pun yang paling cocok untuk bahasa Anda. - "Oktet" pada dasarnya adalah bilangan bulat dari 0 hingga 255 inklusif, yang dapat Anda enkode sebagai bilangan bulat, karakter, atau apa pun yang sesuai untuk bahasa Anda.
- E dan D harus sepenuhnya deterministik (yaitu memberi mereka input yang sama akan selalu menghasilkan output yang sama, di mana "input" didefinisikan sebagai input dan radiasi untuk E, atau encdng untuk D). Secara khusus, E mungkin tidak mengkomunikasikan informasi ke D melalui saluran samping.
- Penghapusan dilakukan dengan menghapus satu elemen dari urutan; pikirkan membuka seuqence dalam editor, menempatkan kursor pada titik arbitrer, dan menekan Backspace. Jika suatu elemen muncul beberapa kali, mungkin saja hanya satu salinan elemen yang akan dihapus (contoh lain dari oktet yang sama tidak akan terpengaruh).
- Meskipun skor hanya dihitung berdasarkan input yang cukup pendek , program Anda harus bekerja secara teori untuk setiap input dan radiasi . Secara khusus, ia harus bekerja tidak peduli oktet mana yang muncul pada input . (Maaf, orang-orang yang menginginkan kemampuan untuk menggunakan karakter yang tidak diinginkan yang mereka tahu tidak akan muncul di input, tapi saya perlu memastikan bahwa inputnya tidak dapat dimampatkan sehingga tantangannya adalah tentang pengerasan radiasi daripada kompresi.)
- Anda dapat mengirim salah satu file yang mendefinisikan dua fungsi; dua file yang masing-masing mendefinisikan suatu fungsi atau yang keduanya merupakan program penuh; atau tiga file, dua di antaranya mengimplementasikan D dan E masing-masing (baik melalui program penuh atau melalui mendefinisikan fungsi), dan yang ketiga merupakan file header atau pustaka yang umum untuk D dan E. Terlepas dari bentuk pengajuan yang Anda gunakan , implementasi bahasa pemrograman Anda harus dapat memahami kedua program tanpa argumen lebih lanjut seperti lokasi file (atau Anda harus membayar penalti byte untuk meminta implementasi Anda dengan cara yang tidak biasa, sesuai aturan standar kami).
Kondisi kemenangan
Untuk setiap panjang dan radiasi , misalkan f ( panjang , radiasi ) menjadi panjang total enkode yang sesuai dengan semua input dengan panjang panjang , dan radiasi yang diberikan . (Yaitu, f ( panjang , radiasi ) = jumlah input memiliki panjang panjang panjang (E ( input , radiasi )).) Kemudian biarkan g ( panjang , radiasi ) sama dengan f ( radiasi panjang ,) ÷ 256 panjang . Dengan kata lain, g adalah panjang rata-rata dari output yang dikodekan, untuk panjang input yang diberikan dan persyaratan pengerasan radiasi yang diberikan. (Secara teori Anda dapat menghitung ini dengan kekerasan, tetapi kemungkinan akan memakan waktu lama untuk menghitung skor Anda dengan cara itu. Saya berharap sebagian besar pengiriman akan dapat membuat argumen matematis tentang berapa skor mereka. Jika Anda ' tidak yakin, memposting skor perkiraan dan Anda atau orang lain dapat menghitungnya lebih mendalam jika entri lain memposting skor yang sama.)
Skor Anda sama dengan jumlah g ( panjang , radiasi ) untuk semua radiasi dalam rentang 0 hingga 9 inklusif, dan semua panjang dalam kisaran 0 hingga 99 inklusif, plus (kebanyakan untuk menghindari hardcoding, atau untuk menjaga agar kompetisi tetap berjalan jika seseorang menemukan penyandian yang sempurna secara matematis; ini mungkin merupakan faktor minimal jika tidak) total jumlah byte dalam pengiriman Anda ke tantangan (ditambah hukuman standar untuk hal-hal seperti memerlukan bendera juru bahasa yang tidak biasa atau nama file tertentu). Pemenangnya adalah entri dengan skor terendah (tiebroken oleh entri pertama yang dikirimkan).
Jawaban:
CJam, skor ≤ 286.516 + 54 + 36 = 286.606
Encoder
Cobalah online!
Dekoder
Cobalah online!
Keduanya mengambil dan mengembalikan daftar bilangan bulat. TIO Link menyertakan konversi dari / ke string untuk kenyamanan. Perhatikan bahwa ini sangat tidak efisien untuk string yang lebih lama. Jika Anda ingin mencoba beberapa karakter lagi, saya sarankan menggunakan karakter dengan kode karakter kecil.
Gagasan dasar untuk membuat pengkodean yang diperkuat radiasi melibatkan dua langkah:
Dengan cara ini, radiasi tidak dapat sepenuhnya menghapus satu run karakter yang identik, sehingga kita dapat mendekode string dengan mengambil satu karakter dari setiap run dan kemudian mendekode langkah 1.
Jadi satu-satunya bagian yang menarik adalah menemukan pengkodean yang tidak pernah menghasilkan oktet berulang. Ide dasarnya adalah menggunakan sesuatu seperti A043096 sebagai sistem angka. Artinya, untuk menyandikan bilangan bulat N , kita cukup menghitung dalam beberapa basis b , melewatkan semua angka dengan oktet berulang. Saya percaya jumlah angka yang dapat direpresentasikan dengan cara ini dengan hingga d digit sama dengan jumlah angka yang dapat direpresentasikan dalam basis bijektif b-1 (karena, ketika Anda ingin menulis angka seperti itu, Anda dapat pilih antara b-1 digit untuk setiap posisi tanpa melanggar batasan).
Tentu saja, untuk mendapatkan kompresi maksimal kita akan menggunakan b = 256 . Untuk mengubah input menjadi bilangan bulat, kita dapat menggunakan konversi basis juga. Jika saya tidak malas, saya akan menggunakan basis kata sifat untuk input, tetapi untuk sekarang saya hanya menambahkan a
1
(untuk memastikan tidak ada nol di depan) dan kemudian menggunakan basis terkecil yang mungkin sehingga semua oktet di input kurang dari basis.Basis ini kemudian didahului dengan pengkodean (sehingga decoder mengetahui basis mana yang digunakan) dan dipisahkan dari angka yang tersisa dengan 0-oktet (ini bekerja karena angka yang tersisa tidak akan pernah mulai dengan nol). Sebagai optimasi kecil, string kosong tetap merupakan string kosong.
Alasan saya belum menghitung skor tepat di atas adalah bahwa saya hanya menghitung batas atas untuk berapa lama setiap input akan didasarkan pada panjang dan oktet maksimalnya. Namun, untuk dua parameter ini, sering kali akan ada dua panjang output yang berbeda, dan saya belum repot mencari tahu di mana titik kritis di antara mereka terjadi. Saya juga menggunakan panjang basis biasa 255 daripada basis bijektif 255 untuk memperkirakan panjang itu, yang lagi-lagi sedikit lebih besar dari yang seharusnya. Kode Mathematica persis yang saya gunakan untuk perhitungan adalah sebagai berikut:
num[l, b]
harus memberikan jumlah string panjangl
dengan oktet maksimumb-1
(kecuali untukb == 1
, di mana saya telah hardcode0
karena saya selalu menggunakan setidaknya pangkalan2
).sumber
bash + Utilitas GNU, skor
294506283468Sunting 1: Memperbaiki masalah yang diperhatikan oleh @Leo - terima kasih!
Sunting 2: Memperbaiki metode pengkodean untuk parameter radiasi, untuk skor yang lebih baik.
Pengkode (97 byte):
Dekoder (121 byte):
Untuk pembuat enkode: Urutan oktet dilewatkan sebagai karakter dalam stdin, parameter radiasi r dilewatkan sebagai argumen.
Untuk dekoder: Input diberikan sebagai karakter di stdin.
Untuk keduanya: Output pada stdout.
Pembuat enkode menambahkan ke dalam data input digit r, dengan karakter 'a' dimasukkan di antara setiap pasangan digit identik berturut-turut, diikuti oleh satu baris baru. Kemudian salin semua input (dimulai dengan karakter yang diawali), mengganti setiap karakter dengan r +1 salinan karakter itu.
Decoder membatalkan ini, melalui masing-masing karakter yang tersisa x dalam inputnya, melompati hingga r salinan identik berturut-turut dari x berikut x, dan mencetak yang tersisa. Data yang digantung sebelumnya tidak memiliki karakter yang diulang, sehingga dapat didekodekan sebelum r diketahui. Pada titik itu, r diketahui, dan nilai itu diperlukan untuk memecahkan kode sisa data dengan benar.
Anda dapat memeriksa apakah ini berfungsi bahkan jika input asli telah mengulangi karakter yang sama.
Perhitungan skor:
Misalkan input memiliki panjang L dan parameter radiasi adalah r (yang paling banyak 9 untuk perhitungan skor, sehingga cocok dalam satu digit dan karenanya tidak memiliki karakter yang diulang berturut-turut). Data yang ditambahkan adalah 2 byte (digit, baris baru), sehingga outputnya adalah (r + 1) (L + 2) byte untuk aliran yang disandikan.
Jadi g (L, r) = (r + 1) (L + 2).
Oleh karena itu skor total dapat dihitung sebagai
sumber
r
dibaca222
), tetapi untungnya skor dihitung lebih dari radiasi 0-9, sehingga tidak akan terlalu terpengaruh. PS Saya sedang berpikir untuk mengimplementasikan pengkodean yang sama ini, itu sebabnya saya langsung melihat kesalahan;)Perl + Math :: {ModInt, Polinomial, Prime :: Util}, skor ≤ 92819
Gambar kontrol digunakan untuk mewakili karakter kontrol yang sesuai (mis
␀
Adalah karakter NUL literal). Jangan terlalu khawatir tentang mencoba membaca kode; ada versi yang lebih mudah dibaca di bawah ini.Jalankan dengan
-Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all
.-MMath::Bigint=lib,GMP
tidak perlu (dan dengan demikian tidak termasuk dalam skor), tetapi jika Anda menambahkannya sebelum perpustakaan lain itu akan membuat program berjalan agak lebih cepat.Perhitungan skor
Algoritme di sini agak dapat diperbaiki, tetapi akan lebih sulit untuk menulis (karena Perl tidak memiliki perpustakaan yang sesuai). Karena itu, saya membuat beberapa ukuran / efisiensi pengorbanan dalam kode, dengan dasar bahwa mengingat byte dapat disimpan dalam pengkodean, tidak ada gunanya mencoba mencukur setiap titik dari golf.
Program ini terdiri dari 600 byte kode, ditambah 78 byte hukuman untuk opsi baris perintah, memberikan hukuman 678 poin. Sisa skor dihitung dengan menjalankan program pada string kasus terbaik dan terburuk (dalam hal panjang output) untuk setiap panjang dari 0 hingga 99 dan setiap tingkat radiasi dari 0 hingga 9; kasus rata-rata ada di antara keduanya, dan ini memberi batasan pada skor. (Tidak ada gunanya mencoba menghitung nilai yang tepat kecuali entri lain masuk dengan skor yang sama.)
Oleh karena itu, ini berarti bahwa skor dari efisiensi pengkodean berada dalam kisaran 91100 hingga 92141 inklusif, sehingga skor akhirnya adalah:
91100 + 600 + 78 = 91778 ≤ skor ≤ 92819 = 92141 + 600 + 78
Versi kurang golf, dengan komentar dan kode uji
Ini adalah program asli + baris baru, lekukan, dan komentar. (Sebenarnya, versi golf diproduksi dengan menghapus baris baru / lekukan / komentar dari versi ini.)
Algoritma
Menyederhanakan masalah
Ide dasarnya adalah untuk mengurangi masalah "penghapusan kode" ini (yang bukan merupakan masalah yang banyak dieksplorasi) menjadi masalah pengkodean penghapusan (area matematika yang dieksplorasi secara komprehensif). Gagasan di balik pengkodean penghapusan adalah bahwa Anda menyiapkan data untuk dikirim melalui "saluran penghapusan", saluran yang terkadang menggantikan karakter yang dikirim dengan karakter "memutarbalikkan" yang menunjukkan posisi kesalahan yang diketahui. (Dengan kata lain, selalu jelas di mana korupsi telah terjadi, meskipun karakter aslinya masih belum diketahui.) Gagasan di balik itu cukup sederhana: kami membagi input menjadi beberapa blok panjang ( radiasi+1, dan menggunakan tujuh dari delapan bit di setiap blok untuk data, sedangkan bit yang tersisa (dalam konstruksi ini, MSB) bergantian antara ditetapkan untuk seluruh blok, kosong untuk seluruh blok berikutnya, ditetapkan untuk blok setelah itu, dan seterusnya. Karena blok lebih panjang dari parameter radiasi, setidaknya satu karakter dari setiap blok bertahan ke output; jadi dengan menjalankan karakter dengan MSB yang sama, kita dapat mengetahui blok mana yang dimiliki masing-masing karakter. Jumlah blok juga selalu lebih besar dari parameter radiasi, jadi kami selalu memiliki setidaknya satu blok tidak rusak di encdng; kita dengan demikian tahu bahwa semua blok yang terpanjang atau terikat paling lama tidak rusak, memungkinkan kita untuk memperlakukan blok yang lebih pendek sebagai yang rusak (sehingga menjadi puing-puing). Kami juga dapat menyimpulkan parameter radiasi seperti ini (itu '
Penghapusan coding
Adapun bagian pengkodean penghapusan masalah, ini menggunakan kasus khusus sederhana dari konstruksi Reed-Solomon. Ini adalah konstruksi yang sistematis: output (dari algoritma pengkodean penghapusan) sama dengan input ditambah sejumlah blok tambahan, sama dengan parameter radiasi. Kita dapat menghitung nilai-nilai yang diperlukan untuk blok ini dengan cara yang sederhana (dan golf!), Dengan memperlakukannya sebagai kerusakan, kemudian menjalankan algoritma penguraian kode pada mereka untuk "merekonstruksi" nilai mereka.
Gagasan sebenarnya di balik konstruksi juga sangat sederhana: kami memuat polinomial, dengan derajat minimum yang mungkin, untuk semua blok dalam pengkodean (dengan krik yang diinterpolasi dari elemen lain); jika polinomialnya adalah f , blok pertama adalah f (0), yang kedua adalah f (1), dan seterusnya. Jelas bahwa derajat polinomial akan sama dengan jumlah blok input minus 1 (karena kami memasukkan polinomial dengan yang pertama, kemudian menggunakannya untuk membangun blok "periksa" tambahan); dan karena d +1 poin secara unik mendefinisikan polinomial derajat d, memutarbalikkan sejumlah blok (hingga parameter radiasi) akan meninggalkan sejumlah blok yang tidak rusak sama dengan input asli, yang merupakan informasi yang cukup untuk merekonstruksi polinomial yang sama. (Kemudian kita hanya perlu mengevaluasi polinomial untuk memisahkan blok.)
Konversi basis
Pertimbangan terakhir yang tersisa di sini adalah berkaitan dengan nilai aktual yang diambil oleh blok; jika kita melakukan interpolasi polinomial pada bilangan bulat, hasilnya mungkin bilangan rasional (bukan bilangan bulat), jauh lebih besar dari nilai input, atau sebaliknya tidak diinginkan. Karena itu, alih-alih menggunakan bilangan bulat, kami menggunakan bidang terbatas; dalam program ini, bidang hingga yang digunakan adalah bidang bilangan bulat modulo p , di mana p adalah bilangan prima terbesar kurang dari 128 radiasi +1(Yaitu prime terbesar yang kami dapat mencocokkan sejumlah nilai berbeda sama dengan prime itu ke dalam bagian data blok). Keuntungan besar bidang hingga adalah pembagian itu (kecuali 0) didefinisikan secara unik dan akan selalu menghasilkan nilai di dalam bidang itu; dengan demikian, nilai interpolasi polinomial akan masuk ke dalam blok sama seperti yang dilakukan oleh nilai input.
Untuk mengubah input menjadi serangkaian data blok, maka, kita perlu melakukan konversi basis: mengkonversi input dari basis 256 menjadi angka, kemudian mengkonversi menjadi basis p (misalnya untuk parameter radiasi 1, kita harus p= 16381). Ini sebagian besar ditangguhkan oleh kurangnya rutinitas konversi basis (Math :: Prime :: Util memiliki beberapa, tetapi mereka tidak bekerja untuk basis bignum, dan beberapa bilangan prima yang bekerja dengan kami di sini sangat besar). Karena kita sudah menggunakan Math :: Polynomial untuk interpolasi polinomial, saya dapat menggunakannya kembali sebagai fungsi "convert from digit sequence" (melalui melihat angka sebagai koefisien dari polinomial dan mengevaluasinya), dan ini berfungsi untuk bignum baik baik saja. Akan tetapi, saya harus menulis sendiri fungsinya. Untungnya, tidak terlalu sulit (atau bertele-tele) untuk menulis. Sayangnya, konversi basis ini berarti bahwa input biasanya tidak dapat dibaca. Ada juga masalah dengan nol terkemuka;
Perlu dicatat bahwa kita tidak dapat memiliki lebih dari p blok dalam output (jika tidak, indeks dua blok akan menjadi sama, namun mungkin perlu menghasilkan output yang berbeda dari polinomial). Ini hanya terjadi ketika input sangat besar. Program ini memecahkan masalah dengan cara yang sangat sederhana: meningkatkan radiasi (yang membuat blok lebih besar dan p jauh lebih besar, artinya kita dapat memasukkan lebih banyak data, dan yang jelas mengarah pada hasil yang benar).
Satu hal lagi yang layak untuk dibuat adalah bahwa kita menyandikan string nol ke dirinya sendiri, karena program seperti yang tertulis akan menabraknya. Ini juga jelas pengkodean terbaik, dan bekerja tidak peduli apa parameter radiasi.
Peningkatan potensial
Inefisiensi asimtotik utama dalam program ini adalah berkaitan dengan penggunaan modulo-prime sebagai bidang hingga yang dimaksud. Hingga bidang ukuran 2 n ada (yang persis apa yang kita inginkan di sini, karena ukuran muatan blok 'secara alami kekuatan 128). Sayangnya, mereka agak lebih kompleks daripada konstruksi modulo sederhana, artinya Math :: ModInt tidak akan memotongnya (dan saya tidak bisa menemukan perpustakaan di CPAN untuk menangani bidang terbatas ukuran non-prime); Saya harus menulis seluruh kelas dengan aritmatika kelebihan muatan untuk Matematika :: Polinomial untuk dapat menanganinya, dan pada saat itu biaya byte mungkin berpotensi melebihi kerugian (sangat kecil) dari penggunaan, misalnya, 16381 daripada 16384.
Keuntungan lain menggunakan ukuran power-of-2 adalah bahwa konversi basis akan menjadi lebih mudah. Namun, dalam kedua kasus tersebut, metode yang lebih baik untuk merepresentasikan panjang input akan bermanfaat; metode "tambahkan 1 dalam kasus ganda" sederhana namun boros. Konversi basis bijektif adalah salah satu pendekatan yang masuk akal di sini (idenya adalah Anda memiliki basis sebagai digit, dan 0 sebagai bukan digit, sehingga setiap angka berhubungan dengan satu string).
Meskipun kinerja asimptotik dari pengkodean ini sangat baik (misalnya untuk input dengan panjang 99 dan parameter radiasi 3, pengkodean selalu panjang 128 byte, daripada ~ 400 byte yang didapat dari pendekatan berbasis pengulangan), kinerjanya kurang baik pada input pendek; panjang encoding selalu setidaknya kuadrat dari (parameter radiasi + 1). Jadi untuk input yang sangat pendek (panjang 1 hingga 8) pada radiasi 9, panjang output tetap 100. (Pada panjang 9, panjang output kadang-kadang 100 dan kadang-kadang 110.) Pendekatan berbasis pengulangan jelas mengalahkan penghapusan ini. pendekatan berbasis-kode pada input yang sangat kecil; mungkin perlu diubah di antara beberapa algoritma berdasarkan ukuran input.
Akhirnya, itu tidak benar-benar muncul dalam penilaian, tetapi dengan parameter radiasi yang sangat tinggi, menggunakan sedikit setiap byte (⅛ dari ukuran output) untuk membatasi blok boros; akan lebih murah untuk menggunakan pembatas antar blok saja. Merekonstruksi blok dari pembatas agak lebih sulit daripada dengan pendekatan bolak-MSB, tapi saya percaya itu mungkin, setidaknya jika datanya cukup panjang (dengan data pendek, bisa sulit untuk menyimpulkan parameter radiasi dari output) . Itu akan menjadi sesuatu untuk dilihat jika bertujuan untuk pendekatan ideal asimptotik terlepas dari parameter.
(Dan tentu saja, mungkin ada algoritma yang sama sekali berbeda yang menghasilkan hasil yang lebih baik daripada yang ini!)
sumber