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) і використовувати сім з восьми біт у кожному блоці для отримання даних, тоді як решта біт (у цій конструкції, MSB) чергується між встановленням для цілого блоку, зрозумілим для всього наступного блоку, встановленим для блоку після цього тощо. Оскільки блоки довші, ніж параметр випромінювання, щонайменше один символ з кожного блоку виживає на виході; тож, беручи пробіги символів з тим самим MSB, ми можемо розібратися, до якого блоку належав кожен символ. Кількість блоків також завжди більша, ніж параметр випромінювання, тому ми завжди маємо принаймні один непошкоджений блок у encdng; Таким чином, ми знаємо, що всі блоки, які є найдовшими або зв'язані довше, не пошкоджені, що дозволяє нам обробляти будь-які короткі блоки як пошкоджені (таким чином, пошкодження). Ми також можемо вивести такий параметр випромінювання, як цей (він '
Кодування стирання
Що стосується частини проблеми, що кодує стирання, то тут використовується простий спеціальний випадок конструкції Ріда-Соломона. Це систематична побудова: вихід (алгоритму кодування стирання) дорівнює входу плюс ряд додаткових блоків, рівний параметру випромінювання. Ми можемо обчислити значення, необхідні для цих блоків, простим (і гольф!) Способом, трактуючи їх як шаблони, а потім запустивши на них алгоритм декодування, щоб "реконструювати" їх значення.
Справжня ідея, що стоїть за конструкцією, також дуже проста: ми підключаємо поліном мінімально можливого ступеня до всіх блоків в кодуванні (з шаблонами, інтерпольованими з інших елементів); якщо многочлен f , перший блок - f (0), другий - f (1) тощо. Зрозуміло, що ступінь многочлена буде дорівнює кількості блоків введення мінус 1 (тому що ми підходимо поліном до тих, хто спочатку, а потім використовуємо його для побудови додаткових «перевіряючих» блоків); і оскільки д +1 точки однозначно визначають многочлен ступеня г, ушивання будь-якої кількості блоків (до параметра випромінювання) залишить кількість непошкоджених блоків, рівних вихідному входу, що є достатньою інформацією для відновлення того ж многочлена. (Тоді ми просто повинні оцінити поліном, щоб не відміняти блок.)
Базова конверсія
Остаточний розгляд, що залишився тут, стосується фактичних значень, прийнятих блоками; якщо ми робимо поліноміальну інтерполяцію на цілі числа, результати можуть бути раціональними числами (а не цілими числами), набагато більшими, ніж вхідні значення, або інакше небажаними. Таким чином, замість цілих чисел ми використовуємо скінченне поле; у цій програмі використовуваним кінцевим полем є поле цілих чисел по модулю p , де р - найбільший простір менше 128 випромінювання +1(тобто найбільший простір, для якого ми можемо помістити ряд чітких значень, рівних цьому простим, у частину даних блоку). Великою перевагою кінцевих полів є те, що поділ (за винятком 0) є однозначно визначеним і завжди створюватиме значення в цьому полі; таким чином, інтерпольовані значення поліномів помістяться в блок точно так само, як і вхідні значення.
Для того, щоб перетворити вхід в ряд даних блоку, тоді нам потрібно зробити перетворення бази: перетворити вхід з бази 256 в число, потім перетворити в базу p (наприклад, для параметра випромінювання 1, маємо p= 16381). Це здебільшого стримувалося відсутністю підпрограми перетворення базових програм Perl (Math :: Prime :: Util має деякі, але вони не працюють для баз bignum, і деякі з праймів, з якими ми тут працюємо, неймовірно великі). Оскільки ми вже використовуємо Math :: Polynomial для поліноміальної інтерполяції, я зміг повторно використовувати його як функцію "перетворити з цифрної послідовності" (переглянувши цифри як коефіцієнти полінома та оцінивши його), і це працює для bignums просто добре. Йдучи іншим шляхом, я мусив сам писати функцію. На щастя, писати не дуже складно (або багатослівно). На жаль, це базове перетворення означає, що введення зазвичай стає нечитабельним. Існує також проблема з першими нулями;
Слід зазначити, що у виході ми не можемо мати більше p блоків (інакше індекси двох блоків стали б рівними, але, можливо, потрібно отримати різні виходи з многочлена). Це відбувається лише тоді, коли вхід надзвичайно великий. Ця програма вирішує проблему дуже простим способом: збільшуючи радіацію (це робить блоки більші, а p значно більшими, це означає, що ми можемо помістити набагато більше даних і що явно призводить до правильного результату).
Ще один момент, який варто зробити, це те, що ми кодуємо нульову рядок собі, тому що програма, як написано, на неї вийде з ладу. Це також очевидно найкраще можливе кодування, і воно працює незалежно від параметрів випромінювання.
Потенційні поліпшення
Основна асимптотична неефективність у цій програмі полягає у використанні модулю-прайме як кінцевих полів, про які йдеться. Кінцеві поля розміром 2 n існують (це саме те, що ми хотіли б тут, оскільки розміри корисної навантаження блоків, природно, є потужністю 128). На жаль, вони є більш складними, ніж проста модульна конструкція, тобто Math :: ModInt не вирізав би її (і я не міг знайти жодної бібліотеки на CPAN для обробки обмежених полів непрості розміри); Я мав би написати цілий клас із перевантаженою арифметикою для Math :: Polynomial, щоб мати змогу впоратися з цим, і в цей момент вартість байта може потенційно перевищувати (дуже малі) втрати від використання, наприклад, 16381, а не 16384.
Ще одна перевага використання розмірів потужності-2 полягає в тому, що базове перетворення стане набагато простішим. Однак у будь-якому випадку кращим методом представлення довжини введення був би корисний; метод "додати 1 в неоднозначних випадках" простий, але марнотратний. Перетворення біективної бази - це один правдоподібний підхід (ідея полягає в тому, що ви маєте базу як цифру, а 0 як не цифру, так що кожне число відповідає одному рядку).
Хоча асимптотична ефективність цього кодування дуже хороша (наприклад, для вводу довжиною 99 та параметром випромінювання 3, кодування завжди має довжину 128 байт, а не ~ 400 байт, які отримали б підходи, засновані на повторенні), його продуктивність менш хороший на коротких входах; довжина кодування завжди не менше квадрата (параметр випромінювання + 1). Отже, для дуже коротких входів (довжина від 1 до 8) при випромінюванні 9 довжина виходу, тим не менш, становить 100. (При довжині 9 довжина виходу іноді становить 100, а іноді і 110.) Підходи, засновані на повторенні, чітко перемагають це стирання - підхід на основі кодування на дуже малих входах; можливо, варто змінити між декількома алгоритмами залежно від розміру вхідних даних.
Нарешті, це не дійсно приходить в бал, але з дуже високими параметрами випромінювання, використання трохи кожного байта (⅛ вихідного розміру) для розмежування блоків є марним; дешевше було б використовувати роздільники між блоками. Реконструювати блоки з роздільників досить важче, ніж із підходом змінної MSB, але я вважаю, що це можливо, принаймні, якщо дані є досить довгими (з короткими даними, може бути важко вивести параметр випромінювання з виходу) . Це було б на що слід звернути увагу, якщо прагнути до асимптотично ідеального підходу незалежно від параметрів.
(І звичайно, може бути зовсім інший алгоритм, який дає кращі результати, ніж цей!)