Perl + Math :: {ModInt, 다항식, 프라임 :: Util}, 점수 ≤ 92819
$m=Math::Polynomial;sub l{($n,$b,$d)=@_;$n||$d||return;$n%$b,l($n/$b,$b,$d&&$d-1)}sub g{$p=$m->interpolate([grep ref$_[$_],0..$map{$p->evaluate($_)}0..$}sub p{prev_prime(128**$s)}sub e{($_,$r)=@_;length||return'';$s=$r+1;s/^[␀␁]/␁$&/;@l=map{mod($_,p$s)}l(Math::BigInt->from_bytes($_),p$s);$@l+$r>p($s)&&return e($_,$s);$a=0;join'',map{map{chr$_+$a}l($_->residue,128,$s,($a^=128))}g(@l)}sub d{@l=split/([␀-␡]+)/,$_[0];@l||return'';$s=vecmax map length,@l;@l=g map{length==$s&&mod($m->new(map{ord()%128}split//)->evaluate(128),p$s)}@l;$$_=$m->new(map{$_->residue}@l)->evaluate(p$s)->to_bytes;s/^␁//;$_}
제어 이미지 (예를 대응하는 제어 특성을 나타내는 데 사용되는 ␀
리터럴 널 문자이다). 많은 코드를 읽으려고에 대해 걱정하지 마십시오; 아래에 더 읽기 쉬운 버전이 있습니다.
으로 실행합니다 -Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all
. -MMath::Bigint=lib,GMP
필요하지 않으므로 (점수에 포함되지 않음) 다른 라이브러리 앞에 추가하면 프로그램이 다소 빠르게 실행됩니다.
점수 계산
여기서 알고리즘은 다소 개선 될 수는 없지만 쓰기가 더 어렵습니다 (Perl에 적절한 라이브러리가 없기 때문에). 이로 인해 코드에서 바이트를 절약 할 수 있다는 점을 감안할 때 코드에서 몇 가지 크기 / 효율성 트레이드 오프를 만들었습니다. 골프에서 모든 포인트를 줄이려고 할 필요는 없습니다.
이 프로그램은 코드의 600 바이트 플러스 678 포인트 페널티를주는 명령 줄 옵션에 대한 처벌, 78 바이트로 구성된다. 나머지 점수는 0에서 99까지의 모든 길이와 0에서 9까지의 모든 방사선 레벨에 대해 최상의 경우와 최악의 경우 (출력 길이로) 문자열에서 프로그램을 실행하여 계산되었습니다. 평균 사례는 중간에 있으며 점수에 한계가 있습니다. (다른 항목이 비슷한 점수로 나오지 않는 한 정확한 값을 계산할 가치는 없습니다.)
따라서 이것은 포괄적 따라서, 최종 스코어는 91,100 92,141에 부호화 효율의 점수 범위 인 것을 의미한다 :
91100 + 600 + 78 = 91,778 점수 ≤ ≤ 92,819 + 92,141 = 600 + 78
주석 및 테스트 코드가 포함 된 덜 골판지 버전
이것은 원래 프로그램 + 줄 바꿈, 들여 쓰기 및 주석입니다. (실제로 골프 버전은이 버전에서 줄 바꿈 / 들여 쓰기 / 설명을 제거하여 제작되었습니다.)
use 5.010; # -M5.010; free
use Math::BigInt lib=>'GMP'; # not necessary, but makes things much faster
use bigint; # -Mbigint
use Math::ModInt 'mod'; # -MMath::ModInt=mod
use Math::Polynomial; # -MMath::Polynomial
use ntheory ':all'; # -Mntheory=:all
use warnings; # for testing; clearly not necessary
### Start of program
$m=Math::Polynomial; # store the module in a variable for golfiness
sub l{ # express a number $n in base $b with at least $d digits, LSdigit first
# Note: we can't use a builtin for this because the builtins I'm aware of
# assume that $b fits into an integer, which is not necessarily the case.
($n,$b,$d)=@_;
$n||$d||return;
$n%$b,l($n/$b,$b,$d&&$d-1)
}
sub g{ # replaces garbled blocks in the input with their actual values
# The basic idea here is to interpolate a polynomial through all the blocks,
# of the lowest possible degree. Unknown blocks then get the value that the
# polynomial evaluates to. (This is a special case of Reed-Solomon coding.)
# Clearly, if we have at least as many ungarbled blocks as we did original
# elements, we'll get the same polynomial, thus we can always reconstruct
# the input.
# Note (because it's confusing): @_ is the input, $_ is the current element
# in a loop, but @_ is written as $_ when using the [ or # operator (e.g.
# $_[0] is the first element of @_.
# We waste a few bytes of source for efficiency, storing the polynomial
# in a variable rather than recalculating it each time.
$p=$m->interpolate([grep ref$_[$_],0..$#_],[grep ref,@_]);
# Then we just evaluate the polynomial for each element of the input.
map{$p->evaluate($_)}0..$#_
}
sub p{ # determines maximum value of a block, given (radiation+1)
# We split the input up into blocks. Each block has a prime number of
# possibilities, and is stored using the top 7 bits of (radiation+1)
# consecutive bytes of the output. Work out the largest possible prime that
# satisfies this property.
prev_prime(128**$s)
}
sub e{ # encoder; arguments: input (bytestring), radiation (integer)
($_,$r)=@_; # Read the arguments into variables, $_ and $r respectively
length||return''; # special case for empty string
$s=$r+1; # Also store radiation+1; we use it a lot
# Ensure that the input doesn't start with NUL, via prepending SOH to it if
# it starts with NUL or SOH. This means that it can be converted to a number
# and back, roundtripping correctly.
s/^[␀␁]/␁$&/; #/# <- unconfuse Stack Exchange's syntax highlighting
# Convert the input to a bignum, then to digits in base p$s, to split it
# into blocks.
@l=map{mod($_,p$s)}l(Math::BigInt->from_bytes($_),p$s);
# Encoding can reuse code from decoding; we append $r "garbled blocks" to
# the blocks representing the input, and run the decoder, to figure out what
# values they should have.
$#l+=$r;
# Our degarbling algorithm can only handle at most p$s blocks in total. If
# that isn't the case, try a higher $r (which will cause a huge increase in
# $b and a reduction in @l).
@l+$r>p($s)&&return e($_,$s);
# Convert each block to a sequence of $s digits in base 128, adding 128 to
# alternating blocks; this way, deleting up to $r (i.e. less than $s) bytes
# will preserve the boundaries between each block; then convert that to a
# string
$a=0; # we must initialize $a to make this function deterministic
join'',map{map{chr$_+$a}l($_->residue,128,$s,($a^=128))}g(@l)
}
sub d{ # decoder: arguments; encdng (bytestring)
# Reconstruct the original blocks by looking at their top bits
@l=split/([␀-␡]+)/,$_[0];
@l||return''; # special case for empty string
# The length of the longest block is the radiation parameter plus 1 (i.e.
# $s). Use that to reconstruct the value of $s.
$s=vecmax map length,@l;
# Convert each block to a number, or to undef if it has the wrong length.
# Then work out the values for the undefs.
@l=g map{
# Convert blocks with the wrong length to undef.
length==$s&&
# Convert other blocks to numbers, via removing any +128 and then
# using Math::Polynomial to convert the digit list to a number.
mod($m->new(map{ord()%128}split// #/# <- fix syntax highlighting
)->evaluate(128),p$s)
}@l;
# Remove the redundant elements at the end; now that they've reconstructed
# the garbled elements they have no further use.
$#l-=$s-1;
# Convert @l to a single number (reversing the conversion into blocks.)
$_=$m->new(map{$_->residue}@l)->evaluate(p$s)
# Convert that number into a string.
->to_bytes;
# Delete a leading SOH.
s/^␁//; #/# <- unconfuse Stack Exchange's syntax highlighting
# Finally, return the string.
$_
}
### Testing code
use Encode qw/encode decode/;
# Express a string using control pictures + IBM437, to make binary strings
# easier for a human to parse
sub format_string {
($_)=@_;
$_ = decode("Latin-1", $_);
s/[\0-\x1f]/chr (0x2400 + ord $&)/eag;
s/\x7f/chr 0x2421/eag;
s/[ -~\x80-\xff]/decode("IBM437",$&)/eag;
encode("UTF-8","\x{ff62}$_\x{ff63}")
}
sub test {
my ($string, $radiation, $samples) = @_;
say "Input: ", format_string($string);
my $encoding = e($string, $radiation);
say "Encoding: ", format_string($encoding);
say "Input length ", length($string), ", encoding length ", length($encoding), ", radiation $radiation";
my $decoding = d($encoding);
$decoding eq $string or die "Mistake in output!";
say "Decoding: ", format_string($decoding), " from ",
format_string($encoding);
# Pseudo-randomly generate $samples radiation-damaged versions.
srand 1;
for my $i (1..$samples) {
my $encdng = $encoding;
for my $r (1..$radiation) {
substr $encdng, int(rand(length $encdng)), 1, "";
}
my $newdecoding = d($encdng);
say "Decoding: ", format_string($newdecoding), " from ",
format_string($encdng);
$newdecoding eq $string or die "Mistake in output!";
}
say "";
length $encoding;
}
test "abcdefghijklm", 1, 10;
test "abcdefghijklm", 2, 10;
test "abcdefghijklm", 5, 10;
test "abcdefghijklm", 10, 10;
test "\0\0\0\0\0", 1, 10;
test "\5\4\3\2\1", 2, 10;
test "a", 10, 10;
my %minlength = ();
my %maxlength = ();
for my $length (0..99) {
my ($min, $max) = ("", "");
$length and ($min, $max) =
("\2" . "\0" x ($length - 1), "\1" . "\377" x ($length - 1));
for my $radiation (0..9) {
$minlength{"$length-$radiation"} = test $min, $radiation, 1;
$maxlength{"$length-$radiation"} = test $max, $radiation, 1;
}
}
say "Minimum score: ", vecsum values %minlength;
say "Maximum score: ", vecsum values %maxlength;
연산
문제 단순화
기본 아이디어는이 "삭제 코딩"문제 (일반적으로 탐색되지 않은 문제)를 소거 코딩 문제 (포괄적으로 탐구되는 수학 영역)로 줄이는 것입니다. 삭제 코딩의 기본 개념은 "삭제 채널"을 통해 데이터를 전송할 준비를하고 있다는 것입니다.이 채널은 전송하는 문자를 알려진 오류 위치를 나타내는 "가블"문자로 대체하는 채널입니다. (즉, 원래 문자는 여전히 알려지지 않았지만 손상이 발생한 위치는 항상 분명합니다.) 그 뒤에있는 아이디어는 매우 간단합니다. 입력을 길이 블록 ( 방사선) 으로 나눕니다.+ 1), 각 블록에서 8 비트 중 7 비트를 데이터로 사용하고 나머지 비트 (이 구성에서는 MSB)가 전체 블록에 대해 설정되고 다음 블록 전체에 대해 설정 해제되고 블록에 대해 설정됩니다. 그 후, 그리고에 이렇게. 블록이 방사선 매개 변수보다 길기 때문에 각 블록에서 하나 이상의 문자가 출력에 남아 있습니다. 따라서 동일한 MSB로 문자를 실행하여 각 문자가 속한 블록을 해결할 수 있습니다. 블록의 수는 항상 방사선 매개 변수보다 크므로 encdng에는 항상 하나 이상의 손상되지 않은 블록이 있습니다. 따라서 우리는 가장 길거나 가장 긴 묶인 모든 블록이 손상되지 않았으므로 더 짧은 블록을 손상된 것으로 취급 할 수 있습니다. 우리는 또한 '(그것을 같은 방사선 매개 변수를 추론 할 수
소거 코딩
문제의 삭제 코딩 부분에 관해서는,이 리드 - 솔로몬 구조의 간단한 특별한 경우를 사용합니다. 이 체계적인 구조이다 : (소거 코딩 알고리즘)의 출력이 입력 더하기 추가 블록의 수와 동일한, 상기 방사선 파라미터와 동일. 우리는 간단한에서 이러한 블록에 필요한 값을 계산 (및 golfy!) 방법을, 다음 "재구성"자신의 값으로 그들에 디코딩 알고리즘을 실행 깨짐로 치료를 통해 할 수 있습니다.
구성의 실제 개념도 매우 간단합니다. 가능한 최소한의 다항식을 인코딩의 모든 블록에 맞 춥니 다 (다른 요소에서 내포 된 가블). 다항식이 f 이면 첫 번째 블록은 f (0)이고 두 번째 블록 은 f (1) 등입니다. 다항식의 차수는 입력에서 1을 뺀 블록 수와 같습니다 (다항식을 먼저 다항식에 맞추고이를 사용하여 여분의 "체크"블록을 구성하기 때문에). 하고 있기 때문에 D +1 포인트 고유 정도의 다항식을 정의 D를(방사선 파라미터까지) 임의의 수의 블록을 잘못 전달하는 손상 블록의 개수가 동일한 다항식을 재구성하기에 충분한 정보 인 일본어 입력 동일 떠날 것이다. 그런 다음 블록을 깨지지 않도록 다항식을 평가하면됩니다.
자료 변환
여기에 남은 마지막 고려 사항은 블록이 취하는 실제 값과 관련이 있습니다. 우리가 정수에 다항식 보간을 할 경우, 결과는 입력 값보다 훨씬 큰, 또는 기타 바람직하지 않은 유리수 (이 아닌 정수) 일 수있다. 따라서 정수를 사용하는 대신 유한 필드를 사용합니다. 이 프로그램에서 사용 된 유한 필드 정수 모듈의 필드 (P) , p는 가장 큰 소수 128 미만이며 , 방사선 +1은(즉, 블록의 데이터 부분에 해당 소수와 동일한 여러 고유 값을 맞출 수있는 가장 큰 소수). 유한 필드의 가장 큰 장점은 나눗셈 (0을 제외하고)이 고유하게 정의되고 해당 필드 내에서 항상 값을 생성한다는 것입니다. 따라서, 다항식의 보간 된 값은 입력 값과 같은 방식으로 블록에 맞습니다.
블록 데이터의 일련의 입력을 변환하기 위해, 다음에, 우리는 기본 변환을 수행해야 다음베이스로 변환 번호로 기재 (256)로부터 입력을 변환 P 예에 대해 ( 방사선 (1)의 파라미터, 우리가 쪽= 16381). 이것은 대부분 Perl의 기본 변환 루틴 부족으로 인해 발생했습니다 (Math :: Prime :: Util에는 일부가 있지만 bignum base에는 작동하지 않으며 여기에서 작업하는 소수는 엄청나게 큽니다). 우리는 이미 다항식 보간에 Math :: Polynomial을 사용하고 있기 때문에, "숫자를 다항식의 계수로보고 평가함으로써)"숫자 시퀀스에서 변환 "기능으로 재사용 할 수있었습니다. 괜찮아 그러나 다른 방법으로 가면 함수를 직접 작성해야했습니다. 운 좋게도 작성하기가 너무 어렵거나 장황하지 않습니다. 불행히도,이 기본 변환은 입력을 일반적으로 읽을 수 없도록 렌더링됨을 의미합니다. 선행 0에는 문제가 있습니다.
우리는 출력에 p 개 이상의 블록을 가질 수 없다는 점에 유의해야합니다 (그렇지 않으면 두 블록의 인덱스가 동일 해지지 만 다항식에서 다른 출력을 생성해야 할 수도 있음). 이것은 입력이 매우 큰 경우에만 발생합니다. 이 프로그램은 매우 간단한 방식으로 문제를 해결합니다. 방사선 증가 (블록을 더 크게, p를 크게하여 더 많은 데이터를 수용 할 수 있고 정확한 결과를 얻을 수 있음).
작성해야 할 또 하나의 요점은 작성된 것처럼 프로그램이 충돌하면 null 문자열을 자체로 인코딩한다는 것입니다. 또한 가능한 최고의 인코딩이며 방사선 매개 변수가 무엇이든 상관없이 작동합니다.
잠재적 개선
이 프로그램에서 주요 점근 비효율은 문제의 유한 필드로 모듈로 프라임을 사용하는 것과 관련이 있습니다. 크기가 2 n 인 유한 필드가 존재합니다 (블록의 페이로드 크기가 자연스럽게 128의 거듭 제곱이기 때문에 여기에서 정확히 원하는 것입니다). 불행하게도, 그들은 수학 :: ModInt 그것을 잘라 않을 것이다 (나는 비 프라임 크기의 유한 필드를 처리하기위한 CPAN에 어떤 라이브러리를 찾을 수 없습니다) 즉, 간단한 모듈로 건설보다는 더 복잡한 것; 나는 수학 :: 다항식을 처리 할 수 과부하 산술 전체 클래스를 작성해야하고, 줄 그 시점에서 바이트 비용 잠재적 예를 들어, 사용에서 (아주 작은) 손실 overweigh, 16,381 아니라 16,384 이하인 수 있습니다.
2의 거듭 제곱 크기를 사용하는 또 다른 이점은 기본 변환이 훨씬 쉬워진다는 것입니다. 그러나 두 경우 모두 입력 길이를 나타내는 더 나은 방법이 유용합니다. "모호한 경우 1 앞에 추가"방법은 간단하지만 낭비입니다. 형용사 기준 변환은 여기에서 하나의 그럴듯한 접근 방식입니다 (각각의 숫자는 단일 문자열에 해당하도록 숫자는 숫자가 아니고 0은 숫자가 아님).
이 인코딩의 점근 적 성능은 매우 우수하지만 (예 : 길이 99의 입력 및 3의 방사선 매개 변수의 경우, 인코딩은 반복 기반 접근법이 얻을 수있는 ~ 400 바이트가 아니라 항상 128 바이트 길이 임) 성능 짧은 입력에는 좋지 않습니다. 인코딩의 길이는 항상 (방사선 매개 변수 + 1)의 제곱 이상입니다. 따라서 방사선 9에서 매우 짧은 입력 (길이 1 ~ 8)의 경우 출력 길이는 100입니다. (길이 9에서 출력 길이는 때때로 100 및 때로는 110입니다.) 반복 기반 접근 방식은이 소거를 분명히 능가합니다. 매우 작은 입력에 대한 코딩 기반 접근법; 입력 크기에 따라 여러 알고리즘간에 변경하는 것이 좋습니다.
마지막으로, 정말를 단락 블록 (출력 크기의 ⅛) 모든 바이트의 비트가 낭비 사용하여 매우 높은 방사선 매개 변수와 함께 득점을 마련하지 않지만, 대신 블록 사이에 구분 기호를 사용하는 것이 저렴하다. 구분 기호로 블록을 재구성하는 것은 대체 MSB 접근 방식보다 다소 어렵지만 적어도 데이터가 충분히 길면 (짧은 데이터로 인해 출력에서 방사선 매개 변수를 추론하기가 어려울 수 있음) 가능하다고 생각합니다 . 즉 관계없이 매개 변수의 점근 적으로 이상적인 접근을 목표로하는 경우를 보면 뭔가 될 것입니다.
(물론,이보다 더 나은 결과를 완전히 다른 알고리즘이있을 수 있습니다!)