Perl, 2 · 70525 + 326508 = 467558
Prediktor
$m=($u=1<<32)-1;open B,B;@e=unpack"C*",join"",<B>;$e=2903392593;sub u{int($_[0]+($_[1]-$_[0])*pop)}sub o{$m&(pop()<<8)+pop}sub g{($h,%m,@b,$s,$E)=@_;if($d eq$h){($l,$u)=(u($l,$u,$L),u($l,$u,$U));$u=o(256,$u-1),$l=o($l),$e=o(shift@e,$e)until($l^($u-1))>>24}$M{"@c"}{$h}++-++$C{"@c"}-pop@c for@p=($h,@c=@p);@p=@p[0..19]if@p>20;@c=@p;for(@p,$L=0){$c="@c";last if" "ne pop@c and@c<2 and$E>99;$m{$_}+=$M{$c}{$_}/$C{$c}for sort keys%{$M{$c}};$E+=$C{$c}}$s>5.393*$m{$_}or($s+=$m{$_},push@b,$_)for sort{$m{$b}<=>$m{$a}}sort keys%m;$e>=u($l,$u,$U=$L+$m{$_}/$s)?$L=$U:return$d=$_ for sort@b}
Untuk menjalankan program ini, Anda perlu file ini di sini , yang harus dinamai B
. (Anda dapat mengubah nama file ini pada karakter karakter kedua di B
atas.) Lihat di bawah untuk cara membuat file ini.
Program ini menggunakan kombinasi model Markov pada dasarnya seperti dalam jawaban ini oleh user2699 , tetapi dengan beberapa modifikasi kecil. Ini menghasilkan distribusi untuk karakter selanjutnya. Kami menggunakan teori informasi untuk memutuskan apakah akan menerima kesalahan atau menghabiskan sedikit penyimpanan dalam B
petunjuk penyandian (dan jika demikian, bagaimana). Kami menggunakan kode aritmatika untuk menyimpan bit fraksional secara optimal dari model.
Program ini panjangnya 582 byte (termasuk baris akhir yang tidak perlu) dan panjang file biner B
adalah 6.9942 byte, jadi berdasarkan aturan untuk mencetak banyak file , kami mendapat skor L
582 + 69942 + 1 = 70525.
Program ini hampir pasti membutuhkan arsitektur 64-bit (little-endian?). Diperlukan waktu sekitar 2,5 menit untuk menjalankan m5.large
instance di Amazon EC2.
Kode uji
# Golfed submission
require "submission.pl";
use strict; use warnings; use autodie;
# Scoring length of multiple files adds 1 penalty
my $length = (-s "submission.pl") + (-s "B") + 1;
# Read input
open my $IN, "<", "whale2.txt";
my $input = do { local $/; <$IN> };
# Run test harness
my $errors = 0;
for my $i ( 0 .. length($input)-2 ) {
my $current = substr $input, $i, 1;
my $decoded = g( $current );
my $correct = substr $input, $i+1, 1;
my $error_here = 0 + ($correct ne $decoded);
$errors += $error_here;
}
# Output score
my $score = 2 * $length + $errors;
print <<EOF;
length $length
errors $errors
score $score
EOF
Test harness menganggap pengajuan ada dalam file submission.pl
, tetapi ini dapat dengan mudah diubah di baris kedua.
Perbandingan teks
"And did none of ye see it before?" cried Ahab, hailing the perched men all around him.\\"I saw him almost that same instant, sir, that Captain
"And wid note of te fee bt seaore cried Ahab, aasling the turshed aen inl atound him. \"' daw him wsoost thot some instant, wer, that Saptain
"And _id no_e of _e _ee _t _e_ore__ cried Ahab, _a_ling the __r_hed _en __l a_ound him._\"_ _aw him ___ost th_t s_me instant, __r, that _aptain
Ahab did, and I cried out," said Tashtego.\\"Not the same instant; not the same--no, the doubloon is mine, Fate reserved the doubloon for me. I
Ahab aid ind I woued tut, said tashtego, \"No, the same instant, tot the same -tow nhe woubloon ws mane. alte ieserved the seubloon ior te, I
Ahab _id_ _nd I ___ed _ut,_ said _ashtego__\"No_ the same instant_ _ot the same_-_o_ _he _oubloon _s m_ne_ __te _eserved the __ubloon _or _e_ I
only; none of ye could have raised the White Whale first. There she blows!--there she blows!--there she blows! There again!--there again!" he cr
gnly towe of ye sould have tersed the shite Whale aisst Ihere ihe blows! -there she blows! -there she blows! Ahere arains -mhere again! ce cr
_nly_ _o_e of ye _ould have ___sed the _hite Whale _i_st_ _here _he blows!_-there she blows!_-there she blows! _here a_ain__-_here again!_ _e cr
Sampel ini (dipilih dalam jawaban lain ) muncul agak terlambat dalam teks, sehingga model ini cukup berkembang pada titik ini. Ingat bahwa model ini ditambah oleh 70 kilobyte "petunjuk" yang secara langsung membantunya menebak karakter; itu tidak didorong hanya oleh potongan pendek kode di atas.
Menghasilkan petunjuk
Program berikut menerima kode pengiriman yang tepat di atas (pada input standar) dan menghasilkan B
file yang tepat di atas (pada output standar):
@S=split"",join"",<>;eval join"",@S[0..15,64..122],'open W,"whale2.txt";($n,@W)=split"",join"",<W>;for$X(0..@W){($h,$n,%m,@b,$s,$E)=($n,$W[$X]);',@S[256..338],'U=0)',@S[343..522],'for(sort@b){$U=($L=$U)+$m{$_}/$s;if($_ eq$n)',@S[160..195],'X<128||print(pack C,$l>>24),',@S[195..217,235..255],'}}'
Diperlukan waktu kira-kira untuk berjalan seperti pengiriman, karena melakukan perhitungan yang sama.
Penjelasan
Di bagian ini, kami akan mencoba menjelaskan apa yang dilakukan solusi ini dengan cukup detail sehingga Anda dapat "mencobanya di rumah" sendiri. Teknik utama yang membedakan jawaban ini dari yang lain adalah beberapa bagian di bawah sebagai mekanisme "mundur", tetapi sebelum kita sampai di sana, kita perlu mengatur dasar-dasarnya.
Model
Bahan dasar dari solusi adalah model bahasa. Untuk tujuan kami, model adalah sesuatu yang mengambil sejumlah teks bahasa Inggris dan mengembalikan distribusi probabilitas pada karakter berikutnya. Ketika kami menggunakan model, teks bahasa Inggris akan menjadi beberapa awalan (benar) dari Moby Dick. Harap dicatat bahwa output yang diinginkan adalah distribusi , dan bukan hanya tebakan tunggal untuk karakter yang paling mungkin.
Dalam kasus kami, kami pada dasarnya menggunakan model dalam jawaban ini oleh user2699 . Kami tidak menggunakan model dari jawaban dengan skor tertinggi (selain dari milik kami) oleh Anders Kaseorg justru karena kami tidak dapat mengekstraksi distribusi daripada hanya satu tebakan terbaik. Secara teori, jawaban itu menghitung rata-rata geometris tertimbang, tetapi kami mendapat hasil yang sedikit buruk ketika kami menafsirkannya secara harfiah. Kami "mencuri" model dari jawaban lain karena "saus rahasia" kami bukanlah model melainkan pendekatan keseluruhan. Jika seseorang memiliki model "lebih baik", maka mereka harus bisa mendapatkan hasil yang lebih baik menggunakan sisa teknik kami.
Sebagai komentar, sebagian besar metode kompresi seperti Lempel-Ziv dapat dilihat sebagai "model bahasa" dengan cara ini, meskipun orang mungkin harus sedikit menyipit. (Ini sangat sulit untuk sesuatu yang melakukan transformasi Burrows-Wheeler!) Juga, perhatikan bahwa model oleh user2699 adalah modifikasi dari model Markov; intinya tidak ada lagi yang kompetitif untuk tantangan ini atau bahkan pemodelan teks secara umum.
Arsitektur keseluruhan
Untuk keperluan pemahaman, senang memecah arsitektur keseluruhan menjadi beberapa bagian. Dari perspektif tingkat tertinggi, perlu ada sedikit kode manajemen negara. Ini tidak terlalu menarik, tetapi untuk kelengkapan kami ingin menekankan bahwa pada setiap titik program diminta untuk tebakan berikutnya, itu telah tersedia untuknya awalan yang benar dari Moby Dick. Kami tidak menggunakan tebakan salah kami di masa lalu dengan cara apa pun. Demi efisiensi, model bahasa mungkin dapat menggunakan kembali statusnya dari karakter N pertama untuk menghitung statusnya untuk karakter pertama (N + 1), tetapi pada prinsipnya, ia dapat menghitung ulang hal-hal dari awal setiap kali dipanggil.
Mari kita kesampingkan "driver" dasar program ini dan mengintip bagian dalam yang menebak karakter selanjutnya. Ini membantu secara konseptual untuk memisahkan tiga bagian: model bahasa (dibahas di atas), file "petunjuk", dan "penerjemah". Pada setiap langkah, penerjemah akan meminta model bahasa untuk distribusi untuk karakter berikutnya dan mungkin membaca beberapa informasi dari file petunjuk. Maka akan menggabungkan bagian-bagian ini menjadi tebakan. Tepatnya informasi apa yang ada dalam file petunjuk serta bagaimana penggunaannya akan dijelaskan nanti, tetapi untuk saat ini membantu memisahkan bagian-bagian ini secara mental. Perhatikan bahwa implementasi-bijaksana, file petunjuk secara harfiah file terpisah (biner) tetapi bisa berupa string atau sesuatu yang disimpan di dalam program. Sebagai perkiraan,
Jika seseorang menggunakan metode kompresi standar seperti bzip2 seperti dalam jawaban ini , file "hints" sesuai dengan file yang dikompresi. "Interpreter" sesuai dengan decompressor, sedangkan "model bahasa" agak implisit (seperti yang disebutkan di atas).
Mengapa menggunakan file petunjuk?
Mari kita ambil contoh sederhana untuk dianalisis lebih lanjut. Misalkan teks N
panjang karakter dan didekati dengan baik oleh model di mana setiap karakter (independen) huruf E
dengan probabilitas sedikit kurang dari setengah, T
sama dengan probabilitas sedikit kurang dari setengah, dan A
dengan probabilitas 1/1000 = 0,1%. Mari kita asumsikan tidak ada karakter lain yang mungkin; dalam kasus apa pun, A
ini cukup mirip dengan kasus karakter yang sebelumnya tidak terlihat tiba-tiba.
Jika kita beroperasi dalam rezim L 0 (seperti kebanyakan, tetapi tidak semua, dari jawaban lain untuk pertanyaan ini), tidak ada strategi yang lebih baik untuk penerjemah daripada memilih salah satu dari E
dan T
. Rata-rata, itu akan mendapatkan sekitar setengah dari karakter yang benar. Jadi E ≈ N / 2 dan skor ≈ N / 2 juga. Namun, jika kita menggunakan strategi kompresi, maka kita dapat memampatkan sedikit lebih dari satu bit per karakter. Karena L dihitung dalam byte, kita mendapatkan L ≈ N / 8 dan dengan demikian skor ≈ N / 4, dua kali lebih baik dari strategi sebelumnya.
Mencapai tingkat ini sedikit lebih dari satu bit per karakter untuk model ini sedikit nontrivial, tetapi satu metode adalah pengkodean aritmatika.
Pengkodean aritmatika
Seperti yang umum diketahui, pengkodean adalah cara mewakili beberapa data menggunakan bit / byte. Sebagai contoh, ASCII adalah pengkodean 7 bit / karakter teks bahasa Inggris dan karakter terkait, dan itu adalah pengkodean file Moby Dick asli yang sedang dipertimbangkan. Jika beberapa huruf lebih umum daripada yang lain, maka penyandian lebar-tetap seperti ASCII tidak optimal. Dalam situasi seperti itu, banyak orang meraih kode Huffman . Ini optimal jika Anda menginginkan kode tetap (bebas awalan) dengan jumlah bit integer per karakter.
Namun, pengkodean aritmatika bahkan lebih baik. Secara kasar, ia dapat menggunakan bit "fraksional" untuk menyandikan informasi. Ada banyak panduan untuk coding aritmatika yang tersedia online. Kami akan melewatkan detailnya di sini (terutama dari implementasi praktis, yang bisa sedikit rumit dari perspektif pemrograman) karena sumber daya lain yang tersedia secara online, tetapi jika seseorang mengeluh, mungkin bagian ini dapat diperlihatkan lebih lanjut.
Jika seseorang memiliki teks yang sebenarnya dihasilkan oleh model bahasa yang dikenal, maka pengkodean aritmatika memberikan pengkodean teks yang pada dasarnya optimal dari model itu. Dalam beberapa hal, ini "memecahkan" masalah kompresi untuk model itu. (Dengan demikian dalam praktiknya, masalah utama adalah bahwa model tidak diketahui, dan beberapa model lebih baik daripada yang lain dalam pemodelan teks manusia.) Jika tidak diizinkan untuk membuat kesalahan dalam kontes ini, maka dalam bahasa bagian sebelumnya , salah satu cara untuk menghasilkan solusi untuk tantangan ini adalah dengan menggunakan encoder aritmatika untuk menghasilkan file "petunjuk" dari model bahasa dan kemudian menggunakan dekoder aritmatika sebagai "penerjemah".
Dalam pengkodean yang pada dasarnya-optimal ini, kita akhirnya menghabiskan -log_2 (p) bit untuk karakter dengan probabilitas p, dan laju bit keseluruhan dari pengkodean adalah entropi Shannon . Ini berarti bahwa karakter dengan probabilitas dekat 1/2 membutuhkan waktu sekitar satu bit untuk mengkodekan, sedangkan karakter dengan probabilitas 1/1000 membutuhkan sekitar 10 bit (karena 2 ^ 10 kira-kira 1000).
Tetapi metrik penilaian untuk tantangan ini dipilih dengan baik untuk menghindari kompresi sebagai strategi optimal. Kita harus mencari cara untuk membuat beberapa kesalahan sebagai tradeoff untuk mendapatkan file petunjuk yang lebih pendek. Sebagai contoh, salah satu strategi yang mungkin dicoba adalah strategi percabangan sederhana: kami biasanya mencoba menggunakan pengkodean aritmatika ketika kami bisa, tetapi jika distribusi probabilitas dari model itu "buruk" dalam beberapa cara kami hanya menebak karakter yang paling mungkin dan tidak t coba menyandikannya.
Mengapa melakukan kesalahan?
Mari kita menganalisis contoh dari sebelumnya untuk memotivasi mengapa kita mungkin ingin membuat kesalahan "dengan sengaja". Jika kita menggunakan pengkodean aritmatika untuk menyandikan karakter yang benar, kita akan menghabiskan kira-kira satu bit dalam kasus E
atau T
, tetapi sekitar sepuluh bit dalam kasus suatu A
.
Secara keseluruhan, ini adalah pengkodean yang cukup bagus, menghabiskan sedikit lebih sedikit per karakter meskipun ada tiga kemungkinan; pada dasarnya, A
ini sangat tidak mungkin dan kami tidak menghabiskan sepuluh bit yang sesuai terlalu sering. Namun, bukankah lebih baik jika kita bisa membuat kesalahan sebagai gantinya A
? Bagaimanapun, metrik untuk masalah ini menganggap 1 byte = 8 bit panjangnya setara dengan 2 kesalahan; jadi sepertinya orang harus lebih memilih kesalahan daripada menghabiskan lebih dari 8/2 = 4 bit pada sebuah karakter. Menghabiskan lebih dari satu byte untuk menyimpan satu kesalahan pasti terdengar kurang optimal!
Mekanisme "mundur"
Bagian ini menjelaskan aspek pintar utama dari solusi ini, yang merupakan cara untuk menangani tebakan yang salah tanpa biaya panjang.
Untuk contoh sederhana yang telah kami analisis, mekanisme mundur sangat mudah. Juru bahasa membaca sedikit dari file petunjuk. Jika 0, itu menebak E
. Jika itu adalah 1, ia menebak T
. Kali berikutnya dipanggil, ia melihat apa karakter yang benar. Jika file petunjuk diatur dengan baik, kami dapat memastikan bahwa dalam kasus seorang E
atau T
, penerjemah menebak dengan benar. Tapi bagaimana dengan itu A
? Gagasan mekanisme mundur adalah tidak kode A
sama sekali . Lebih tepatnya, jika penerjemah kemudian mengetahui bahwa karakter yang benar adalah A
, maka secara metaforis " memutar ulang kaset": ia mengembalikan bit yang dibacanya sebelumnya. Bit yang dibaca memang bermaksud untuk kode E
atauT
, tapi tidak sekarang; itu akan digunakan nanti. Dalam contoh sederhana ini, ini pada dasarnya berarti bahwa ia terus menebak karakter yang sama ( E
atau T
) sampai benar; kemudian membaca sedikit lagi dan terus berjalan.
Pengkodean untuk file petunjuk ini sangat sederhana: ubah semua E
s menjadi 0 bit dan T
s menjadi 1 bit, semuanya mengabaikan A
sepenuhnya s. Dengan analisis di akhir bagian sebelumnya, skema ini membuat beberapa kesalahan tetapi mengurangi skor secara keseluruhan dengan tidak menyandikan salah satu dari A
s. Sebagai efek yang lebih kecil, itu sebenarnya menghemat panjang file petunjuk juga, karena kita akhirnya menggunakan tepat satu bit untuk masing-masing E
dan T
, alih-alih sedikit lebih dari sedikit.
Teorema kecil
Bagaimana kita memutuskan kapan harus membuat kesalahan? Misalkan model kita memberi kita distribusi probabilitas P untuk karakter berikutnya. Kami akan memisahkan karakter yang mungkin menjadi dua kelas: kode dan bukan kode . Jika karakter yang benar tidak dikodekan, maka kita akan berakhir menggunakan mekanisme "mundur" untuk menerima kesalahan tanpa biaya panjang. Jika karakter yang benar dikodekan, maka kita akan menggunakan beberapa distribusi Q lainnya untuk menyandikannya menggunakan pengkodean aritmatika.
Tetapi distribusi Q apa ​​yang harus kita pilih? Tidak terlalu sulit untuk melihat bahwa karakter yang dikode semuanya harus memiliki probabilitas yang lebih tinggi (dalam P) daripada karakter yang tidak diberi kode. Juga, distribusi Q seharusnya hanya menyertakan karakter kode; lagipula, kita tidak mengkode yang lain, jadi kita seharusnya tidak "menghabiskan" entropi untuk mereka. Agak sulit untuk melihat bahwa distribusi probabilitas Q harus proporsional dengan P pada karakter kode. Menyatukan pengamatan ini berarti bahwa kita harus mengkode karakter yang paling mungkin tetapi mungkin bukan karakter yang kurang mungkin, dan bahwa Q hanya P yang ditulis ulang pada karakter yang dikode.
Lebih jauh lagi ternyata ada teorema keren tentang "cutoff" mana yang harus dipilih untuk karakter pengkodean: Anda harus mengkode karakter selama paling tidak 1/5, 393 lebih besar dari kemungkinan kombinasi karakter berkode lainnya. Ini "menjelaskan" penampilan konstanta yang tampaknya acak 5.393
mendekati akhir program di atas. Angka 1 / 5.393 ≈ 0,18542 adalah solusi untuk persamaan -p log (16) - p log p + (1 + p) log (1 + p) = 0 .
Mungkin ide yang masuk akal untuk menuliskan prosedur ini dalam kode. Cuplikan ini ada di C ++:
// Assume the model is computed elsewhere.
unordered_map<char, double> model;
// Transform p to q
unordered_map<char, double> code;
priority_queue<pair<double,char>> pq;
for( char c : CHARS )
pq.push( make_pair(model[c], c) );
double s = 0, p;
while( 1 ) {
char c = pq.top().second;
pq.pop();
p = model[c];
if( s > 5.393*p )
break;
code[c] = p;
s += p;
}
for( auto& kv : code ) {
char c = kv.first;
code[c] /= s;
}
Menyatukan semuanya
Bagian sebelumnya sayangnya sedikit teknis, tetapi jika kita menyatukan semua bagian lainnya, strukturnya adalah sebagai berikut. Setiap kali program diminta untuk memprediksi karakter berikutnya setelah karakter yang diberikan benar:
- Tambahkan karakter yang benar ke awalan Moby Dick yang diketahui benar.
- Perbarui model (Markov) teks.
- The saus rahasia : Jika menebak sebelumnya tidak benar, mundur keadaan decoder aritmatika ke keadaan sebelum menebak sebelumnya!
- Minta model Markov untuk memprediksi distribusi probabilitas P untuk karakter selanjutnya.
- Ubah P menjadi Q menggunakan subrutin dari bagian sebelumnya.
- Minta decoder aritmatika untuk mendekode karakter dari sisa file petunjuk, sesuai dengan distribusi Q.
- Tebak karakter yang dihasilkan.
Pengkodean file petunjuk beroperasi dengan cara yang sama. Dalam hal ini, program tahu apa karakter selanjutnya yang benar. Jika itu adalah karakter yang harus dikodekan, maka tentu saja orang harus menggunakan encoder aritmatika di atasnya; tetapi jika itu bukan karakter kode, itu hanya tidak memperbarui keadaan aritmatika encoder.
Jika Anda memahami latar belakang teori-informasi seperti distribusi probabilitas, entropi, kompresi, dan pengkodean aritmatika tetapi mencoba dan gagal memahami posting ini (kecuali mengapa teorema itu benar), beri tahu kami dan kami dapat mencoba untuk menyelesaikannya. Terima kasih sudah membaca!