Perl, 2, 70525 + 326508 = 467558
Predictor
$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}
Bu programı çalıştırmak için, burada isimlendirilmesi gereken dosyaya ihtiyacınız varB
. (Bu dosya adını yukarıdaki karakterin ikinci örneğinde değiştirebilirsiniz B
.) Bu dosyanın nasıl oluşturulacağı hakkında aşağıya bakın.
Program, esas olarak user2699 tarafından bu cevabında olduğu gibi Markov modellerinin bir kombinasyonunu kullanıyor , ancak birkaç küçük değişiklikle. Bu, bir sonraki karakter için bir dağıtım üretir . Bir hata kabul edip etmemeye veya B
kodlama ipuçlarına depolama alanı harcamayı (ve eğer öyleyse) karar vermek için bilgi teorisini kullanırız . Kesirli bitleri modelden en iyi şekilde saklamak için aritmetik kodlama kullanıyoruz .
Program 582 bayt uzunluğunda (gereksiz bir son satırsonu dahil) ve ikili dosya B
69942 bayt uzunluğundadır, bu nedenle birden fazla dosya puanlama kuralları uyarıncaL
582 + 69942 + 1 = 70525 puan alırız.
Program neredeyse kesinlikle 64-bit (küçük-endian?) Mimarisini gerektirir. m5.large
Amazon EC2'deki bir örnek üzerinde çalışmak yaklaşık 2,5 dakika sürer .
Test kodu
# 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 kablo demeti, gönderimin dosyada olduğunu varsayar submission.pl
, ancak bu ikinci satırda kolayca değiştirilebilir.
Metin karşılaştırması
"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
Bu örnek ( başka bir cevapta seçilen ) metinde geç kalır, bu yüzden model bu noktada oldukça gelişmiştir. Modelin, doğrudan karakterleri tahmin etmesine yardımcı olan 70 kilobayt "ipucu" ile güçlendirildiğini unutmayın; basitçe yukarıdaki kısa kod parçacığı tarafından yönlendirilmez.
İpuçları üreten
Aşağıdaki program yukarıdaki tam gönderme kodunu kabul eder (standart girişte) ve B
yukarıdaki tam dosyayı oluşturur (standart çıktıda):
@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],'}}'
Benzer hesaplamalar yaptığından, gönderim süresinin dolması yaklaşık olarak zaman alır.
açıklama
Bu bölümde, bu çözümün ne yaptığını “evde deneyebileceğiniz” bir ayrıntıda kendiniz açıklamaya çalışacağız. Bu cevabı diğerlerinden ayıran ana teknik, "geri sarma" mekanizması olarak birkaç bölümdür, ancak oraya varmadan önce, temelleri oluşturmamız gerekir.
model
Çözümün temel bileşeni bir dil modelidir. Amaçlarımız için, bir model bir miktar İngilizce metin alan ve bir sonraki karakterde olasılık dağılımı veren bir şeydir . Modeli kullandığımızda, İngilizce metin Moby Dick'in bazı (doğru) ön ekleri olacak. İstenilen çıktının bir dağıtım olduğunu ve en olası karakter için yalnızca tek bir tahmin olmadığını lütfen unutmayın .
Bizim durumumuzda, bu cevaptaki modeli esasen user2699 tarafından kullanıyoruz . Bu modeli Anders Kaseorg'ın en yüksek puan alan cevabından (kendi yorumumuz dışında) kullanmadık , çünkü tek bir en iyi tahminden ziyade bir dağıtım çıkaramadık. Teoride, bu cevap ağırlıklı geometrik bir ortalamayı hesaplar, ancak bunu kelimenin tam anlamıyla yorumladığımızda biraz kötü sonuçlar aldık. Bir modeli başka bir cevaptan "çaldık" çünkü "gizli sosumuz" model değil, genel yaklaşımdır. Birisi "daha iyi" bir modele sahipse, geri kalan tekniklerimizi kullanarak daha iyi sonuçlar alabilmelidir.
Bir açıklama olarak, Lempel-Ziv gibi çoğu sıkıştırma yönteminin bu şekilde bir "dil modeli" olduğu görülebilir, ancak birinin biraz kısaltması gerekebilir. (Özellikle bir Burrows-Wheeler dönüşümü yapan bir şey için çok zor!) Ayrıca, kullanıcı modelinin model 2999'daki Markov modelinin bir değişikliği olduğunu unutmayın; Aslında bu zorluk için başka hiçbir şey rekabet edemez veya genel olarak metni örnekleyebilir.
Genel mimari
Anlamak amacıyla, genel mimariyi birkaç parçaya ayırmak güzel. En üst düzey bakış açısına göre, bir miktar devlet yönetim kodu olması gerekir. Bu özellikle ilginç değil, ancak bütünlük için programın bir sonraki tahminde bulunacağı her noktada, Moby Dick'in doğru bir öneki olduğunu vurgulamak istiyoruz. Geçmiş yanlış tahminlerimizi hiçbir şekilde kullanmayız. Verimlilik uğruna, dil modeli, ilk (N + 1) karakter için durumunu hesaplamak için durumunu ilk N karakterinden yeniden kullanabilir, ancak ilke olarak, her çağrıldığında sıfırdan bir şeyi yeniden hesaplayabilir.
Programın bu temel "sürücüsünü" bir kenara bırakıp bir sonraki karakteri tahmin eden kısmın içine bakalım. Kavramsal olarak üç bölümü ayırmaya yardımcı olur: dil modeli (yukarıda tartışılmıştır), bir "ipuçları" dosyası ve bir "tercüman". Her adımda, tercüman dil modelinden bir sonraki karakter için bir dağıtım isteyecektir ve muhtemelen ipuçları dosyasından bazı bilgileri okuyacaktır. Sonra bu parçaları bir tahminde birleştirir. Tam olarak ipuçları dosyasında hangi bilgilerin kullanıldığı ve nasıl kullanıldığı daha sonra açıklanacak, ancak şimdilik bu bölümleri zihinsel olarak ayırmaya yardımcı olacak. Uygulama bilge, ipuçları dosyasının tam anlamıyla ayrı (ikili) bir dosya olduğunu ancak programın içinde bir dize veya bir şey olabileceğini unutmayın. Bir yaklaşım olarak,
Bu cevapta bzip2 gibi standart bir sıkıştırma yöntemi kullanılıyorsa , "ipuçları" dosyası sıkıştırılmış dosyaya karşılık gelir. "Tercüman", dekompresere karşılık gelirken, "dil modeli" biraz yukarıda bulunur (yukarıda belirtildiği gibi).
Neden bir ipucu dosyası kullanıyorsunuz?
Daha fazla analiz etmek için basit bir örnek seçelim. Metnin, N
her karakterin (bağımsız olarak) E
olasılıkla bir buçuktan T
biraz az, olasılıkla aynı bir buçuktan biraz daha az A
olasılıkla 1/1000 =% 0.1 olasılıkla benzer olduğu bir karakterle olduğu, uzun ve iyi bir karaktere sahip olduğunu varsayalım . Başka karakterlerin mümkün olmadığını varsayalım; Her durumda, A
daha önce görünmeyen bir karakterin mavi dışında olması durumuyla oldukça benzer.
L 0 rejimde çalıştırılan (Bu soruya diğer cevapların çoğu, ama hepsi değil yapmak gibi) varsa, birini seçmek çok tercüman için daha iyi bir strateji var E
ve T
. Ortalama olarak, karakterlerin yaklaşık yarısını doğru alacak. Yani E ≈ N / 2 ve skor ≈ N / 2 de. Bununla birlikte, eğer bir sıkıştırma stratejisi kullanırsak, o zaman karakter başına bir bitden biraz daha fazla sıkıştırabiliriz. L, bayt cinsinden sayıldığı için, önceki stratejinin iki katı kadar L and N / 8 alıyoruz ve böylece ≈ N / 4 puan alıyoruz.
Bu model için karakter başına bir bitden biraz bu oranın elde edilmesi biraz önemsiz olmakla birlikte, bir yöntem aritmetik kodlamadır.
Aritmetik kodlama
Yaygın olarak bilindiği gibi bir kodlama , bit / bayt kullanarak bazı verileri temsil etmenin bir yoludur. Örneğin, ASCII İngilizce metin ve ilgili karakterlerin 7 bit / karakter kodlamasıdır ve söz konusu orijinal Moby Dick dosyasının kodlamasıdır. Bazı harfler diğerlerinden daha yaygınsa, ASCII gibi sabit genişlikli bir kodlama uygun değildir. Böyle bir durumda, birçok kişi Huffman kodlaması için ulaşır . Karakter başına tam sayı bit içeren sabit (öneksiz) bir kod istiyorsanız, bu idealdir.
Ancak, aritmetik kodlama daha iyidir. Kabaca konuşursak, bilgiyi kodlamak için "kesirli" bitleri kullanabilir. Aritmetik kodlamanın çevrimiçi olarak kullanılabilecek birçok kılavuzu vardır. Çevrimiçi olarak sunulan diğer kaynaklar nedeniyle ayrıntıları burada atlayacağız (özellikle programlama açısından biraz zor olabilen pratik uygulama), ancak birileri şikayet ederse, belki bu bölüm daha da ortaya çıkabilir.
Bir kişi aslında bilinen bir dil modeli tarafından oluşturulan bir metne sahipse, aritmetik kodlama o modelden esasen optimum bir metin kodlaması sağlar. Bir anlamda bu, bu model için sıkıştırma problemini "çözüyor". (Bu nedenle pratikte, asıl mesele, modelin bilinmemesi ve bazı modellerin insan metnini modellemede diğerlerinden daha iyi olmasıdır.) Bu yarışmada hata yapmasına izin verilmediyse, o zaman önceki bölümün dilinde Bu zorluğa bir çözüm üretmenin bir yolu, dil modelinden bir "ipuçları" dosyası üretmek için bir aritmetik kodlayıcı kullanmak ve ardından "tercüman" olarak bir aritmetik kod çözücü kullanmak olabilir.
Bu esasen en uygun kodlamada, p olasılıklı bir karakter için -log_2 (p) bit harcıyoruz ve kodlamanın genel bit hızı Shannon entropisidir . Bu, 1 / 2'ye yakın olasılıkla bir karakterin kodlanması yaklaşık bir bit sürdüğü, 1/10 olasılıkla 10 bit aldığı anlamına gelir (çünkü 2 ^ 10 kabaca 1000'dir).
Ancak bu zorluk için puanlama ölçütü, en iyi strateji olarak sıkıştırmayı önlemek için iyi seçildi. Daha kısa bir ipucu dosyası almak için bir hata olarak bazı hataları yapmanın bir yolunu bulmalıyız. Örneğin, birinin deneyebileceği bir strateji basit bir dallanma stratejisidir: genellikle yapabildiğimizde aritmetik kodlamayı kullanmaya çalışırız, ancak modelden olasılık dağılımı "kötü" ise, bir şekilde en muhtemel karakteri tahmin ediyoruz t Kodlamayı dene.
Neden hata yapmalı?
Neden "kasıtlı olarak" hata yapmak isteyebileceğimizi motive etmek için örneği daha önce analiz edelim. Doğru karakteri kodlamak için aritmetik kodlamayı kullanırsak, bir E
veya için kabaca bir bit T
, ancak bir durumda yaklaşık on bit harcarız A
.
Genel olarak, bu, üç olasılık olmasına rağmen karakter başına biraz fazla para harcayan, oldukça iyi bir kodlama; Temel olarak, A
oldukça düşük bir ihtimaldir ve karşılık gelen on parçayı çok sık harcamak zorunda kalmayız. Ancak, bunun yerine bir hata yapabilirsek iyi olmaz mıydı A
? Ne de olsa, sorunun ölçütü 1 bayt = 8 bit uzunluğunu 2 hataya eşdeğer kabul eder; bu nedenle, bir karaktere 8/2 = 4 bitten fazla harcamak yerine bir hatayı tercih etmek gerekiyor gibi görünüyor. Bir hatayı kurtarmak için bir bayttan daha fazlasını harcamak, kesinlikle yetersiz sesler!
"Geri sarma" mekanizması
Bu bölüm, uzun vadede hiçbir bedel ödemeden yanlış tahminleri ele almanın bir yolu olan bu çözümün ana akıllı yönünü açıklamaktadır.
Analiz ettiğimiz basit örnek için, geri sarma mekanizması özellikle basittir. Tercüman, ipucu dosyasından bir bit okur. 0 ise, tahmin ediyor E
. 1 ise, tahmin ediyor T
. Bir dahaki sefere çağrıldığında doğru karakterin ne olduğunu görür. Eğer ipucu dosyası iyi ayarlanmışsa, bir E
veya durumunda T
tercümanın doğru tahmin etmesini sağlayabiliriz . Peki ya A
? Geri sarma mekanizması fikri basitçe etmektir kodlamayın A
hiç . Daha doğrusu, tercüman daha sonra doğru karakterin bir karakter olduğunu öğrenirse A
, mecazi olarak " bandı geri alır": daha önce okuduğu biti döndürür. Okuduğu bit kodlamak niyetinde E
veyaT
, fakat şimdi değil; daha sonra kullanılacak. Bu basit örnekte, bu temelde aynı karakteri ( veya ) doğru olana kadar tahmin etmeye devam ettiği anlamına gelir ; sonra başka bir bit okur ve devam ediyor.E
T
Bu ipuçları dosyasının kodlaması çok basittir: tamamen yok sayarak tüm s'leri E
0 bit'e ve T
1 bit'e dönüştürün A
. Önceki bölümün sonundaki analizle, bu şema bazı hatalar yapar, ancak herhangi bir A
s kodlamadan genel puanı düşürür . İpuçları uzunluğu yanı dosyası üzerinde her ötürü tam olarak bir bit kullanarak sonuna çünkü daha küçük bir etkisi olarak, aslında, kaydeder E
ve T
yerine biraz biraz daha fazla,,.
Küçük bir teorem
Ne zaman hata yapacağınıza nasıl karar veririz? Diyelim ki modelimiz bize bir sonraki karakter için bir olasılık dağılımı P veriyor. Olası karakterleri iki sınıfa ayıracağız: kodlanmış ve kodlanmamış . Doğru karakter kodlanmadıysa, hiçbir ücret ödemeden bir hatayı kabul etmek için "geri sarma" mekanizmasını kullanacağız. Doğru karakter kodlanmışsa, aritmetik kodlamayı kullanarak kodlamak için başka bir Q dağıtımını kullanacağız.
Fakat hangi dağıtım Q'yu seçmeliyiz? Kodlanmış karakterlerin hepsinin kodlanmış karakterlerden daha yüksek bir olasılık (P cinsinden) olması gerektiğini görmek zor değil. Ayrıca, Q dağılımı sadece kodlanmış karakterleri içermelidir; Sonuçta, diğerlerini kodlamıyoruz, bu yüzden onlara "entropi" harcamamalıyız. Olasılık dağılımının kodlanmış karakterlerde P ile orantılı olması gerektiğini görmek biraz zor. Bu gözlemleri bir araya getirmek, en muhtemel karakterleri kodlamamız gerektiği, ancak daha az muhtemel karakterleri kodlamamız gerektiği ve Q'nun kodlu karakterlerde yalnızca P ölçeklendirildiği anlamına gelir.
Dahası, kodlama karakterleri için hangisinin “kesme” yi seçmesi gerektiğine dair serin bir teorem olduğu ortaya çıkmaktadır: karakterleri, birleştirilmiş diğer kodlanmış karakterlerden en az 1 / 5.393 olduğu sürece kodlamalısınız. Bu 5.393
, yukarıdaki programın sonuna yakın görünüşte rasgele görünen sabitin görünümünü "açıklar" . 1 / 5.393 ≈ 0.18542 sayısı, -p log (16) - p log p + (1 + p) log (1 + p) = 0 denkleminin çözümdür .
Belki de bu prosedürü kodda yazmak makul bir fikirdir. Bu pasajı C ++ dilindedir:
// 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;
}
Hepsini bir araya koy
Önceki bölüm maalesef biraz teknik, ancak diğer tüm parçaları bir araya getirirsek yapı aşağıdaki gibidir. Program, verilen doğru karakterden sonra bir sonraki karakteri tahmin etmesi istendiğinde:
- Moby Dick'in bilinen doğru önekine doğru karakteri ekleyin.
- Metnin (Markov) modelini güncelleyin.
- Gizli sos : Önceki tahminim yanlış ise, geri sarma önceki tahmin önceki konumuna aritmetik dekoderi durumunu!
- Markov modelinden sonraki karakter için bir olasılık dağılımı P öngörmesini isteyin.
- Önceki bölümdeki alt yordamı kullanarak P'yi Q'ya dönüştürün.
- Aritmetik kod çözücünün Q dağılımına göre, ipucu dosyasının geri kalanından bir karakteri kodlamasını isteyin.
- Elde edilen karakteri tahmin et.
İpuçları dosyasının kodlaması benzer şekilde çalışır. Bu durumda, program sonraki karakterin doğru olduğunu bilir. Eğer kodlanması gereken bir karakter ise, elbette biri üzerinde aritmetik kodlayıcı kullanmalıdır; fakat eğer kodlanmamış bir karakter ise, sadece aritmetik kodlayıcının durumunu güncellemez.
Olasılık dağılımları, entropi, sıkıştırma ve aritmetik kodlama gibi bilgi-teorik arka planı anlıyorsanız ancak bu yazıyı anlamaya çalıştınız ve anlayamadılar (teoremin doğru olması haricinde), bize bildirin ve işleri düzeltmeye çalışabiliriz. Okuduğunuz için teşekkürler!