Perl + Math :: {ModInt、Polynomial、Prime :: 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/^␁//;$_}
制御画像は、対応する制御文字を表すために使用されます(␀
リテラルNUL文字など)。コードを読み取ろうとすることをあまり心配しないでください。より読みやすいバージョンが以下にあります。
で実行し-Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all
ます。-MMath::Bigint=lib,GMP
必要ではありません(したがって、スコアに含まれません)が、他のライブラリの前に追加すると、プログラムの実行が多少速くなります。
スコア計算
ここでのアルゴリズムは多少改善できますが、(Perlが適切なライブラリを持っていないため)書くのがかなり難しいでしょう。このため、エンコードでバイトを節約できることを考えると、コードでサイズと効率のトレードオフをいくつか行いました。ゴルフからすべてのポイントを削り取ろうとする意味はありません。
このプログラムは、600バイトのコードと、コマンドラインオプションに対する78バイトのペナルティで構成され、678ポイントのペナルティが与えられます。スコアの残りは、0〜99のすべての長さと0〜9のすべての放射レベルのベストケースとワーストケース(出力の長さ)でプログラムを実行して計算されました。平均的なケースはその中間のどこかにあり、これによりスコアの範囲が決まります。(別のエントリが同様のスコアで入らない限り、正確な値を計算しようとする価値はありません。)
したがって、これは、エンコード効率のスコアが91100から92141の範囲にあることを意味し、最終スコアは次のようになります。
91100 + 600 + 78 = 91778≤スコア≤92819 = 92141 + 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)は、ブロック全体に設定されるか、次のブロック全体にクリアされるか、ブロックに設定されるかを交互に切り替えますその後、など。ブロックは放射パラメータよりも長いため、各ブロックの少なくとも1つの文字が出力に残っています。したがって、同じMSBの文字を実行することにより、各文字がどのブロックに属しているかを判断できます。ブロックの数は常に放射パラメーターよりも大きいため、encdngには常に少なくとも1つの損傷のないブロックがあります。したがって、最も長いブロックまたは最も長く結ばれているブロックはすべて損傷を受けておらず、短いブロックを損傷したものとして扱うことができます(したがって、文字化け)。また、このような放射パラメータを推定することもできます(それは
消去コーディング
問題の消去符号化部分に関しては、これはリードソロモン構造の単純な特殊なケースを使用します。これは体系的な構成です。(イレージャーコーディングアルゴリズムの)出力は、入力に加えて、放射パラメーターに等しいいくつかの余分なブロックに等しくなります。これらのブロックに必要な値を単純な(そしてゴルフな!)方法で計算することができます。それらを文字化けとして扱い、それらに対してデコードアルゴリズムを実行して値を「再構築」します。
構築の背後にある実際の考え方も非常に単純です。可能な限り最小の多項式を、エンコードのすべてのブロックに適合させます(他の要素から文字化けが補間されます)。多項式がfの場合、最初のブロックはf(0)、2番目のブロックはf(1)などとなります。多項式の次数が入力のブロック数から1を引いたものに等しいことは明らかです(最初に多項式を当てはめ、それを使用して追加の「チェック」ブロックを構築するため)。また、d +1点は、d次の多項式を一意に定義するため、任意の数のブロック(放射パラメータまで)を文字化けすると、同じ多項式を再構築するのに十分な情報である、元の入力と同じ数の損傷していないブロックが残ります。(ブロックを解読するには、多項式を評価する必要があります。)
ベース変換
ここで最後に考慮すべきことは、ブロックが取得した実際の値を処理することです。整数で多項式補間を行う場合、結果は(整数ではなく)有理数であるか、入力値よりもはるかに大きいか、そうでなければ望ましくない可能性があります。そのため、整数を使用する代わりに、有限体を使用します。このプログラムでは、使用される有限体はpを法とする整数の体です。ここで、pは128 放射線 +1より小さい最大素数です(つまり、その素数に等しいいくつかの異なる値をブロックのデータ部分に収めることができる最大の素数)。有限フィールドの大きな利点は、除算(0を除く)が一意に定義され、常にそのフィールド内で値を生成することです。したがって、多項式の補間値は、入力値とまったく同じ方法でブロックに収まります。
入力を一連のブロックデータに変換するには、ベース変換を行う必要があります。入力をベース256から数値に変換し、ベースpに変換します(たとえば、放射パラメータ1の場合、p= 16381)。これは主に、Perlの基本変換ルーチンの欠如によって支えられていました(Math :: Prime :: Utilにはいくつかありますが、bignumの基底には機能せず、ここで扱う素数のいくつかは非常に大きいです)。既に多項式補間にMath :: Polynomialを使用しているため、「桁からの変換」関数として再利用することができました(多項式の係数として桁を表示し、評価することにより)、これはbignumで機能します結構です ただし、逆に言えば、自分で関数を記述する必要がありました。幸いにも、書くのはそれほど難しくありません(または冗長ではありません)。残念ながら、この基本変換は、入力が通常読み取り不能になることを意味します。先行ゼロに関する問題もあります。
出力にp個を超えるブロックを含めることはできません(そうしないと、2つのブロックのインデックスが等しくなりますが、多項式から異なる出力を生成する必要がある可能性があります)。これは、入力が非常に大きい場合にのみ発生します。このプログラムは、非常に簡単な方法で問題を解決します:放射を増加させます(ブロックを大きくし、pを大きくし、より多くのデータを収めることができ、明らかに正しい結果につながります)。
作成する価値のあるもう1つのポイントは、書き込まれたプログラムがそれ以外の場合にクラッシュするため、null文字列をそれ自体にエンコードすることです。また、明らかに最適なエンコーディングであり、放射パラメータが何であっても機能します。
潜在的な改善
このプログラムの主な漸近的な非効率性は、問題の有限体としてモジュロ素数を使用することです。サイズ2 nの有限フィールドが存在します(ブロックのペイロードサイズは自然に128の累乗であるため、ここでまさに必要です)。残念ながら、それらは単純なモジュロ構造よりもかなり複雑です。つまり、Math :: ModIntはそれをカットしません(非プライムサイズの有限フィールドを処理するためのCPANにライブラリが見つかりませんでした)。Math :: Polynomialのオーバーロードされた演算を使用してクラス全体を記述して処理する必要があり、その時点で、バイトコストは、たとえば16384ではなく16381の使用による(非常に小さな)損失を上回る可能性があります。
2のべき乗サイズを使用するもう1つの利点は、基本変換がはるかに簡単になることです。ただし、どちらの場合でも、入力の長さを表すより良い方法が役立ちます。「あいまいな場合に1を追加する」方法は単純ですが、無駄です。ここでは、全単射の基数変換がもっともらしいアプローチの1つです(考えは、基数を数字として、0を数字としてではなく、各数字が1つの文字列に対応するというものです)。
このエンコードの漸近的なパフォーマンスは非常に優れていますが(たとえば、長さ99の入力および放射パラメーター3の場合、エンコードは反復ベースのアプローチで得られる〜400バイトではなく、常に128バイトです)、そのパフォーマンス短い入力ではあまり良くありません。エンコードの長さは常に(放射パラメーター+ 1)の2乗以上です。したがって、放射9での非常に短い入力(長さ1から8)の場合、出力の長さは100です。(長さ9では、出力の長さは100であり、110である場合があります。) -非常に小さな入力に対するコーディングベースのアプローチ。入力のサイズに基づいて複数のアルゴリズム間で変更する価値があるかもしれません。
最後に、それは実際には得点されませんが、非常に高い放射パラメーターでは、各バイトのビット(出力サイズのof)を使用してブロックを区切ることは無駄です。代わりにブロック間に区切り文字を使用する方が安価です。区切り文字からブロックを再構築することは、交互MSBアプローチよりもかなり困難ですが、少なくともデータが十分に長い場合は可能だと思います(短いデータでは、出力から放射パラメータを推測することは困難です) 。パラメータに関係なく漸近的に理想的なアプローチを目指している場合、それは注目すべきことです。
(そしてもちろん、これよりも良い結果を生む完全に異なるアルゴリズムがあるかもしれません!)