Цель этой задачи - найти невероятно короткую реализацию следующей функции p
в выбранном вами языке. Вот код C, реализующий его (см.
Эту ссылку TIO, которая также печатает его выходные данные) и страницу википедии, содержащую его.
unsigned char pi[] = {
252,238,221,17,207,110,49,22,251,196,250,218,35,197,4,77,
233,119,240,219,147,46,153,186,23,54,241,187,20,205,95,193,
249,24,101,90,226,92,239,33,129,28,60,66,139,1,142,79,
5,132,2,174,227,106,143,160,6,11,237,152,127,212,211,31,
235,52,44,81,234,200,72,171,242,42,104,162,253,58,206,204,
181,112,14,86,8,12,118,18,191,114,19,71,156,183,93,135,
21,161,150,41,16,123,154,199,243,145,120,111,157,158,178,177,
50,117,25,61,255,53,138,126,109,84,198,128,195,189,13,87,
223,245,36,169,62,168,67,201,215,121,214,246,124,34,185,3,
224,15,236,222,122,148,176,188,220,232,40,80,78,51,10,74,
167,151,96,115,30,0,98,68,26,184,56,130,100,159,38,65,
173,69,70,146,39,94,85,47,140,163,165,125,105,213,149,59,
7,88,179,64,134,172,29,247,48,55,107,228,136,217,231,137,
225,27,131,73,76,63,248,254,141,83,170,144,202,216,133,97,
32,113,103,164,45,43,9,91,203,155,37,208,190,229,108,82,
89,166,116,210,230,244,180,192,209,102,175,194,57,75,99,182,
};
unsigned char p(unsigned char x) {
return pi[x];
}
Что такое p
p
является составной частью двух российских криптографических стандартов, а именно хеш-функции Streebog и блочного шифра Kuznyechik . В этой статье (и во время совещаний ISO) разработчики этих алгоритмов утверждали, что они генерировали массив pi
, выбирая случайные 8-битные перестановки.
«Невозможные» реализации
Есть перестановок на 8 бит. Следовательно, для данной случайной перестановки не следует ожидать, что программе, которая ее реализует, потребуется менее 1683 битов.
Однако мы нашли несколько аномально небольших реализаций (которые мы перечислим здесь ), например, следующую программу на C:
p(x){unsigned char*k="@`rFTDVbpPBvdtfR@\xacp?\xe2>4\xa6\xe9{z\xe3q5\xa7\xe8",l=0,b=17;while(--l&&x^1)x=2*x^x/128*285;return l%b?k[l%b]^k[b+l/b]^b:k[l/b]^188;}
который содержит только 158 символов и, таким образом, помещается в 1264 бит. Нажмите здесь, чтобы увидеть, что это работает.
Мы говорим о «невозможной» короткой реализации, потому что, если перестановка была результатом случайного процесса (как утверждают его разработчики), то такой короткой программы не существовало бы (см. Эту страницу для более подробной информации).
Ссылочная реализация
Более читаемая версия предыдущего кода C:
unsigned char p(unsigned char x){
unsigned char
s[]={1,221,146,79,147,153,11,68,214,215,78,220,152,10,69},
k[]={0,32,50,6,20,4,22,34,48,16,2,54,36,52,38,18,0};
if(x != 0) {
unsigned char l=1, a=2;
while(a!=x) {
a=(a<<1)^(a>>7)*29;
l++;
}
unsigned char i = l % 17, j = l / 17;
if (i != 0) return 252^k[i]^s[j];
else return 252^k[j];
}
else return 252;
}
Таблица k
такова, что k[x] = L(16-x)
, где L
линейно в том смысле, что L(x^y)==L(x)^L(y)
и где, как в C, ^
обозначает XOR. Однако нам не удалось использовать это свойство для сокращения нашей реализации. Мы не знаем ни о какой структуре, s
которая могла бы позволить более простую реализацию - хотя ее вывод всегда находится в подполе, то есть где возведение в степень выполняется в конечном поле. Конечно, вы можете свободно использовать более простое выражение, s
если найдете его!
Цикл while соответствует оценке дискретного логарифма в конечном поле с 256 элементами. Он работает с помощью простого перебора: фиктивная переменная a
задается как генератор конечного поля и умножается на этот генератор до тех пор, пока результат не будет равен x
. Когда это так, у нас l
есть это дискретный журнал x
. Эта функция не определена в 0, следовательно, это особый случай, соответствующий if
утверждению.
Умножение на генератор можно рассматривать как умножение на в которое затем уменьшается по модулю полинома . Роль unsigned char
заключается в том, чтобы гарантировать, что переменная a
остается на 8 битах. В качестве альтернативы мы могли бы использовать a=(a<<1)^(a>>7)*(256^29)
, в этом случае a
может быть int
(или любой другой целочисленный тип). С другой стороны, нужно начинать с того, l=1,a=2
что нужно, l=255
когда x
равен 1.
Более подробная информация о свойствах p
представлена в нашей статье с описанием большинства наших оптимизаций для получения предыдущей краткой реализации.
правила
Предложите программу, которая реализует функцию p
менее чем в 1683 битах. Чем короче программа, тем ненормальнее она для данного языка, тем короче, тем лучше. Если на вашем языке есть Kuznyechik, Streebog или p
как встроенный, вы не можете их использовать.
Метрика, которую мы используем для определения наилучшей реализации, - это длина программы в байтах. Мы используем длину в битах в нашей академической статье, но здесь для простоты мы придерживаемся байтов.
Если ваш язык не имеет четкого представления о функции, аргумента или вывода, кодирование до вас , чтобы определить, но приемы , такие как кодирующая значение , pi[x]
как x
, очевидно , запрещены.
Мы уже представили исследовательскую работу с нашими выводами по этой теме. Это доступно здесь . Однако, если она будет опубликована в научном центре, мы с радостью примем авторов лучших реализаций.
Кстати, спасибо xnor за помощь при составлении этого вопроса!
источник
1683 bits at most
это строгое ограничение [sic?] Или цель?Ответы:
Сборка AMD64 (78 байтов или 624 бит машинного кода)
64-битная сборка x86
Разобранный 64-битный код
32-битная сборка x86
Разобрали 32-битный код
источник
uint8_t
аргументы расширяются от нуля до 64-битных для JRCXZ). Кроме того, если вы пишете зависимый от позиции код, вы можете поместить адрес таблицы в регистр с 5-байтовымmov ebx, imm32
вместо 6-байтовогоcall
/pop
. Или использовать его в качествеdisp32
дюймаmov al, [table + rax]
, но это может потерять , так как у вас есть дваxlatb
иmov
уже. Трюк с call + pop шелкодом действительно выигрывает против 7-байтового REA-относительного LEA с данными послеret
, хотя.CJam ,
72676663 байтаes*
повторяет что-то по текущей метке времени, которая является большим числом, и это займет слишком много времени, чтобы закончить.Фактически тестируемая версия, 64 байта:
Попробуйте онлайн!
Попробуйте все онлайн!
Для выполнения этого кода (на машине Linux), вам нужно добавить строку
en_US.ISO-8859-1 ISO-8859-1
в/etc/locale.gen
и запуститьlocale-gen
. Тогда вы можете использовать:Или вы можете попробовать эту эквивалентную 72-байтовую версию UTF-8:
объяснение
Символы в строке:
источник
"Ý0$&Ü™ÖD�’\n˜×EO“N"
?Желе
7159 байтПопробуйте онлайн!
Проверьте все возможности
Теперь переписайте его, используя переработанную версию умного ответа CJam от jimmy23013, так что не забудьте также его подтвердить! Использует только 472 бита (28,0% от наивного подхода). @ jimmy23013 также сохранил еще один байт!
объяснение
Этап 1
Этап 2
Этап 3
Оригинальный подход
Желе ,
7166 байтПопробуйте онлайн!
Проверьте все возможности
Монадическая ссылка или полная программа, которая принимает один аргумент и возвращает соответствующее значение
pi[x]
. Это 536 бит, так что меньше трети наивного хранилища пи.Сэкономили 3 байта, используя метод для поиска ответа CJam
l
от jimmy23013, так что не забудьте также его подтвердить!объяснение
Этап 1
Этап 2
Этап 3
источник
C (gcc) ,
157 148 140139 байтСкромное улучшение по сравнению с примером C.
Попробуйте онлайн!
C (gcc) ,
150 142 127126 байтовЭто зависит от особенностей gcc, x86 и UTF-8.
Попробуйте онлайн!
Спасибо @XavierBonnetain за -1 и менее неопределенное поведение.
источник
05AB1E ,
10110098979594 байта-3 байта благодаря @Grimy .
Попробуйте онлайн или проверьте все контрольные примеры .
Объяснение:
Отсюда порт C-функции Ксавье Боннетейна (версия на 1106 битов) с тем же улучшением, которое @ceilingcat внес в свой ответ C, чтобы сэкономить 3 байта, так что не забудьте его оповестить!
Смотрите этот 05AB1E наконечник шахты (разделы Как сжать большие целые числа? И Как сжать целые списки? ) , Чтобы понять , почему
•α">η≠ε∍$<Θγ\&@(Σα•
это20576992798525946719126649319401629993024
;•α">η≠ε∍$<Θγ\&@(Σα•₅в
есть[64,96,114,70,84,68,86,98,112,80,66,118,100,116,102,82,64]
;Ƶ¹
есть285
;•¾#kôlb¸ù,-ó"a·ú•
есть930891775969900394811589640717060184
;•¾#kôlb¸ù,-ó"a·ú•₅в
есть[189,97,46,243,47,37,183,248,106,107,242,96,36,182,249]
; иƵ∞
есть188
.источник
s^
=>^
(XOR коммутативно). На самом деле, неs^_
так же, какQ
?i==0 || X==0 || X==1
.Stax ,
6564625958 байтЗапустите и отладьте его
К сожалению, эта программа использует некоторые инструкции, которые внутренне используют некоторые устаревшие инструкции stax. Я просто забыл обновить их реализацию. Это приводит к появлению ложного предупреждения, но результаты все еще верны.
Это вдохновлено отличным ответом jimmy23013 . Некоторые детали были изменены, чтобы лучше подходить для Stax.
Программы Stax, написанные на ASCII для печати, имеют альтернативное представление, которое сохраняет чуть более 1 бита на байт, поскольку существует только 95 печатаемых символов ASCII.
Вот представление ascii этой программы, отформатированное для «читабельности» с комментариями.
Запустите этот
Модифицированная версия для запуска для всех входов 0..255
источник
S
для набора мощности. Вы можете получить набор мощности [18 38 36 48], индексировать и уменьшить на xor. (Я не знаю, Стакс, и я не уверен, что это будет короче.)S
оператором, не в том порядке, чтобы это работало. Например,"abc"SJ
(powerset из «abc» соединяется с пробелами) создает «ab abc ac b bc c».Python 3 , 151 байт
Попробуйте онлайн!
Функция, которая реализует перестановку. Код использует только 7-битные символы ASCII.
Кодирует
k
как строку Python 3, сдвинутую^64
в диапазон для печати. Напротив,s
кодируется как базовые 256 цифр числовой константы, а цифры извлекаются как[number]>>[shift]*8&255
. Это было короче, чем кодированиеs
в строке из-за необходимого количества escape-символов, даже с оптимальным сдвигом,^160
чтобы минимизировать их.Вычисление дискретного логарифма выполняется в обратном направлении. Обновление
x=x*2^x//128*285
зацикливается в циклической группе, имитируя умножение на генерирование, пока оно не достигнет идентификатораx=1
. Мы начинаем дискретный лог вl=255
(длина цикла) и уменьшаем его на каждой итерации. Чтобы обработатьx=0
случай и сделать так, чтобы он не зацикливался вечно, мы также завершаем когдаl=0
, что делаетx=0
сопоставление сl=0
указанным.Python 2 проигрывает из-за отсутствия хороших строк байтов, поэтому мы должны это сделать
map(ord,...)
(ArBo сохранил здесь байт). Это позволяет нам использовать,/
а не//
для целочисленного деления.Python 2 , 156 байт
Попробуйте онлайн!
источник
JavaScript (ES6), 139 байт
Аналогично версии Node.js, но с использованием символов за пределами диапазона ASCII.
Попробуйте онлайн!
JavaScript (Node.js) ,
149148 байтОсновано на реализации C Ксавье Боннетейна (которая представлена здесь ).
Попробуйте онлайн!
кодирование
В исходном ответе Ксавье таблицы
s[]
иk[]
хранятся в следующей строке:Первые 17 символов являются представлениями ASCII,
k[i] XOR 64
а следующие 15 символов являются представлениями ASCIIs[i-17] XOR 173
илиs[i-17] XOR 64 XOR 17 XOR 252
.k[i] XOR 64
s[i-17] XOR 173
Вот что мы получаем:
110010011001001
NB: Это просто примечание, не связанное с ответами выше.
Попробуйте онлайн!
источник
C # (интерактивный компилятор Visual C #) , 141 байт
Просто порт примера решения, портированный на C #.
Попробуйте онлайн!
источник
Python 3 , 182 байта
Попробуйте онлайн!
Python не собирается выиграть первый приз здесь, но это все еще 10-байтовый гольф лучшей программы Python здесь .
Python 3 , 176 байт
Попробуйте онлайн!
Как лямбда, он еще на шесть байт короче. Мне больно пользоваться
if... else
, но я не вижу другого варианта для короткого замыкания, учитывая, что 0 является возможным ответом.Python 3 , 173 байта
Попробуйте онлайн!
Еще короче в байтах (хотя я не уверен насчет битов, потому что это больше не чистый ASCII), любезно предоставлено ovs.
источник
\x..
побеговРжавчина ,
170163 байтаПопробуйте онлайн!
Это порт моего решения в C с несколько иной строкой, который не требует xor 17. Я ожидаю, что большинство решений основано на строке "@` rFTDVbpPBvdtfR @ \ xacp? \ Xe2> 4 \ xa6 \ xe9 {z \ xe3q5 \ xa7 \ xe8 "также можно улучшить (просто измените строку, удалите xor 17 и xor 173 вместо 188).
Я удалил один из поисков, добавив
17*17
к немуl
, как мы (более или менее), в решении с машинным кодом ARM.У Rust есть логический тип и замыкания, но его приведения (даже для логических значений или между целыми числами) всегда явные, изменчивость должна быть отмечена, у нее нет тернарного оператора, целочисленные операции, по умолчанию, паника при переполнении и операции мутации (
l+=1
) возвращает единицу. Мне не удалось получить более короткое решение с итераторами, так как замыкания + отображение все еще довольно многословны.Это, кажется, делает Rust довольно плохим выбором для игры в гольф. Тем не менее, даже на языке, который подчеркивает удобочитаемость и безопасность, а не краткость, мы слишком коротки.
Обновление: использовалась анонимная функция, по предложению manatwork.
источник
let p=
в Header и не считать его. Не уверен насчет того;
, что для анонимного звонка это не нужно: попробуйте онлайн! ,05AB1E , 74 байта
Порт первого ответа желе @NickKennedy . Я работал над портом ответа CJam @jimmy23013 напрямую , но у меня уже было 78 байт, и мне все еще приходилось исправлять ошибку, поэтому она была бы больше. Это, безусловно, все еще можно сыграть в гольф совсем немного.
Попробуйте онлайн или проверьте все контрольные примеры .
Объяснение:
Смотрите этот 05AB1E наконечник шахты (разделы Как сжать большие целые числа? И Как сжать целые списки? ) , Чтобы понять , почему
Ƶf
это142
;•5›@¾ÂÌLìÁŒ©.ðǝš«YWǝŠ•
есть29709448685778434533295690952203992295278432248
,ƵŠ
есть239
; и•5›@¾ÂÌLìÁŒ©.ðǝš«YWǝŠ•ƵŠв
есть[19,48,36,38,18,238,87,24,138,206,92,197,196,86,25,139,129,93,128,207]
.источник