Celem tego wyzwania jest znalezienie niemożliwie krótkiej realizacji następującej funkcji p
, w wybranym przez ciebie języku. Oto kod C implementujący go (zobacz
ten link TIO, który również drukuje jego wyniki) i zawierającą go stronę wikipedii .
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];
}
Co jest p
p
jest składnikiem dwóch rosyjskich standardów kryptograficznych, mianowicie funkcji skrótu Streebog i szyfru blokowego Kuźnyechik . W tym artykule (i podczas spotkań ISO) projektanci tych algorytmów twierdzili, że wygenerowali tablicę pi
, wybierając losowe permutacje 8-bitowe.
Implementacje „niemożliwe”
Jest permutacji na 8 bitach. Dlatego dla danej losowej permutacji program, który ją implementuje, nie powinien wymagać mniej niż 1683 bitów.
Znaleźliśmy jednak wiele nienormalnie małych implementacji (które wymieniliśmy tutaj ), na przykład następujący program 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;}
który zawiera tylko 158 znaków, a zatem mieści się w 1264 bitach. Kliknij tutaj, aby zobaczyć, czy to działa.
Mówimy o „niemożliwie” krótkiej implementacji, ponieważ jeśli permutacja byłaby wynikiem losowego procesu (jak twierdzą jej projektanci), to taki program nie istniałby ( więcej szczegółów na tej stronie ).
Realizacja referencyjna
Bardziej czytelna wersja poprzedniego kodu C to:
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;
}
Tabela k
jest taka, że k[x] = L(16-x)
gdzie L
jest liniowy w tym sensie L(x^y)==L(x)^L(y)
, i gdzie, podobnie jak w C, ^
oznacza XOR. Nie udało nam się jednak wykorzystać tej właściwości, aby skrócić nasze wdrożenie. Nie jesteśmy świadomi żadnej struktury, s
która mogłaby pozwolić na prostszą implementację --- jednak jej dane wyjściowe są zawsze w polu podrzędnym, tj. gdzie potęgowanie odbywa się w polu skończonym. Oczywiście masz absolutną swobodę w stosowaniu prostszego wyrażenia, jeśli go znajdziesz!s
Pętla while odpowiada ocenie dyskretnego logarytmu w polu skończonym z 256 elementami. Działa poprzez proste wyszukiwanie siłowe: zmienna fikcyjna a
jest ustawiona na generator pola skończonego i jest mnożona przez ten generator, aż wynik będzie równy x
. W takim przypadku mamy do czynienia l
z dyskretnym dziennikiem x
. Ta funkcja nie jest zdefiniowana w 0, stąd specjalny przypadek odpowiadający if
instrukcji.
Mnożenie przez generator może być postrzegane jako mnożenie przez w który jest następnie redukowany modulo do wielomianu . Rolą tego jest zapewnienie, że zmienna pozostaje na 8 bitach. Alternatywnie, możemy użyć , w którym to przypadku może być (lub dowolny inny typ całkowity). Z drugiej strony należy zacząć od tego, co musimy mieć, gdy jest równe 1.unsigned char
a
a=(a<<1)^(a>>7)*(256^29)
a
int
l=1,a=2
l=255
x
Więcej szczegółów na temat właściwości p
przedstawiono w naszym artykule , z podsumowaniem większości naszych optymalizacji w celu uzyskania poprzedniej krótkiej implementacji.
Zasady
Zaproponuj program, który implementuje funkcję p
w mniej niż 1683 bitach. Im krótszy program, tym bardziej nienormalny, dla danego języka, krótszy jest lepszy. Jeśli twój język ma Kuznyechik, Streebog lub p
jako wbudowany, nie możesz ich używać.
Miarą używaną do określenia najlepszej implementacji jest długość programu w bajtach. W naszej pracy naukowej używamy długości bitów, ale dla uproszczenia trzymamy się tu bajtów.
Jeśli język nie ma jasnego pojęcia funkcji, argumentu lub wyjścia, kodowanie jest do ciebie, aby określić, ale sztuczki jak kodowania wartości pi[x]
jak x
są oczywiście zabronione.
Przedłożyliśmy już dokument badawczy z naszymi ustaleniami na ten temat. Jest dostępny tutaj . Jeśli jednak zostanie opublikowany w miejscu naukowym, chętnie uznamy autorów najlepszych wdrożeń.
Nawiasem mówiąc, dzięki xnor za pomoc w przygotowaniu tego pytania!
źródło
1683 bits at most
to ścisłe ograniczenie [sic?] Czy cel?Odpowiedzi:
Zestaw AMD64 (78 bajtów lub 624 bitów kodu maszynowego)
64-bitowy zestaw x86
Zdemontowany 64-bitowy kod
32-bitowy zestaw x86
Zdemontowany 32-bitowy kod
źródło
uint8_t
argumenty są rozszerzone zera do 64-bit dla JRCXZ). Ponadto, jeśli napiszesz kod zależny od pozycji, możesz umieścić adres tabeli w rejestrze z 5 bajtamimov ebx, imm32
zamiast 6 bajtówcall
/pop
. Lub wykorzystać go jakodisp32
INmov al, [table + rax]
, ale że może stracić skoro masz dwaxlatb
amov
już. Jednak sztuczka call + pop shellcode wygrywa w porównaniu z 7-bajtowym LEA zależnym od RIP z danymi poret
.CJam ,
72676663 bajtyes*
powtarza coś według aktualnego znacznika czasu, który jest dużą liczbą, a jego ukończenie zajęłoby zbyt dużo czasu.Rzeczywista wersja testowa, 64 bajty:
Wypróbuj online!
Wypróbuj wszystkie online!
Aby uruchomić ten kod (na komputerze z systemem Linux), musisz dodać linię
en_US.ISO-8859-1 ISO-8859-1
do/etc/locale.gen
i uruchomićlocale-gen
. Następnie możesz użyć:Lub możesz wypróbować tę równoważną 72-bajtową wersję UTF-8:
Wyjaśnienie
Znaki w ciągu to:
źródło
"Ý0$&Ü™ÖD�’\n˜×EO“N"
?Galaretka
7159 bajtówWypróbuj online!
Sprawdź wszystkie możliwości
Teraz przepisane przy użyciu przerobionej wersji sprytnej odpowiedzi CJam jimmy23013, więc pamiętajcie o tym! Używa tylko 472 bitów (28,0% naiwnego podejścia). @ jimmy23013 również zapisał kolejny bajt!
Wyjaśnienie
Scena 1
Etap 2
Etap 3
Oryginalne podejście
Galaretka ,
7166 bajtówWypróbuj online!
Sprawdź wszystkie możliwości
Monadyczny link lub pełny program, który pobiera pojedynczy argument i zwraca odpowiednią wartość
pi[x]
. Jest to 536 bitów, czyli mniej niż jedna trzecia naiwnego przechowywania pi.Zapisane 3 bajty za pomocą metody znajdowania
l
od jimmy23013 za CJam odpowiedź więc należy upvote że jeden zbyt!Wyjaśnienie
Scena 1
Etap 2
Etap 3
źródło
C (gcc) ,
157 148 140139 bajtówNiewielka poprawa w stosunku do przykładu C.
Wypróbuj online!
C (gcc) ,
150 142 127126 bajtówTo zależy od dziwactwa gcc i x86 oraz UTF-8.
Wypróbuj online!
Dzięki @XavierBonnetain za -1 i mniej niezdefiniowane zachowanie.
źródło
05AB1E ,
10110098979594 bajtów-3 bajty dzięki @Grimy .
Wypróbuj online lub sprawdź wszystkie przypadki testowe .
Wyjaśnienie:
Stąd funkcja C portu Xaviera Bonnetaina (wersja 1106 bitów) , z tym samym ulepszeniem @ceilingcat wprowadzonym w jego odpowiedzi C, aby zaoszczędzić 3 bajty, więc pamiętaj, aby go głosować!
Zobacz tę końcówkę 05AB1E kopalni (sekcje Jak skompresować dużych liczb całkowitych? I jak skompresować list całkowitych? ) , Aby zrozumieć, dlaczego
•α">η≠ε∍$<Θγ\&@(Σα•
jest20576992798525946719126649319401629993024
;•α">η≠ε∍$<Θγ\&@(Σα•₅в
jest[64,96,114,70,84,68,86,98,112,80,66,118,100,116,102,82,64]
;Ƶ¹
jest285
;•¾#kôlb¸ù,-ó"a·ú•
jest930891775969900394811589640717060184
;•¾#kôlb¸ù,-ó"a·ú•₅в
jest[189,97,46,243,47,37,183,248,106,107,242,96,36,182,249]
; iƵ∞
jest188
.źródło
s^
=>^
(XOR jest przemienny). Właściwie to nies^_
to samo coQ
?i==0 || X==0 || X==1
.Stax ,
6564625958 bajtówUruchom i debuguj
Niestety, ten program używa instrukcji, które wewnętrznie używają niektórych przestarzałych instrukcji stax. Właśnie zapomniałem zaktualizować ich implementację. Powoduje to wyświetlenie fałszywych ostrzeżeń, ale wyniki są nadal poprawne.
Jest to inspirowane doskonałą odpowiedzią jimmy23013 . Niektóre części zostały zmienione, aby lepiej pasowały do stax.
Programy Stax napisane w drukowanym ASCII mają alternatywną reprezentację, która zapisuje nieco więcej niż 1 bit na bajt, ponieważ jest tylko 95 drukowanych znaków ASCII.
Oto reprezentacja ascii tego programu sformatowana pod kątem „czytelności” z komentarzami.
Uruchom ten
Zmodyfikowana wersja do uruchomienia dla wszystkich wejść 0..255
źródło
S
zestaw mocy. Możesz uzyskać zestaw mocy [18 38 36 48], indeksować i zmniejszać o xor. (Nie znam Staxa i nie jestem pewien, czy byłby krótszy.)S
operatora nie jest w odpowiedniej kolejności, aby działało. np."abc"SJ
(zestaw „abc” połączony ze spacjami) daje „a ab abc ac b bc c”.Python 3 , 151 bajtów
Wypróbuj online!
Funkcja, która implementuje permutację. Kod używa tylko 7-bitowych znaków ASCII.
Koduje się
k
jako bajtowanie Pythona 3, przeniesione^64
do zakresu do wydrukowania. Natomiasts
jest zakodowany jako podstawa-256 cyfr stałej numerycznej, a cyfry są wyodrębniane jako[number]>>[shift]*8&255
. Było to krótsze niż kodowanies
w ciągu ze względu na wymaganą liczbę znaków specjalnych, nawet przy optymalnym przesunięciu,^160
aby je zminimalizować.Obliczenia w dzienniku dyskretnym są wykonywane wstecz. Aktualizacja
x=x*2^x//128*285
zapętla się w grupie cyklicznej, symulując pomnożenie przez generator, aż osiągnie tożsamośćx=1
. Log dyskretny rozpoczynamy odl=255
(długości cyklu) i zmniejszamy go przy każdej iteracji. Aby obsłużyćx=0
sprawę i sprawić, aby nie zapętlała się na zawsze, kończymy również kiedyl=0
, co powoduje, żex=0
mapowanie dol=0
określonego.Python 2 przegrywa z brakiem miłych testów, więc musimy to zrobić
map(ord,...)
(ArBo zapisał tutaj bajt). To pozwala nam używać/
raczej niż//
do dzielenia liczb całkowitych.Python 2 , 156 bajtów
Wypróbuj online!
źródło
JavaScript (ES6), 139 bajtów
Podobne do wersji Node.js, ale przy użyciu znaków spoza zakresu ASCII.
Wypróbuj online!
JavaScript (Node.js) ,
149148 bajtówNa podstawie implementacji C Xaviera Bonnetaina (która jest tutaj przedstawiona ).
Wypróbuj online!
Kodowanie
W oryginalnej odpowiedzi Xaviera tabele
s[]
ik[]
są przechowywane w następującym ciągu:Pierwsze 17 znaków to reprezentacje ASCII,
k[i] XOR 64
a kolejne 15 znaków to reprezentacje ASCIIs[i-17] XOR 173
lubs[i-17] XOR 64 XOR 17 XOR 252
.k[i] XOR 64
s[i-17] XOR 173
Oto, co otrzymujemy:
110010011001001
Uwaga: To tylko dodatkowa uwaga, niezwiązana z powyższymi odpowiedziami.
Wypróbuj online!
źródło
C # (interaktywny kompilator Visual C #) , 141 bajtów
Tylko port przykładowego rozwiązania, przeniesiony do C #.
Wypróbuj online!
źródło
Python 3 , 182 bajty
Wypróbuj online!
Python nie wygra tutaj pierwszej nagrody, ale wciąż jest to 10-bajtowy golf najlepszego programu Python tutaj .
Python 3 , 176 bajtów
Wypróbuj online!
Jako lambda ma jeszcze sześć bajtów mniej. Boli mnie to, że muszę go użyć
if... else
, ale nie widzę innej opcji zwarcia, biorąc pod uwagę, że 0 jest możliwą odpowiedzią.Python 3 , 173 bajtów
Wypróbuj online!
Jeszcze krótszy w bajtach (choć nie jestem pewien co do bitów, ponieważ nie jest to już czysty ASCII), dzięki uprzejmości ovs.
źródło
\x..
ucieczekRdza ,
170163 bajtówWypróbuj online!
To jest port mojego rozwiązania w C, z nieco innym ciągiem, który nie wymaga XOR 17. Oczekuję, że większość rozwiązań opiera się na ciągu „@ 'rFTDVbpPBvdtfR @ \ xacp? \ Xe2> 4 \ xa6 \ xe9 {z \ xe3q5 \ xa7 \ xe8 "również można poprawić (wystarczy zmienić ciąg, usunąć xor 17 i xor 173 zamiast 188).
Usunąłem jedno z przeglądów, warunkowo dodając
17*17
jel
, podobnie jak my (mniej więcej) w rozwiązaniu kodu maszynowego ARM.Rdza ma wnioskowanie typu i zamykanie, ale jej rzutowania (nawet dla boolanów lub między liczbami całkowitymi) są zawsze jawne, zmienność musi być zaznaczona, nie ma operatora trójskładnikowego, operacji na liczbach całkowitych, domyślnie, paniki przy przepełnieniu i operacji mutacji (
l+=1
) zwraca jednostkę. Nie udało mi się uzyskać krótszego rozwiązania z iteratorami, ponieważ zamknięcia + mapowanie wciąż są dość szczegółowe.To sprawia, że Rust jest dość złym wyborem do gry w golfa. Niemniej jednak, nawet w języku, który kładzie nacisk na czytelność i bezpieczeństwo w stosunku do zwięzłości, jesteśmy zdecydowanie zbyt niscy.
Aktualizacja: użyto anonimowej funkcji z sugestii manatwork.
źródło
let p=
do Nagłówka i nie liczyć. Nie masz pewności;
, czy anonimowe połączenie nie jest potrzebne: Wypróbuj online! .05AB1E , 74 bajty
Port pierwszej odpowiedzi galaretki @NickKennedy . Ja pracuje na porcie @ jimmy23013 „s CJam odpowiedź bezpośrednio , ale byłem już na 78 bajtów i jeszcze naprawić błąd, więc byłoby większe. Jednak na pewno można jeszcze trochę grać w golfa.
Wypróbuj online lub sprawdź wszystkie przypadki testowe .
Wyjaśnienie:
Zobacz tę końcówkę 05AB1E kopalni (sekcje Jak skompresować dużych liczb całkowitych? I jak skompresować list całkowitych? ) , Aby zrozumieć, dlaczego
Ƶf
jest142
;•5›@¾ÂÌLìÁŒ©.ðǝš«YWǝŠ•
jest29709448685778434533295690952203992295278432248
,ƵŠ
jest239
; i•5›@¾ÂÌLìÁŒ©.ðǝš«YWǝŠ•ƵŠв
jest[19,48,36,38,18,238,87,24,138,206,92,197,196,86,25,139,129,93,128,207]
.źródło