펄, 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
. 위 문자 의 두 번째 인스턴스 에서이 파일 이름을 변경할 수 있습니다 B
.이 파일을 생성하는 방법은 아래를 참조하십시오.
이 프로그램은 본질적으로 user2699의 답변 에서와 같이 Markov 모델의 조합을 사용 하지만 약간의 수정이 있습니다. 이것은 다음 캐릭터에 대한 분포 를 생성합니다 . 우리는 정보 이론을 사용하여 B
인코딩 힌트 에 오류를 허용할지 또는 스토리지 비트를 소비할지 여부를 결정합니다 (있는 경우). 우리는 산술 코딩 을 사용 하여 모델의 소수 비트를 최적으로 저장합니다.
프로그램의 길이는 582 바이트 (불필요한 최종 개행 포함)이고 이진 파일의 B
길이는 69942 바이트이므로 여러 파일의 점수 를 매기는 규칙에L
따라 582 + 69942 + 1 = 70525 로 점수 가 매겨집니다.
이 프로그램에는 거의 확실하게 64 비트 (little-endian?) 아키텍처가 필요합니다. 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
하지만 두 번째 줄에서 쉽게 변경할 수 있습니다.
텍스트 비교
"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와 같은 대부분의 압축 방법은 이러한 방식으로 "언어 모델"로 간주 될 수 있지만 약간 움켜 쥐어 야 할 수도 있습니다. (Brows-Wheeler 변환을 수행하는 것은 특히 까다 롭습니다!) 또한 user2699의 모델은 Markov 모델의 수정 사항입니다. 본질적으로이 도전에 대한 경쟁 또는 대체로 텍스트를 모델링하는 다른 어떤 것도 없습니다.
전반적인 아키텍처
이해를 돕기 위해 전체 아키텍처를 여러 조각으로 나누는 것이 좋습니다. 최상위 수준 관점에서 약간의 상태 관리 코드가 필요합니다. 이것은 특별히 흥미롭지는 않지만, 완전성을 위해 프로그램이 다음 추측을 요구할 때마다 Moby Dick의 올바른 접두사를 사용할 수 있다고 강조하고 싶습니다. 우리는 과거의 잘못된 추측을 어떤 식 으로든 사용하지 않습니다. 효율성을 위해 언어 모델은 첫 번째 N 문자의 상태를 재사용하여 첫 번째 (N + 1) 문자의 상태를 계산할 수 있지만 원칙적으로 호출 될 때마다 처음부터 다시 계산할 수 있습니다.
프로그램의 기본 "드라이버"를 따로 설정하고 다음 문자를 추측하는 부분을 들여다 보도록합시다. 언어 모델 (위에서 논의), "힌트"파일 및 "통역사"의 세 부분을 개념적으로 분리하는 데 도움이됩니다. 각 단계에서 통역사는 언어 모델에 다음 문자의 배포를 요청하고 힌트 파일에서 일부 정보를 읽을 수 있습니다. 그런 다음 이러한 부분을 추측으로 결합합니다. 정확히 힌트 파일에 어떤 정보가 있고 어떻게 사용되는지는 나중에 설명 할 것이지만 지금은이 부분들을 정신적으로 분리시키는 데 도움이됩니다. 구현 측면에서 힌트 파일은 문자 그대로 별도의 (이진) 파일이지만 프로그램 내부에 저장된 문자열이거나 무언가 일 수 있습니다. 근사치로
이 답변 에서처럼 bzip2 와 같은 표준 압축 방법을 사용하는 경우 "hints"파일은 압축 파일에 해당합니다. "통역사"는 압축 해제기에 해당하는 반면 "언어 모델"은 약간 암시 적입니다 (위에서 언급 한 바와 같이).
힌트 파일을 사용하는 이유는 무엇입니까?
추가 분석을위한 간단한 예를 선택해 봅시다. 텍스트가 N
길고 근사한 문자 라고 가정합니다. 모든 문자는 E
확률이 절반보다 약간 작은 문자 , 확률이 절반보다 약간 작은 문자 T
, A
1/1000 = 0.1 %의 확률을 갖는 문자 (독립적으로) 입니다. 다른 문자는 불가능하다고 가정하자. 어쨌든 A
이전에 보지 못한 캐릭터가 파란색으로 보이는 경우와 매우 유사합니다.
만약 우리가 L 0 체제에서이 질문에 대한 다른 답변들 대부분이 아닌 대부분을 운영한다면, E
and 중 하나를 선택하는 것보다 통역사에게 더 나은 전략은 없습니다 T
. 평균적으로 문자의 약 절반이 맞습니다. 따라서 E ≈ N / 2와 ≈ N / 2도 점수입니다. 그러나 압축 전략을 사용하면 문자 당 하나 이상의 비트로 압축 할 수 있습니다. L은 바이트 단위로 계산되므로 L ≈ N / 8을 얻으므로 이전 전략보다 두 배 좋은 strategy N / 4를 얻습니다.
이 모델에서 문자 당 하나 이상의 비트를이 속도로 달성하는 것은 사소한 것이 아니지만 한 가지 방법은 산술 코딩입니다.
산술 코딩
일반적으로 알려진 바와 같이, 인코딩 은 비트 / 바이트를 사용하여 일부 데이터를 나타내는 방법입니다. 예를 들어 ASCII는 영어 텍스트 및 관련 문자의 7 비트 / 문자 인코딩이며, 고려중인 원본 Moby Dick 파일의 인코딩입니다. 일부 문자가 다른 문자보다 일반적인 경우 ASCII와 같은 고정 너비 인코딩이 최적이 아닙니다. 이러한 상황에서 많은 사람들이 허프만 코딩에 도달합니다 . 문자 당 정수 비트 수의 고정 (프리픽스 프리) 코드를 원하는 경우에 최적입니다.
그러나 산술 코딩 이 훨씬 좋습니다. 대략적으로 말하면, "분수"비트를 사용하여 정보를 인코딩 할 수 있습니다. 온라인으로 사용할 수있는 산술 코딩에 대한 많은 안내서가 있습니다. 온라인에서 사용 가능한 다른 리소스로 인해 여기에서 세부 사항 (특히 실제 구현, 프로그래밍 측면에서 약간 까다로울 수 있음)을 건너 뛰지 만 누군가가 불평하면이 섹션을 더 자세히 설명 할 수 있습니다.
알려진 언어 모델에 의해 실제로 생성 된 텍스트가있는 경우, 산술 코딩은 해당 모델의 텍스트를 기본적으로 최적으로 인코딩합니다. 어떤 의미에서 이것은 해당 모델의 압축 문제를 "해결"합니다. (실제로 주요 문제는 모델을 알지 못하고 일부 모델은 인간의 텍스트를 모델링 할 때 다른 모델보다 낫다는 것입니다.)이 콘테스트에서 오류를 허용하지 않은 경우 이전 섹션의 언어로 이 과제에 대한 해결책을 만드는 한 가지 방법은 산술 인코더를 사용하여 언어 모델에서 "힌트"파일을 생성 한 다음 산술 디코더를 "통역사"로 사용하는 것입니다.
이 본질적으로 최적화 된 인코딩에서, 우리는 확률 p를 갖는 문자에 대해 -log_2 (p) 비트를 소비하게되고, 인코딩의 전체 비트 레이트는 Shannon 엔트로피 입니다. 이는 확률이 1/2에 가까운 문자는 인코딩하는 데 약 1 비트가 걸리고, 1/1000의 확률이있는 문자는 약 10 비트가됩니다 (2 ^ 10은 대략 1000이므로).
그러나이 과제에 대한 점수 메트릭은 압축을 최적의 전략으로 피하기 위해 잘 선택되었습니다. 더 짧은 힌트 파일을 얻는 데있어 트레이드 오프로 오류를 만드는 방법을 찾아야합니다. 예를 들어, 시도 할 수있는 전략 중 하나는 간단한 분기 전략입니다. 일반적으로 가능한 경우 산술 인코딩을 사용하려고하지만 모델의 확률 분포가 "나쁜"경우 가장 가능성이 높은 문자를 추측하여 인코딩하지 마십시오.
왜 오류가 발생합니까?
왜 우리가 "의도적으로"오류를 만들고 싶을 지 동기를 부여하기 위해 이전의 예를 분석해 봅시다. 올바른 문자를 인코딩하기 위해 산술 코딩을 사용하는 경우 E
또는 의 경우 대략 1 비트를 사용 T
하지만의 경우에는 약 10 비트를 사용합니다 A
.
전반적으로, 이것은 세 가지 가능성이 있지만 문자 당 조금 이상을 소비하는 꽤 좋은 인코딩입니다. 기본적으로 A
는 가능성이 거의 없으며 해당 10 비트를 너무 자주 소비하지 않습니다. 그러나 A
? 의 경우 대신 오류를 만들 수 있다면 좋지 않을까요? 결국 문제에 대한 메트릭은 1 바이트 = 8 비트 길이의 길이가 2 개의 오류와 같다고 간주합니다. 따라서 문자에 8/2 = 4 비트 이상을 소비하는 대신 오류를 선호 해야하는 것처럼 보입니다. 하나의 오류를 저장하기 위해 1 바이트 이상을 소비하면 확실히 차선책으로 들립니다!
"되감기"메커니즘
이 섹션에서는이 솔루션의 주요 영리한 측면에 대해 설명합니다.이 방법은 비용없이 잘못된 추측을 처리 할 수있는 방법입니다.
우리가 분석 한 간단한 예에서 되감기 메커니즘은 특히 간단합니다. 인터프리터는 힌트 파일에서 1 비트를 읽습니다. 0이면 추측 E
합니다. 1이면 추측 T
합니다. 다음에 호출 될 때 올바른 문자가 무엇인지 확인합니다. 힌트 파일이 제대로 설정 되면 E
or 의 경우 T
인터프리터가 올바르게 추측 할 수 있습니다. 그러나 어떻 A
습니까? 되감기 메커니즘의 아이디어는 단순히 코드 A
를 작성 하지 않는 것 입니다. 더 정확하게 말하면, 인터프리터가 나중에 올바른 문자가이라는 것을 알게되면 A
은유 적으로 " 테이프를 되감습니다 ": 이전에 읽은 비트를 반환합니다. 읽은 비트는 코딩 E
하거나T
그러나 지금은 아닙니다. 나중에 사용됩니다. 이 간단한 예에서 이것은 기본적으로 동일한 문자 ( 또는 )가 올바르게 될 때까지 계속 추측 한다는 것을 의미합니다 . 그런 다음 다른 비트를 읽고 계속 진행합니다.E
T
이 힌트 파일의 인코딩은 매우 간단합니다. 모든 s를 완전히 무시하면서 모든 E
s를 0 비트로, T
s를 1 비트로 전환 A
합니다. 이전 섹션의 끝 부분에있는 분석에 따르면이 체계는 오류를 발생 시키지만 A
s를 인코딩하지 않으면 서 전체적으로 점수를 줄 입니다. 더 작은 효과로 실제로 힌트 파일의 길이도 절약됩니다. 비트 E
와 비트가 T
아닌 비트 와 비트마다 정확히 하나의 비트를 사용하기 때문 입니다.
작은 정리
언제 오류를 내야합니까? 모델이 다음 캐릭터에 대한 확률 분포 P를 제공한다고 가정합니다. 가능한 문자를 coded 와 not coded의 두 클래스로 분리합니다 . 올바른 문자가 코딩되지 않은 경우 "되감기"메커니즘을 사용하여 길이에 상관없이 오류를 허용합니다. 올바른 문자가 코딩되면 다른 분포 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를 예측하도록 Markov 모델에 요청하십시오.
- 이전 섹션의 서브 루틴을 사용하여 P를 Q로 변환합니다.
- 분포 Q에 따라 산술 디코더에 힌트 파일의 나머지 부분에서 문자를 디코딩하도록 요청하십시오.
- 결과 캐릭터를 맞춰보세요.
힌트 파일의 인코딩도 비슷하게 작동합니다. 이 경우 프로그램은 올바른 다음 문자가 무엇인지 알고 있습니다. 그것이 코딩되어야하는 문자라면, 물론 그 위에 산술 인코더를 사용해야합니다. 그러나 코드화되지 않은 문자이면 산술 인코더의 상태를 업데이트하지 않습니다.
확률 분포, 엔트로피, 압축 및 산술 코딩과 같은 정보 이론적 배경을 이해했지만이 게시물을 이해하지 못하고 시도하지 않은 경우 (이론이 참인 이유는 제외) 알려 주시면 정리할 수 있습니다. 읽어 주셔서 감사합니다!