Perl + Math :: {ModInt, Polinom, Başbakan :: Util}, skor 28 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/^␁//;$_}
Kontrol resimleri, karşılık gelen kontrol karakterini temsil etmek için kullanılır (örneğin ␀
, bir değişmez NUL karakteridir). Kodu okumaya çalışmayla ilgili endişelenmeyin; aşağıda daha okunabilir bir sürümü var.
İle çalıştırın -Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all
. -MMath::Bigint=lib,GMP
gerekli değildir (ve dolayısıyla skora dahil edilmez), ancak diğer kütüphanelerden önce eklerseniz, programın biraz daha hızlı çalışmasını sağlar.
Puan hesaplama
Buradaki algoritma biraz geliştirilebilir, ancak yazmak daha zor olacaktır (Perl'in uygun kütüphanelere sahip olmaması nedeniyle). Bu nedenle, kodda baytların kaydedilebileceği göz önüne alındığında, kodda birkaç boyut / verimlilik dengesi yaptım, golfün her noktasını tıraş etmeye çalışmanın bir anlamı yok.
Program, 600 bayt kod ve ayrıca komut satırı seçenekleri için 78 bayt cezadan oluşur ve 678 puan ceza verir. Skorun geri kalanı, 0 ila 99 arasındaki her uzunluk ve 0 ila 9 arasındaki her radyasyon seviyesi için programın en iyi durumdaki ve en kötü durumdaki (çıkış uzunluğu açısından) dizesinde çalıştırılmasıyla hesaplanmıştır; ortalama vaka aradaki bir yerde ve bu da skorun sınırlarını veriyor. (Benzer bir puanla başka bir giriş gelmediği sürece kesin değeri hesaplamaya çalışmak değmez.)
Bu nedenle, kodlama verimliliğinden alınan puanın 91100 ila 92141 dahil olduğu anlamına gelir, bu nedenle nihai puan:
91100 + 600 + 78 = 91778 ≤ skor ≤ 92819 = 92141 + 600 + 78
Yorumlar ve test koduyla daha az golf oynayan versiyon
Bu orijinal program + yeni satırlar, girinti ve yorumlar. (Aslında, golf edilmiş sürüm bu sürümden satırsonu / girinti / yorum kaldırılarak üretildi.)
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;
Algoritma
Sorunu basitleştirme
Temel fikir, bu "silme kodlama" problemini (yaygın olarak araştırılan bir sorun değildir) bir silme kodlama problemine (kapsamlı olarak keşfedilen bir matematik alanı) indirmektir. Silme kodlamasının ardındaki fikir, bazen gönderdiği karakterleri bir hatanın bilinen konumunu gösteren "garble" karakteriyle değiştiren bir kanal olan "silme kanalı" üzerinden gönderilecek verileri hazırlamanızdır. (Başka bir deyişle, orijinal karakter hala bilinmemekle birlikte, yolsuzluğun nerede olduğu her zaman açıktır.) Bunun arkasındaki fikir oldukça basittir: girişi uzunluk bloklarına ( radyasyon) bölüyoruz+ 1) kullanın ve veri için her bloktaki sekiz bitin yedisini kullanın; kalan bit (bu yapımda MSB), tüm blok için ayarlanmak, blok için ayarlanan bir sonraki tüm blok için temizlenmek arasında değişir. ondan sonra, vb. Bloklar radyasyon parametresinden daha uzun olduğu için, her bloktan en az bir karakter çıktıda kalır; böylece aynı MSB'ye sahip karakterleri çalıştırarak, her karakterin hangi bloğa ait olduğunu bulabiliriz. Blokların sayısı da her zaman radyasyon parametresinden daha fazladır, bu nedenle her zaman enkodda en az bir hasarsız blok vardır; bu nedenle en uzun veya en uzun süre bağlı tüm blokların hasarsız olduğunu biliyoruz, böylece daha kısa blokları hasarlı (böylece bir garble) olarak tedavi etmemize izin veriyoruz. Ayrıca radyasyon parametresini şu şekilde çıkarabiliriz ('
Silme kodlaması
Sorunun silinme kodlama kısmına gelince, bu, Reed-Solomon yapısının basit bir özel durumunu kullanır. Bu sistematik bir yapıdır: çıktı (silme kodlama algoritmasının) girişe ve radyasyon parametresine eşit bir dizi ekstra bloğa eşittir. Bu bloklar için gereken değerleri basit (ve golfçü!) Bir şekilde, onları çile muamelesi yaparak, sonra değerlerini "yeniden yapılandırmak" için kod çözme algoritmasını çalıştırarak hesaplayabiliriz.
Yapının arkasındaki gerçek fikir de çok basittir: kodlamadaki tüm bloklara (diğer elemanlardan enterpolasyonlu çelenklerle) mümkün olan en düşük derecede bir polinom yerleştiriyoruz; eğer polinom f ise, ilk blok f (0), ikincisi f (1), vb. Polinom derecesinin giriş eksi 1'in blok sayısına eşit olacağı açıktır (çünkü önce bir polinomu yerleştiririz, daha sonra ekstra "kontrol" bloklarını oluşturmak için kullanırız); ve d +1 puanları benzersiz olarak d derece polinomunu tanımlar, herhangi bir sayıda bloğun (radyasyon parametresine kadar) garberleştirilmesi, aynı polinomu yeniden inşa etmek için yeterli bilgi olan orijinal girişe eşit sayıda hasarsız blok bırakacaktır. (O zaman sadece bir bloğu çözmek için polinomu değerlendirmeliyiz.)
Temel dönüşüm
Burada kalan son husus, bloklar tarafından alınan gerçek değerlerle ilgilidir; tamsayılarda polinom enterpolasyonu yaparsak, sonuçlar rasyonel sayılar (tamsayılardan ziyade), giriş değerlerinden çok daha büyük veya istenmeyen bir sonuç olabilir. Bu nedenle, tamsayıları kullanmak yerine sonlu bir alan kullanırız; Bu programda, kullanılan sonlu alan modulo p tamsayıları alanıdır , burada p 128 radyasyondan küçük en büyük asaldır +1(yani, bir bloğun veri kısmına o prime eşit sayıda farklı değer sığdırabileceğimiz en büyük asal). Sonlu cisimlerin en büyük avantajı, bölünmenin (0 hariç) benzersiz bir şekilde tanımlanmış olması ve bu alanda daima bir değer üretmesidir; dolayısıyla, polinomların enterpolasyonlu değerleri, giriş değerlerinin yaptığı gibi bir bloğa sığacaktır.
Veri bloğu bir dizi giriş dönüştürmek amacıyla, o zaman, taban dönüşüm yapmak gerekir: bu durumda baz dönüştürmek, bir dizi halinde bir baz 256 girdi dönüştürmek p , örneğin bir için ( radyasyon 1 parametresi, elimizdeki p= 16381). Bu çoğunlukla Perl'in temel dönüşüm rutinleri eksikliğinden kaynaklandı (Math :: Prime :: Util bazılarına sahiptir, ancak bignum bazları için çalışmazlar ve burada çalıştığımız bazı primler inanılmaz derecede büyüktür). Zaten polinom enterpolasyonu için Math :: Polynomial'ı kullandığımız için, bunu bir "basamak dizisinden dönüştür" işlevi olarak tekrar kullanabildim (basamakları bir polinomun katsayıları olarak görüp değerlendirerek) ve bu bignumlar için çalışıyor sadece iyi. Diğer taraftan, işlevi kendim yazmak zorunda kaldım. Neyse ki, yazmak çok zor değil (veya ayrıntılı). Ne yazık ki, bu temel dönüşüm, girdinin tipik olarak okunamaz hale getirildiği anlamına gelir. Baştaki sıfırlarla ilgili bir sorun da var;
Çıktıda p'den fazla bloğa sahip olamayacağımız not edilmelidir (aksi takdirde iki bloğun indeksleri eşit olur ve yine de polinomdan farklı çıktılar üretmesi gerekir). Bu yalnızca giriş çok büyük olduğunda gerçekleşir. Artan: Bu program çok basit bir yolu sorunu çözer radyasyon (bloklar daha büyük ve yapar p biz doğru sonuca çok daha verileri ve net bir şekilde yol sığabilecek anlamına çok daha büyük).
Yapmaya değer bir diğer nokta ise, boş dizeyi kendisine kodlamamızdır, çünkü yazılı olarak program aksi takdirde çökecektir. Aynı zamanda mümkün olan en iyi kodlamadır ve radyasyon parametresi ne olursa olsun çalışır.
Potansiyel iyileştirmeler
Bu programdaki ana asimtotik verimsizlik, söz konusu sonlu alanlar olarak modulo-prime kullanımı ile ilgilidir. 2 n büyüklüğünde sonlu alanlar mevcuttur (bu tam olarak burada istediğimiz şeydir, çünkü blokların taşıma yükü boyutları doğal olarak 128 güçtür). Ne yazık ki, basit bir modulo yapısından daha karmaşıktırlar, yani Math :: ModInt onu kesmez (ve asal olmayan boyutların sonlu alanlarını işlemek için CPAN'da herhangi bir kütüphane bulamadım); Math :: Polynomial ile başa çıkabilmek için aşırı yüklenmiş aritmetik ile tüm bir sınıf yazmak zorundayım ve bu noktada bayt maliyeti, 16384 yerine 16381 yerine (çok küçük) kaybın üzerinde olabilir.
2 güç boyutu kullanmanın bir diğer avantajı, temel dönüşümün çok daha kolay hale gelmesidir. Bununla birlikte, her iki durumda da, girdinin uzunluğunu temsil etmek için daha iyi bir yöntem yararlı olacaktır; "belirsiz durumlarda 1 başına" yöntemi basit ama israf. Bijektif taban dönüşümü burada mantıklı bir yaklaşımdır (fikir, tabana rakam olarak değil, rakam olarak 0'a sahip olmanızdır, böylece her sayı tek bir dizeye karşılık gelir).
Bu kodlamanın asimptotik performansı çok iyi olmasına rağmen (örneğin 99 uzunluğundaki bir giriş ve 3 radyasyon parametresi için, kodlama her zaman tekrarlanan yaklaşımların alacağı ~ 400 bayt yerine 128 bayt uzunluğundadır), performansı kısa girdilerde daha az iyidir; kodlamanın uzunluğu daima en azından (radyasyon parametresi + 1) karesidir. Bu nedenle radyasyon 9'daki çok kısa girişler (uzunluk 1 ila 8) için, çıkışın uzunluğu yine de 100'dür. (Uzunluk 9'da, çıkışın uzunluğu bazen 100 ve bazen 110'dur.) Tekrar tabanlı yaklaşımlar bu silmeyi açıkça yener. - çok küçük girdilere kodlama tabanlı yaklaşım; girişin boyutuna bağlı olarak birden fazla algoritma arasında değişmeye değer olabilir.
Son olarak, skorlamada gerçekten ortaya çıkmaz, ancak çok yüksek radyasyon parametreleriyle, blokları sınırlamak için her bayttan biraz (çıktı boyutunun)) kullanılması israftır; bunun yerine bloklar arasında sınırlayıcılar kullanmak daha ucuz olacaktır. Blokları sınırlayıcılardan yeniden oluşturmak, alternatif MSB yaklaşımından daha zordur, ancak en azından veriler yeterince uzunsa (kısa verilerle, radyasyon parametresini çıktıdan çıkarmak zor olabilir) mümkün olduğuna inanıyorum. . Parametrelere bakılmaksızın asimptotik olarak ideal bir yaklaşımı hedefliyorsa bakılması gereken bir şey olurdu.
(Ve elbette, bundan daha iyi sonuçlar veren tamamen farklı bir algoritma olabilir!)