Perl + Math :: {ModInt, Polynom, Prime :: Util}, Ergebnis ≤ 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/^␁//;$_}
Kontrollbilder werden verwendet, um das entsprechende Kontrollzeichen darzustellen (z. B. ␀
ein wörtliches NUL-Zeichen). Mach dir keine Sorgen, wenn du versuchst, den Code zu lesen. Es gibt unten eine besser lesbare Version.
Laufen Sie mit -Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all
. -MMath::Bigint=lib,GMP
ist nicht notwendig (und daher nicht in der Partitur enthalten), aber wenn Sie es vor den anderen Bibliotheken hinzufügen, wird das Programm etwas schneller ausgeführt.
Ergebnisberechnung
Der Algorithmus hier ist etwas verbesserungsfähig, aber schwieriger zu schreiben (da Perl nicht über die entsprechenden Bibliotheken verfügt). Aus diesem Grund habe ich einige Kompromisse in Bezug auf Größe und Effizienz im Code gemacht, da es keinen Grund gibt, zu versuchen, jeden Punkt vom Golfspielen abzuhalten, da Bytes in der Codierung gespeichert werden können.
Das Programm besteht aus 600 Byte Code und 78 Byte Strafen für Befehlszeilenoptionen, was eine Strafe von 678 Punkten ergibt. Der Rest der Punktzahl wurde berechnet, indem das Programm für jede Länge von 0 bis 99 und jede Strahlungsstufe von 0 bis 9 mit der Zeichenfolge für den besten und den schlechtesten Fall (in Bezug auf die Ausgabelänge) ausgeführt wurde. Der durchschnittliche Fall liegt irgendwo dazwischen, und dies gibt Grenzen für die Punktzahl. (Es lohnt sich nicht, den genauen Wert zu berechnen, es sei denn, ein anderer Eintrag kommt mit einer ähnlichen Punktzahl.)
Dies bedeutet, dass die Bewertung der Kodierungseffizienz im Bereich von 91100 bis einschließlich 92141 liegt. Die endgültige Bewertung lautet daher:
91100 + 600 + 78 = 91778 ≤ Score ≤ 92819 = 92141 + 600 + 78
Weniger Golf-Version mit Kommentaren und Testcode
Dies ist das ursprüngliche Programm + Zeilenumbrüche, Einrückungen und Kommentare. (Tatsächlich wurde die Golfversion durch Entfernen von Zeilenumbrüchen / Einrückungen / Kommentaren aus dieser Version erstellt.)
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;
Algorithmus
Das Problem vereinfachen
Die Grundidee besteht darin, dieses Problem der "Löschkodierung" (das nicht weit verbreitet ist) in ein Löschkodierungsproblem (ein umfassend erforschtes Gebiet der Mathematik) zu reduzieren. Die Idee hinter der Löschcodierung ist, dass Sie Daten vorbereiten, die über einen "Löschkanal" gesendet werden. Dieser Kanal ersetzt manchmal die gesendeten Zeichen durch ein "Garble" -Zeichen, das auf eine bekannte Fehlerposition hinweist. (Mit anderen Worten, es ist immer klar, wo Korruption aufgetreten ist, obwohl der ursprüngliche Charakter noch unbekannt ist.) Die Idee dahinter ist ziemlich einfach: Wir teilen die Eingabe in Längenblöcke ( Strahlung) auf+ 1) und verwenden Sie sieben der acht Bits in jedem Block für Daten, während das verbleibende Bit (bei dieser Konstruktion das MSB) abwechselnd für einen gesamten Block gesetzt, für den gesamten nächsten Block gelöscht und für den Block gesetzt wird danach und so weiter. Da die Blöcke länger als der Strahlungsparameter sind, bleibt mindestens ein Zeichen von jedem Block in der Ausgabe erhalten. Wenn wir also eine Reihe von Zeichen mit demselben MSB ausführen, können wir herausfinden, zu welchem Block jedes Zeichen gehört. Die Anzahl der Blöcke ist auch immer größer als der Strahlungsparameter, so dass wir immer mindestens einen unbeschädigten Block in der Kodierung haben; Wir wissen also, dass alle Blöcke, die am längsten oder am längsten gebunden sind, unbeschädigt sind, so dass wir kürzere Blöcke als beschädigt behandeln können (also als eine Unlesbarkeit). Wir können den Strahlungsparameter auch so ableiten (es '
Löschcodierung
Für den Löschcodierungsteil des Problems wird ein einfacher Spezialfall der Reed-Solomon-Konstruktion verwendet. Dies ist eine systematische Konstruktion: Die Ausgabe (des Löschcodierungsalgorithmus) entspricht der Eingabe plus einer Anzahl zusätzlicher Blöcke, die dem Strahlungsparameter entsprechen. Wir können die Werte, die für diese Blöcke benötigt werden, auf einfache (und golferische) Weise berechnen, indem wir sie als Störsignale behandeln und dann den Decodierungsalgorithmus ausführen, um ihren Wert zu "rekonstruieren".
Die eigentliche Idee hinter der Konstruktion ist ebenfalls sehr einfach: Wir passen ein Polynom mit dem kleinstmöglichen Grad an alle Blöcke in der Codierung an (mit Verzerrungen, die von den anderen Elementen interpoliert werden); Wenn das Polynom f ist , ist der erste Block f (0), der zweite ist f (1) und so weiter. Es ist klar, dass der Grad des Polynoms der Anzahl der Eingabeblöcke minus 1 entspricht (da wir zuerst ein Polynom an diese anpassen und dann die zusätzlichen "Check" -Blöcke daraus konstruieren); und weil d + 1 Punkte ein Polynom des Grades d eindeutig definierenWenn eine beliebige Anzahl von Blöcken (bis zum Strahlungsparameter) verstümmelt wird, bleibt eine Anzahl von unbeschädigten Blöcken gleich der ursprünglichen Eingabe, was ausreicht, um dasselbe Polynom zu rekonstruieren. (Dann müssen wir nur noch das Polynom auswerten, um einen Block freizugeben.)
Basisumwandlung
Die letzte Überlegung betrifft die tatsächlichen Werte der Blöcke. Wenn wir eine Polynominterpolation für die ganzen Zahlen durchführen, können die Ergebnisse rationale Zahlen (und nicht ganze Zahlen) sein, die viel größer als die Eingabewerte sind oder auf andere Weise unerwünscht sind. Anstatt die ganzen Zahlen zu verwenden, verwenden wir daher ein endliches Feld. In diesem Programm wird als endliches Feld das Feld der ganzen Zahlen modulo p verwendet , wobei p die größte Primzahl unter 128 Strahlung +1 ist(dh die größte Primzahl, für die wir eine Anzahl unterschiedlicher Werte, die dieser Primzahl entsprechen, in den Datenteil eines Blocks einfügen können). Der große Vorteil von endlichen Feldern besteht darin, dass die Division (außer durch 0) eindeutig definiert ist und immer einen Wert in diesem Feld erzeugt. Daher passen die interpolierten Werte der Polynome genauso in einen Block wie die Eingabewerte.
Um die Eingabe in eine Reihe von Blockdaten umzuwandeln, müssen wir eine Basiskonvertierung durchführen: Konvertieren Sie die Eingabe von der Basis 256 in eine Zahl und konvertieren Sie sie dann in die Basis p (z. B. für einen Strahlungsparameter von 1 haben wir p= 16381). Dies wurde größtenteils durch das Fehlen von Basenkonvertierungsroutinen in Perl aufgehalten (Math :: Prime :: Util hat einige, aber sie funktionieren nicht für Bignum-Basen, und einige der Primzahlen, mit denen wir hier arbeiten, sind unglaublich groß). Da wir Math :: Polynomial bereits für die Polynominterpolation verwenden, konnte ich es als Funktion "Konvertieren aus Ziffernfolge" wiederverwenden (indem ich die Ziffern als Koeffizienten eines Polynoms betrachtete und es auswertete). Dies funktioniert für Bignome Alles gut. Umgekehrt musste ich die Funktion selbst schreiben. Zum Glück ist es nicht zu schwer (oder wortreich) zu schreiben. Leider bedeutet diese Basiskonvertierung, dass die Eingabe normalerweise nicht lesbar ist. Es gibt auch ein Problem mit führenden Nullen.
Es sollte beachtet werden, dass wir nicht mehr als p Blöcke in der Ausgabe haben können (andernfalls würden die Indizes von zwei Blöcken gleich werden und müssen möglicherweise unterschiedliche Ausgaben aus dem Polynom erzeugen). Dies geschieht nur, wenn die Eingabe extrem groß ist. Dieses Programm löst das Problem auf sehr einfache Weise: Erhöhung der Strahlung (wodurch die Blöcke größer und p viel größer werden, was bedeutet, dass wir viel mehr Daten einpassen können, und was eindeutig zu einem korrekten Ergebnis führt).
Ein weiterer Punkt, den es sich zu machen lohnt, ist, dass wir den Null-String in sich selbst kodieren, da das geschriebene Programm andernfalls darauf abstürzen würde. Es ist auch eindeutig die bestmögliche Kodierung und funktioniert unabhängig von den Strahlungsparametern.
Mögliche Verbesserungen
Die hauptsächliche asymptotische Ineffizienz in diesem Programm liegt in der Verwendung von Modulo-Prime als die fraglichen endlichen Felder. Es existieren endliche Felder der Größe 2 n (was wir hier genau wollen, da die Nutzlastgrößen der Blöcke natürlich eine Potenz von 128 sind). Leider sind sie komplexer als eine einfache Modulo-Konstruktion, was bedeutet, dass Math :: ModInt sie nicht schneiden würde (und ich konnte keine Bibliotheken im CPAN finden, um endliche Felder von Nicht-Prim-Größen zu verarbeiten). Ich müsste eine ganze Klasse mit überladener Arithmetik schreiben, damit Math :: Polynomial damit umgehen kann, und zu diesem Zeitpunkt können die Bytekosten möglicherweise den (sehr geringen) Verlust aus der Verwendung von z. B. 16381 anstelle von 16384 überwiegen.
Ein weiterer Vorteil der Verwendung von Potenz-2-Größen besteht darin, dass die Basisumwandlung viel einfacher wird. In beiden Fällen wäre jedoch eine bessere Methode zur Darstellung der Länge der Eingabe nützlich. Die Methode "Voranstellen einer 1 in mehrdeutigen Fällen" ist einfach, aber verschwenderisch. Die bijektive Basiskonvertierung ist hier ein plausibler Ansatz (die Idee ist, dass Sie die Basis als Ziffer und 0 als keine Ziffer haben, sodass jede Zahl einer einzelnen Zeichenfolge entspricht).
Obwohl die asymptotische Leistung dieser Codierung sehr gut ist (z. B. bei einer Eingabe von Länge 99 und einem Strahlungsparameter von 3, ist die Codierung immer 128 Bytes lang, anstatt der ~ 400 Bytes, die wiederholungsbasierte Ansätze erhalten würden), ist ihre Leistung sehr gut ist bei kurzen Eingaben weniger gut; Die Länge der Kodierung ist immer mindestens das Quadrat von (Strahlungsparameter + 1). Für sehr kurze Eingaben (Länge 1 bis 8) bei Strahlung 9 beträgt die Länge der Ausgabe dennoch 100. (Bei Länge 9 beträgt die Länge der Ausgabe manchmal 100 und manchmal 110.) Wiederholungsbasierte Ansätze schlagen diese Löschung deutlich -codierungsbasierter Ansatz für sehr kleine Eingaben; Möglicherweise lohnt es sich, je nach Größe der Eingabe zwischen mehreren Algorithmen zu wechseln.
Schließlich kommt es in der Wertung nicht wirklich vor, aber bei sehr hohen Strahlungsparametern ist es verschwenderisch, ein bisschen von jedem Byte (⅛ der Ausgabegröße) zur Begrenzung von Blöcken zu verwenden. Es wäre billiger, stattdessen Trennzeichen zwischen den Blöcken zu verwenden. Die Rekonstruktion der Blöcke aus Begrenzern ist schwieriger als mit dem Alternating-MSB-Ansatz, aber ich halte es für möglich, zumindest wenn die Daten ausreichend lang sind (bei kurzen Daten kann es schwierig sein, den Strahlungsparameter aus der Ausgabe abzuleiten). . Dies ist zu prüfen, wenn unabhängig von den Parametern ein asymptotisch idealer Ansatz angestrebt wird.
(Und natürlich könnte es einen völlig anderen Algorithmus geben, der bessere Ergebnisse liefert als dieser!)