Фон
На этом сайте иногда возникают вопросы, требующие, чтобы программы были «радиационно-стойкими»; это означает, что программа должна выдерживать удаление одного или нескольких байтов, независимо от того, какие байты удалены.
Как это часто бывает для задач, которые часто задаются в задачах программирования, естественно, хочется создать язык, который особенно хорош в этих задачах. Учитывая, что естественным способом сделать это является добавление метаданных, позволяющих обратить вспять коррупцию, на самом деле это не язык, требующий проектирования, а кодирование; Идея состоит в том, чтобы преобразовать каждый вход в последовательность байтов таким образом, чтобы, даже если последовательность была слегка облучена, можно было извлечь исходный ввод.
Задание
Напишите две программы или функции, E (кодер) и D (декодер), такие, что:
- E принимает два аргумента, последовательность октетов (которые мы будем называть « вход » в этой спецификации) и неотрицательное целое число « излучение », и выводит последовательность октетов « кодирование »;
- D принимает один аргумент, последовательность октетов (« encdng ») и выводит последовательность октетов « реконструкция »;
- Если вы запустите и E, и D (с encdng , вход для D, выбранный путем удаления не более чем элементов излучения из кодирования (не обязательно непрерывно)), тогда восстановление будет равно вводу независимо от того, какие символы были удалены для формирования encdng .
Разъяснения
- Если вы отправляете функции, вам не нужно вызывать их
E
иD
; Вы можете выбрать любое имя, наиболее подходящее для вашего языка. - «Октет» - это, по сути, целое число от 0 до 255 включительно, которое вы можете кодировать как целое число, символ или все, что подходит для вашего языка.
- E и D должны быть полностью детерминированными (т. Е. Предоставление им одинаковых входных данных всегда будет давать один и тот же выходной сигнал, где «входные данные» определены как входные данные и излучение для E или encdng для D). В частности, E может не передавать информацию D через побочный канал.
- Удаление выполняется путем удаления одного элемента последовательности; Подумайте об открытии последовательности в редакторе, расположении курсора в произвольной точке и нажатии Backspace. Если элемент появляется несколько раз, возможно, будет удалена только одна копия элемента (т. Е. Другие экземпляры того же октета не будут затронуты).
- Хотя оценка рассчитывается только на основе довольно короткого ввода , ваша программа должна работать теоретически для любого ввода и излучения . В частности, он должен работать независимо от того, какие октеты появляются при вводе . (Извините, люди, которые хотели бы иметь возможность использовать непечатаемые символы, которые, как они знают, не появятся на входе, но я должен убедиться, что вход несжимаемый, так что проблема заключается в радиационном упрочнении, а не в сжатии.)
- Вы можете отправить либо один файл, который определяет две функции; два файла, каждый из которых определяет функцию или оба являются полными программами; или три файла, два из которых реализуют D и E соответственно (либо через полные программы, либо через определение функции), а третий - файл заголовка или библиотека, общие для D и E. Независимо от того, какую форму представления вы используете Ваша реализация на языке программирования должна понимать обе программы без дополнительных аргументов, таких как расположение файлов (иначе вы должны заплатить штраф за байты за необычный вызов вашей реализации в соответствии с нашими стандартными правилами).
Состояние победы
Для каждой длины и излучения пусть f ( длина , излучение ) будет полной длиной кодирования s, которая соответствует всем входным данным с длиной длины и данному излучению . (То есть f ( длина , излучение ) = сумма ввода имеет длину длина длина (E ( вход , излучение )).) Тогда пусть g ( длина , излучение ) будет равно f ( длина ,излучение ) ÷ 256 длина . Другими словами, g - это средняя длина кодированного выхода для заданной длины ввода и заданного требования радиационной стойкости. (Теоретически вы можете рассчитать это методом грубой силы, но, скорее всего, это займет невероятно много времени, чтобы рассчитать ваш счет таким образом. Я ожидаю, что большинство представлений смогут сделать математический аргумент относительно их оценки. Если вы ' Если вы не уверены, опубликуйте приблизительный балл, и вы или кто-то еще можете рассчитать его более подробно, если в другой записи будет аналогичный балл.)
Ваша оценка равна сумме g ( длина , излучение ) для всех излучений в диапазоне от 0 до 9 включительно и для всей длины в диапазоне от 0 до 99 включительно, плюс (в основном, чтобы избежать жесткого кодирования или для продолжения соревнования, если кто-то обнаружит математически совершенную кодировку (в противном случае это, вероятно, будет минимальный фактор) общее количество байтов в вашей подаче на вызов (плюс стандартные штрафы за такие вещи, как требование необычных флагов интерпретатора или конкретных имен файлов). Победителем становится запись с наименьшим количеством баллов (разбитая на первую запись для подачи).
Ответы:
CJam, оценка ≤ 286 516 + 54 + 36 = 286 606
кодировщик
Попробуйте онлайн!
дешифратор
Попробуйте онлайн!
Оба они берут и возвращают список целых чисел. Ссылки TIO включают преобразование из / в строки для удобства. Обратите внимание, что они невероятно неэффективны для длинных строк. Если вы хотите попробовать еще несколько символов, я рекомендую использовать символы с маленькими кодами символов.
Основная идея создания радиационно-стойкой кодировки состоит из двух этапов:
Таким образом, излучение не может полностью удалить один цикл идентичных символов, поэтому мы можем декодировать строку, беря один символ из каждого цикла, а затем декодировать шаг 1.
Таким образом, единственная интересная часть - это найти кодировку, которая никогда не даст повторяющихся октетов. Основная идея - использовать что-то вроде A043096 в качестве системы счисления. То есть, чтобы закодировать целое число N , мы просто считаем в некоторой базе b , пропуская все числа с повторяющимися октетами. Я полагаю, что количество чисел, которое может быть представлено таким образом с помощью до d цифр, равно количеству чисел, которое может быть представлено в биективном основании b-1 (поскольку, когда вы хотите написать такое число, вы можете выбирайте между цифрами b-1 для каждой позиции, не нарушая ограничения).
Конечно, чтобы получить максимальное сжатие, мы будем использовать b = 256 . Чтобы превратить ввод в целое число, мы также можем использовать базовое преобразование. Если бы я не был ленивым, я бы использовал биективную основу для ввода, но сейчас я просто добавляю
1
(чтобы убедиться, что нет ведущих нулей), а затем использую наименьшую возможную базу, чтобы все октеты в вход меньше базового.Эта база затем добавляется к кодированию (чтобы декодер знал, какую базу использовать) и отделяется от оставшегося числа 0-октетом (это работает, потому что оставшееся число никогда не начинается с нуля). В качестве незначительной оптимизации пустая строка остается пустой строкой.
Причина, по которой я не вычислил точную оценку выше, состоит в том, что я вычисляю только верхнюю границу того, как долго каждый вход будет основан на его длине и максимальном октете. Тем не менее, для этих двух параметров часто будут две разные выходные длины, и я пока не удосужился выяснить, где происходит переломный момент между ними. Я также использовал длину обычного основания 255 вместо биективного основания 255, чтобы оценить эту длину, которая снова немного больше, чем должна быть. Точный код Mathematica, который я использовал для вычисления, следующий:
num[l, b]
должен дать количество строк длиныl
с максимальным октетомb-1
(за исключением тогоb == 1
, где я жестко запрограммировал его,0
потому что я всегда использую хотя бы основание2
).источник
утилиты bash + GNU, оценка
294506283468Редактировать 1: исправляет проблему, замеченную @Leo - спасибо!
Редактировать 2: Улучшен метод кодирования для параметра излучения, для лучшей оценки.
Кодировщик (97 байт):
Декодер (121 байт):
Для кодера: последовательность октетов, переданная в виде символов в stdin, параметр излучения r, переданный в качестве аргумента.
Для декодера: ввод передается как символы в stdin.
Для обоих: вывод на стандартный вывод.
Кодировщик добавляет к входным данным цифры r, причем между каждой парой последовательных идентичных цифр вставляется символ «a», за которым следует одна новая строка. Затем он копирует все введенные данные (начиная с предварительно добавленных символов), заменяя каждый символ на r + 1 копию этого символа.
Декодер отменяет это, просматривая каждый из оставшихся символов x на своем входе, пропуская до r последовательных идентичных копий x после x и печатая то, что осталось. Предварительно добавленные данные не имеют повторяющихся символов, поэтому их можно декодировать до того, как станет известно r. В этот момент r известно, и это значение необходимо для правильного декодирования остальных данных.
Вы можете проверить, что это работает, даже если исходный ввод повторял одинаковые символы.
Расчет баллов:
Предположим, что вход имеет длину L и параметр излучения равен r (что не более 9 для вычисления оценки, поэтому он умещается в одну цифру и поэтому не имеет последовательных повторяющихся символов). Предварительно добавленные данные имеют размер 2 байта (цифра, новая строка), поэтому выходные данные для кодированного потока (r + 1) (L + 2) байта.
Итак, g (L, r) = (r + 1) (L + 2).
Отсюда следует, что общий балл может быть рассчитан как
источник
r
читать222
), но, к счастью, оценка рассчитывается только для излучений 0-9, поэтому это не окажет значительного влияния. PS Я думал о реализации той же кодировки, поэтому сразу заметил ошибку;)Perl + Math :: {ModInt, Polynomial, Prime :: Util}, оценка ≤ 92819
Управляющие изображения используются для представления соответствующего управляющего символа (например
␀
, это буквенный символ 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
Версия для гольфа с комментариями и тестовым кодом
Это оригинальная программа + переводы строк, отступы и комментарии. (На самом деле, версия для гольфа была создана путем удаления новых строк / отступов / комментариев из этой версии.)
Алгоритм
Упрощение проблемы
Основная идея состоит в том, чтобы свести эту проблему «кодирования удаления» (которая не является широко исследованной) к проблеме кодирования стирания (всесторонне изученной области математики). Идея кодирования стирания заключается в том, что вы готовите данные для отправки по «каналу стирания», каналу, который иногда заменяет отправляемые символы «искаженным» символом, который указывает на известную позицию ошибки. (Другими словами, всегда ясно, где произошло искажение, хотя первоначальный персонаж все еще неизвестен.) Идея, лежащая в основе этого, довольно проста: мы разделяем входные данные на блоки длины ( излучение+ 1) и использовать семь из восьми битов в каждом блоке для данных, в то время как оставшийся бит (в этой конструкции, MSB) чередуется между установленным для всего блока, очищенным для всего следующего блока, установленным для блока после этого и так далее. Поскольку блоки длиннее, чем параметр излучения, по крайней мере один символ из каждого блока остается на выходе; поэтому, взяв серии символов с одним и тем же MSB, мы можем определить, к какому блоку принадлежит каждый символ. Количество блоков также всегда больше, чем параметр излучения, поэтому у нас всегда есть хотя бы один неповрежденный блок в encdng; таким образом, мы знаем, что все блоки, которые являются самыми длинными или связаны с самыми длинными, не имеют повреждений, что позволяет нам рассматривать любые более короткие блоки как поврежденные (таким образом, мусор). Мы также можем вывести параметр излучения следующим образом:
Стирание кодирования
Что касается части проблемы, связанной с кодированием стирания, то здесь используется простой частный случай конструкции Рида-Соломона. Это систематическая конструкция: выходной сигнал (алгоритма кодирования стирания) равен входному значению плюс количество дополнительных блоков, равных параметру излучения. Мы можем вычислить значения, необходимые для этих блоков, простым (и гольфистским!) Способом, обрабатывая их как искаженные объекты, а затем запуская алгоритм декодирования для них, чтобы «восстановить» их значение.
Реальная идея построения также очень проста: мы подгоняем полином минимально возможной степени ко всем блокам кодирования (с искажением, интерполированным из других элементов); если многочлен f , первый блок f (0), второй f (1) и т. д. Понятно, что степень многочлена будет равна количеству входных блоков минус 1 (потому что сначала мы подгоняем полином к ним, а затем используем его для построения дополнительных «проверочных» блоков); и потому, что d +1 баллов однозначно определяют многочлен степени dпри искажении любого количества блоков (вплоть до параметра излучения) количество неповрежденных блоков останется равным исходному вводу, что достаточно для восстановления одного и того же полинома. (Тогда мы просто должны оценить многочлен, чтобы разгрузить блок.)
Базовая конверсия
Последнее оставленное здесь соображение связано с фактическими значениями, взятыми блоками; если мы выполняем полиномиальную интерполяцию по целым числам, результаты могут быть рациональными числами (а не целыми числами), намного большими, чем входные значения, или иным образом нежелательными. Таким образом, вместо использования целых чисел мы используем конечное поле; в этой программе используемое конечное поле представляет собой поле целых чисел по модулю p , где p - наибольшее простое число, меньшее 128 излучения +1(т. е. наибольшее простое число, для которого мы можем поместить несколько различных значений, равных этому простому числу, в часть данных блока). Большим преимуществом конечных полей является то, что деление (кроме 0) определяется однозначно и всегда будет давать значение в этом поле; таким образом, интерполированные значения полиномов будут помещаться в блок точно так же, как и входные значения.
Чтобы преобразовать входные данные в серию блочных данных, нам нужно выполнить базовое преобразование: преобразовать входные данные из базового 256 в число, а затем преобразовать в базовое p (например, для параметра излучения 1 мы имеем p= 16381). В основном это было вызвано отсутствием в Perl подпрограмм преобразования баз (у Math :: Prime :: Util есть некоторые, но они не работают для баз bignum, и некоторые простые числа, с которыми мы здесь работаем, невероятно велики). Поскольку мы уже используем Math :: Polynomial для полиномиальной интерполяции, я смог использовать его как функцию «преобразования из последовательности цифр» (просматривая цифры как коэффициенты полинома и оценивая его), и это работает для больших чисел просто хорошо. Если пойти по другому пути, мне пришлось написать эту функцию самому. К счастью, это не слишком сложно (или многословно), чтобы написать. К сожалению, это базовое преобразование означает, что входные данные обычно отображаются нечитаемыми. Есть также проблема с ведущими нулями;
Следует отметить, что у нас не может быть более 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.) Подходы, основанные на повторениях, явно превосходят это стирание. -подход на основе кодирования на очень маленьких входах; возможно, стоило бы переключаться между несколькими алгоритмами в зависимости от размера ввода.
Наконец, это не совсем подходит для оценки, но при очень высоких параметрах излучения использование каждого байта (size выходного размера) для разделения блоков является расточительным; вместо этого было бы дешевле использовать разделители между блоками. Реконструировать блоки из разделителей довольно сложно, чем при использовании метода чередующихся MSB, но я считаю, что это возможно, по крайней мере, если данные достаточно длинные (при коротких данных может быть сложно вывести параметр излучения из выходного сигнала) , Это было бы на что посмотреть, если мы стремимся к асимптотически идеальному подходу независимо от параметров.
(И, конечно, может быть совершенно другой алгоритм, который дает лучшие результаты, чем этот!)
источник