Perl、2・70525 + 326508 = 467558
予測子
$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}
このプログラムを実行するには、次のものが必要ここで、このファイルという名前を付ける必要があります、B
。(上記の文字の2番目のインスタンスでこのファイル名を変更できますB
。)このファイルの生成方法については、以下を参照してください。
プログラムは、user2699によるこの回答のように、基本的にマルコフモデルの組み合わせを使用しますが、いくつかの小さな変更を加えています。これにより、次のキャラクターの分布が作成されます。情報理論を使用して、エラーを受け入れるか、またはB
ヒントをエンコードする際にストレージのビットを使用するかを決定します(そうであれば、どのように)。算術コーディングを使用して、モデルの小数ビットを最適に保存します。
プログラムの長さは582バイト(不要な最終改行を含む)で、バイナリファイルのB
長さは69942バイトです。したがって、複数のファイルのスコアリングのルールではL
、582 + 69942 + 1 = 70525 とスコア付けされます。
このプログラムには、ほぼ確実に64ビット(リトルエンディアン?)アーキテクチャが必要です。m5.large
Amazon EC2のインスタンスで実行するには約2.5分かかります。
テストコード
# 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
テストハーネスは、提出がファイルsubmission.pl
にあると想定していますが、これは2行目で簡単に変更できます。
テキスト比較
"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
このサンプル(別の回答で選択)は、本文のかなり後の方にあるため、この時点でモデルはかなり発展しています。モデルには、文字を推測するのに直接役立つ70キロバイトの「ヒント」が追加されていることに注意してください。上記のコードの短いスニペットだけで駆動されるわけではありません。
ヒントを生成する
次のプログラムは、上記の正確な送信コード(標準入力)を受け入れ、上記の正確なB
ファイル(標準出力)を生成します。
@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],'}}'
同様の計算を実行するため、送信とほぼ同じ時間がかかります。
説明
このセクションでは、このソリューションが何をするのかを十分に詳しく説明し、「自宅で試してみる」ことができます。この答えを他の答えと区別する主なテクニックは、「巻き戻し」メカニズムとして数セクション下にありますが、そこに到達する前に、基本を設定する必要があります。
型
ソリューションの基本的な要素は言語モデルです。私たちの目的にとって、モデルとは、ある程度の英語のテキストを受け取り、次の文字の確率分布を返すものです。モデルを使用すると、英語のテキストはMoby Dickの(正しい)プレフィックスになります。必要な出力は分布であり、最も可能性の高いキャラクターの単なる推測ではないことに注意してください。
私たちの場合、この回答ではuser2699によるモデルを基本的に使用しています。Anders Kaseorgによる最高得点の回答(独自の回答以外)のモデルは、単一の最良の推測ではなく分布を抽出できなかったため、使用しませんでした。理論的には、その答えは重み付き幾何平均を計算しますが、文字通りに解釈するとやや悪い結果になりました。私たちの「秘密のソース」はモデルではなく、全体的なアプローチであるため、別の答えからモデルを「盗みました」。誰かが「より良い」モデルを持っているなら、残りのテクニックを使ってより良い結果を得ることができるはずです。
注釈として、Lempel-Zivなどのほとんどの圧縮方法は、この方法で「言語モデル」と見なすことができますが、少し目を凝らす必要があるかもしれません。(Burrows-Wheeler変換を行うものには特に注意が必要です!)また、user2699によるモデルはマルコフモデルの修正であることに注意してください。本質的に、この課題やおそらくテキスト全般のモデリングに対して競争力のあるものはありません。
全体的なアーキテクチャ
理解するために、アーキテクチャ全体をいくつかの部分に分割すると便利です。最高レベルの観点から、少しの状態管理コードが必要です。これは特に興味深いものではありませんが、完全を期すために、プログラムが次の推測を求められるたびに、Moby Dickの正しいプレフィックスが利用できることを強調したいと思います。過去の誤った推測は一切使用しません。効率のために、言語モデルはおそらく最初のN文字の状態を再利用して最初の(N + 1)文字の状態を計算できますが、原則として、呼び出されるたびにゼロから再計算できます。
プログラムのこの基本的な「ドライバー」を脇に置き、次の文字を推測する部分を覗いてみましょう。概念的には3つの部分を分離するのに役立ちます:言語モデル(上記)、「ヒント」ファイル、および「インタープリター」。各ステップで、インタープリターは言語モデルに次の文字の分布を要求し、ヒントファイルからいくつかの情報を読み取ります。次に、これらの部分を組み合わせて推測します。ヒントファイルに含まれる情報とその使用方法については、後で詳しく説明しますが、現時点では、これらの部分を精神的に分離しておくと役立ちます。実装面では、ヒントファイルは文字通り別個の(バイナリ)ファイルですが、文字列またはプログラム内に保存されたものである可能性があることに注意してください。近似として、
この回答のようにbzip2などの標準的な圧縮方法を使用している場合、「ヒント」ファイルは圧縮ファイルに対応します。「インタープリター」は圧縮解除プログラムに対応し、「言語モデル」は少し暗黙的です(上記のとおり)。
ヒントファイルを使用する理由
さらに分析するために簡単な例を選択しましょう。テキストがN
長い文字であり、すべての文字が(独立して)E
半分よりわずかに小さい確率で、T
同様に半分より少し小さいA
確率で、確率1/1000 = 0.1%であるモデルによって近似されていると仮定します。他の文字は使用できないと仮定しましょう。いずれにせよ、これA
は以前に目に見えなかったキャラクターが青から外れている場合とよく似ています。
我々は(この質問に対する他の回答の、すべてのほとんどではなくがそうであるように)L 0政権で操作した場合、のいずれかを選ぶよりも、通訳のためには良い戦略がないE
とはT
。平均して、約半分の文字が正しくなります。したがって、E≈N / 2であり、スコア≈N / 2です。ただし、圧縮戦略を使用する場合は、文字ごとに1ビットを少し超える量まで圧縮できます。Lはバイト単位でカウントされるため、L≈N / 8になり、スコア≈N / 4になり、前の戦略の2倍になります。
このモデルで1文字あたり1ビットを少し超えるこのレートを達成するのはわずかではありませんが、1つの方法は算術コーディングです。
算術コーディング
よく知られているように、エンコードはビット/バイトを使用して一部のデータを表す方法です。たとえば、ASCIIは英語のテキストと関連文字の7ビット/文字エンコーディングであり、検討中の元のMoby Dickファイルのエンコーディングです。一部の文字が他の文字よりも一般的である場合、ASCIIのような固定幅のエンコードは最適ではありません。このような状況では、多くの人がハフマンコーディングに手を伸ばします。これは、文字あたりのビット数が整数の固定(プレフィックスなし)コードが必要な場合に最適です。
ただし、算術コーディングはさらに優れています。おおまかに言って、「分数」ビットを使用して情報をエンコードすることができます。オンラインで利用可能な算術コーディングに関する多くのガイドがあります。オンラインで利用できる他のリソースがあるため、ここでは詳細をスキップします(特に実用的な実装については、プログラミングの観点からは少し注意が必要です)。
既知の言語モデルによって実際に生成されたテキストがある場合、算術コーディングは、そのモデルからのテキストの本質的に最適なエンコーディングを提供します。ある意味で、これはそのモデルの圧縮問題を「解決」します。(したがって、実際には、主な問題はモデルが不明であり、一部のモデルは人間のテキストのモデリングにおいて他のモデルより優れているということです。) 、この課題の解決策を生み出す1つの方法は、算術エンコーダーを使用して言語モデルから「ヒント」ファイルを生成し、算術デコーダーを「インタープリター」として使用することでした。
この本質的に最適なエンコーディングでは、確率pの文字に-log_2(p)ビットを費やすことになり、エンコーディングの全体的なビットレートはシャノンエントロピーになります。つまり、確率が1/2に近い文字はエンコードに約1ビットかかり、確率が1/1000である文字は約10ビットかかります(2 ^ 10は約1000ビットであるため)。
しかし、この課題のスコアリングメトリックは、最適な戦略としての圧縮を避けるために適切に選択されました。短いヒントファイルを取得するためのトレードオフとして、いくつかのエラーを作成する方法を見つけ出す必要があります。たとえば、試みる可能性のある戦略の1つは単純な分岐戦略です。可能な場合は一般に算術エンコードを使用しようとしますが、モデルの確率分布が何らかの形で「悪い」場合、最も可能性の高い文字を推測して、エンコードしてみてください。
なぜエラーを起こすのですか?
以前から例を分析して、「意図的に」エラーを発生させたい理由を動機付けましょう。算術符号化を使用して正しい文字をエンコードする場合、E
or の場合は約1ビットを使いますが、の場合はT
約10ビットを使いますA
。
全体として、これはかなり良いエンコードであり、3つの可能性がありますが、文字ごとに少し以上を費やします。基本的に、これA
はかなり可能性が低く、対応する10ビットをあまり頻繁に使うことはありません。ただし、A
?の場合に代わりにエラーを作成できたらいいと思いませんか?結局のところ、問題のメトリックは、1バイト= 8ビット長が2エラーに相当すると見なします。したがって、文字に8/2 = 4ビット以上を費やすのではなく、エラーを好むように思われます。1バイト以上を費やして1つのエラーを保存すると、間違いなく最適とは言えません!
「巻き戻し」メカニズム
このセクションでは、このソリューションの主な巧妙な側面について説明します。これは、長さのコストをかけずに誤った推測を処理する方法です。
分析している簡単な例では、巻き戻しメカニズムは特に簡単です。インタープリターは、ヒントファイルから1ビットを読み取ります。0の場合、推測しE
ます。1の場合、推測しT
ます。次に呼び出されたときに、正しい文字が何であるかを確認します。ヒントファイルが適切に設定されていれば、E
またはのT
場合、インタープリターが正しく推測することを保証できます。しかし、どうA
ですか?巻き戻しメカニズムの考えは単にコード化A
しないことです。より正確には、インタプリタが正しい文字がであることを後で知った場合、それは比phorA
的に「テープを巻き戻します」:以前に読んだビットを返します。読み取ったビットは、コーディングE
またはT
、 しかし今ではありません; 後で使用されます。この単純な例では、これは基本的に、正しい文字になるまで同じ文字(または)を推測し続けることを意味します。その後、別のビットを読み取り、続行します。E
T
このヒントファイルのエンコードは非常に簡単です。sを完全に無視しながら、すべてのE
sを0ビットに、T
sを1ビットに変換しA
ます。前のセクションの最後の分析により、このスキームではいくつかのエラーが発生しますが、A
sのいずれもエンコードしないため、全体のスコアが低下します。より小さな効果として、実際にはヒントファイルの長さも節約されます。これは、ビットごとにわずかではなく、それぞれに対してE
とビットを正確に1ビット使用T
するためです。
少し定理
エラーをいつ発生させるかをどのように決定しますか?モデルが次の文字の確率分布Pを与えると仮定します。可能な文字を、コード化されたコードとコード化されていない 2つのクラスに分けます。正しい文字がコーディングされていない場合、「巻き戻し」メカニズムを使用して、長さのコストなしでエラーを受け入れます。正しい文字がコーディングされている場合、算術コーディングを使用してエンコードするために、他の分布Qを使用します。
しかし、どのディストリビューションQを選択する必要がありますか?コード化された文字がすべて、コード化されていない文字よりも高い確率(P)を持っていることを確認するのはそれほど難しくありません。また、ディストリビューションQにはコード化された文字のみを含める必要があります。結局、他のものをコーディングしていないので、それらにエントロピーを「使う」べきではありません。確率分布Qがコード化文字のPに比例する必要があることを確認するのは少し複雑です。これらの観察結果をまとめると、最も可能性の高い文字をコーディングする必要がありますが、それほど可能性の低い文字はコーディングしないでください。また、Qはコーディングされた文字でPを再スケーリングします。
さらに、コーディング文字にどの「カットオフ」を選択するべきかについてのクールな定理があることがわかります。他のコーディングされた文字の少なくとも1 / 5.393の可能性がある限り、コーディングする必要があります。これ5.393
は、上記のプログラムの終わり近くで一見ランダムな定数の出現を「説明」します。1 / 5.393≈0.18542という数値は、方程式-p log(16)-p log p +(1 + p)log(1 + p)= 0の解です。
おそらく、この手順をコードで記述することは合理的な考えです。このスニペットは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;
}
すべてを一緒に入れて
前のセクションは残念ながら少し技術的ですが、他の部分をすべてまとめると、構造は次のようになります。指定された正しい文字の後の次の文字を予測するようにプログラムが要求されるたびに:
- Moby Dickの既知の正しいプレフィックスに正しい文字を追加します。
- テキストの(Markov)モデルを更新します。
- 秘密のソース:以前の推測が間違っていた場合は、巻き戻し、前の推測前の状態に算術復号器の状態を!
- 次の文字の確率分布Pを予測するためにマルコフモデルに依頼します。
- 前のセクションのサブルーチンを使用して、PをQに変換します。
- 算術デコーダーに、分布Qに従って、ヒントファイルの残りの部分から文字をデコードするように依頼します。
- 結果の文字を推測します。
ヒントファイルのエンコードも同様に動作します。その場合、プログラムは正しい次の文字が何であるかを知っています。コーディングする必要がある文字である場合、もちろん算術エンコーダーを使用する必要があります。ただし、コード化されていない文字の場合、算術エンコーダーの状態は更新されません。
確率分布、エントロピー、圧縮、算術コーディングなどの情報理論的背景を理解しているが、この投稿を理解しようとして失敗した場合(定理が真である理由を除く)、私たちに知らせてください。読んでくれてありがとう!