Losowy golf dnia 6: Rzuć k20

17

O serii

Po pierwsze, możesz potraktować to jak każde inne wyzwanie związane z golfem i odpowiedzieć na nie, nie martwiąc się w ogóle serią. Istnieje jednak tabela wyników dla wszystkich wyzwań. Możesz znaleźć tabelę liderów wraz z kilkoma więcej informacji o serii w pierwszym poście .

Chociaż mam szereg pomysłów w szeregu, przyszłe wyzwania nie są jeszcze ustalone. Jeśli masz jakieś sugestie, daj mi znać w odpowiednim poście z piaskownicą .

Dziura 6: Rzuć k20

Bardzo popularną kością w stołowych grach RPG jest matryca dwustronna ( dwudziestościan , powszechnie znany jako d20 ). Twoim zadaniem jest rzucić taką kostką. Jeśli jednak zwracasz losową liczbę od 1 do 20, byłoby to trochę trywialne. Twoim zadaniem jest wygenerowanie losowej sieci dla danej kości.

Wykorzystamy następującą sieć:

wprowadź opis zdjęcia tutaj

Jest to pasek trójkąta, dzięki czemu można go łatwo przedstawić jako listę liczb całkowitych. Np. Jeśli otrzymasz dane wejściowe:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Odpowiadałoby to następującej kości (fajny fakt: to sieć używana przez Magic: liczniki życia Gathering / kości spin-down).

wprowadź opis zdjęcia tutaj

Nie jest to jednak jedyna sieć reprezentująca tę kostkę. W zależności od tego, jak rozwijamy twarze, istnieje 60 różnych sieci. Oto dwa kolejne:

[1, 8, 9, 10, 2, 3, 4, 5, 6, 7, 17, 18, 19, 11, 12, 13, 14, 15, 16, 20]
[10, 9, 18, 19, 11, 12, 3, 2, 1, 8, 7, 17, 16, 20, 13, 14, 4, 5, 6, 15]

Lub graficznie (nie obracałem etykiet twarzy dla uproszczenia):

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Wyzwanie

Biorąc pod uwagę listę liczb całkowitych reprezentujących matrycę (jak opisano powyżej) i liczbę całkowitą N, wyprowadzane Nniezależnie, równomiernie losowe sieci d20 odpowiadające danej matrycy. (Oznacza to, że każda z 60 możliwych sieci powinna mieć takie samo prawdopodobieństwo wygenerowania.)

Oczywiście, ze względu na ograniczenia techniczne PRNG, idealna jednolitość będzie niemożliwa. W celu oceny jednolitości przesłanych danych następujące operacje zostaną uznane za zapewniające idealnie jednolite rozkłady:

  • Uzyskiwanie numeru z PRNG (w dowolnym zakresie), który jest udokumentowany jako (w przybliżeniu) jednolity.
  • Mapowanie równomiernego rozkładu większego zbioru liczb na mniejszy zbiór poprzez modulo lub mnożenie (lub inną operację, która równomiernie rozprowadza wartości). Większy zestaw musi zawierać co najmniej 1024 razy więcej możliwych wartości niż mniejszy zestaw.

Biorąc pod uwagę te założenia, algorytm musi dawać idealnie jednolity rozkład.

Twój program powinien być w stanie wygenerować 100 sieci w mniej niż sekundę (więc nie próbuj generować losowych sieci, dopóki nie odpowiada to kości podanej powyżej).

Możesz napisać program lub funkcję, pobierając dane wejściowe przez STDIN (lub najbliższą alternatywę), argument wiersza poleceń lub argument funkcji i wypisując wynik przez STDOUT (lub najbliższą alternatywę), wartość zwracaną funkcji lub parametr funkcji (wyjściowej).

Dane wejściowe i wyjściowe mogą być w dowolnym wygodnym, jednoznacznym, płaskim formacie listy. Możesz założyć, że wartości nominalne d20 są odrębnymi dodatnimi liczbami całkowitymi, które pasują do naturalnego typu liczb całkowitych twojego języka.

To jest kod golfowy, więc wygrywa najkrótsze przesłanie (w bajtach). I oczywiście najkrótsze zgłoszenie na użytkownika wejdzie również do ogólnej tabeli liderów serii.

Przykładowe dane wyjściowe

Dla danych wejściowych

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

60 możliwych sieci (o ile nie popełniłem błędu), w żadnej określonej kolejności, to:

[11, 10, 9, 18, 19, 20, 13, 12, 3, 2, 1, 8, 7, 17, 16, 15, 14, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
[8, 7, 17, 18, 9, 10, 2, 1, 5, 6, 15, 16, 20, 19, 11, 12, 3, 4, 14, 13]
[3, 12, 13, 14, 4, 5, 1, 2, 10, 11, 19, 20, 16, 15, 6, 7, 8, 9, 18, 17]
[3, 4, 5, 1, 2, 10, 11, 12, 13, 14, 15, 6, 7, 8, 9, 18, 19, 20, 16, 17]
[11, 19, 20, 13, 12, 3, 2, 10, 9, 18, 17, 16, 15, 14, 4, 5, 1, 8, 7, 6]
[4, 14, 15, 6, 5, 1, 2, 3, 12, 13, 20, 16, 17, 7, 8, 9, 10, 11, 19, 18]
[2, 10, 11, 12, 3, 4, 5, 1, 8, 9, 18, 19, 20, 13, 14, 15, 6, 7, 17, 16]
[4, 5, 1, 2, 3, 12, 13, 14, 15, 6, 7, 8, 9, 10, 11, 19, 20, 16, 17, 18]
[10, 2, 1, 8, 9, 18, 19, 11, 12, 3, 4, 5, 6, 7, 17, 16, 20, 13, 14, 15]
[3, 2, 10, 11, 12, 13, 14, 4, 5, 1, 8, 9, 18, 19, 20, 16, 15, 6, 7, 17]
[7, 8, 1, 5, 6, 15, 16, 17, 18, 9, 10, 2, 3, 4, 14, 13, 20, 19, 11, 12]
[13, 12, 11, 19, 20, 16, 15, 14, 4, 3, 2, 10, 9, 18, 17, 7, 6, 5, 1, 8]
[16, 15, 14, 13, 20, 19, 18, 17, 7, 6, 5, 4, 3, 12, 11, 10, 9, 8, 1, 2]
[15, 16, 17, 7, 6, 5, 4, 14, 13, 20, 19, 18, 9, 8, 1, 2, 3, 12, 11, 10]
[20, 13, 12, 11, 19, 18, 17, 16, 15, 14, 4, 3, 2, 10, 9, 8, 7, 6, 5, 1]
[5, 4, 14, 15, 6, 7, 8, 1, 2, 3, 12, 13, 20, 16, 17, 18, 9, 10, 11, 19]
[10, 11, 12, 3, 2, 1, 8, 9, 18, 19, 20, 13, 14, 4, 5, 6, 7, 17, 16, 15]
[4, 3, 12, 13, 14, 15, 6, 5, 1, 2, 10, 11, 19, 20, 16, 17, 7, 8, 9, 18]
[19, 20, 13, 12, 11, 10, 9, 18, 17, 16, 15, 14, 4, 3, 2, 1, 8, 7, 6, 5]
[1, 8, 9, 10, 2, 3, 4, 5, 6, 7, 17, 18, 19, 11, 12, 13, 14, 15, 16, 20]
[8, 1, 5, 6, 7, 17, 18, 9, 10, 2, 3, 4, 14, 15, 16, 20, 19, 11, 12, 13]
[18, 9, 8, 7, 17, 16, 20, 19, 11, 10, 2, 1, 5, 6, 15, 14, 13, 12, 3, 4]
[12, 3, 2, 10, 11, 19, 20, 13, 14, 4, 5, 1, 8, 9, 18, 17, 16, 15, 6, 7]
[2, 3, 4, 5, 1, 8, 9, 10, 11, 12, 13, 14, 15, 6, 7, 17, 18, 19, 20, 16]
[10, 9, 18, 19, 11, 12, 3, 2, 1, 8, 7, 17, 16, 20, 13, 14, 4, 5, 6, 15]
[9, 8, 7, 17, 18, 19, 11, 10, 2, 1, 5, 6, 15, 16, 20, 13, 12, 3, 4, 14]
[16, 17, 7, 6, 15, 14, 13, 20, 19, 18, 9, 8, 1, 5, 4, 3, 12, 11, 10, 2]
[17, 7, 6, 15, 16, 20, 19, 18, 9, 8, 1, 5, 4, 14, 13, 12, 11, 10, 2, 3]
[1, 5, 6, 7, 8, 9, 10, 2, 3, 4, 14, 15, 16, 17, 18, 19, 11, 12, 13, 20]
[9, 18, 19, 11, 10, 2, 1, 8, 7, 17, 16, 20, 13, 12, 3, 4, 5, 6, 15, 14]
[16, 20, 19, 18, 17, 7, 6, 15, 14, 13, 12, 11, 10, 9, 8, 1, 5, 4, 3, 2]
[5, 1, 2, 3, 4, 14, 15, 6, 7, 8, 9, 10, 11, 12, 13, 20, 16, 17, 18, 19]
[8, 9, 10, 2, 1, 5, 6, 7, 17, 18, 19, 11, 12, 3, 4, 14, 15, 16, 20, 13]
[13, 20, 16, 15, 14, 4, 3, 12, 11, 19, 18, 17, 7, 6, 5, 1, 2, 10, 9, 8]
[6, 15, 16, 17, 7, 8, 1, 5, 4, 14, 13, 20, 19, 18, 9, 10, 2, 3, 12, 11]
[6, 5, 4, 14, 15, 16, 17, 7, 8, 1, 2, 3, 12, 13, 20, 19, 18, 9, 10, 11]
[7, 6, 15, 16, 17, 18, 9, 8, 1, 5, 4, 14, 13, 20, 19, 11, 10, 2, 3, 12]
[19, 18, 17, 16, 20, 13, 12, 11, 10, 9, 8, 7, 6, 15, 14, 4, 3, 2, 1, 5]
[14, 15, 6, 5, 4, 3, 12, 13, 20, 16, 17, 7, 8, 1, 2, 10, 11, 19, 18, 9]
[17, 18, 9, 8, 7, 6, 15, 16, 20, 19, 11, 10, 2, 1, 5, 4, 14, 13, 12, 3]
[6, 7, 8, 1, 5, 4, 14, 15, 16, 17, 18, 9, 10, 2, 3, 12, 13, 20, 19, 11]
[14, 13, 20, 16, 15, 6, 5, 4, 3, 12, 11, 19, 18, 17, 7, 8, 1, 2, 10, 9]
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[7, 17, 18, 9, 8, 1, 5, 6, 15, 16, 20, 19, 11, 10, 2, 3, 4, 14, 13, 12]
[15, 6, 5, 4, 14, 13, 20, 16, 17, 7, 8, 1, 2, 3, 12, 11, 19, 18, 9, 10]
[9, 10, 2, 1, 8, 7, 17, 18, 19, 11, 12, 3, 4, 5, 6, 15, 16, 20, 13, 14]
[2, 1, 8, 9, 10, 11, 12, 3, 4, 5, 6, 7, 17, 18, 19, 20, 13, 14, 15, 16]
[12, 13, 14, 4, 3, 2, 10, 11, 19, 20, 16, 15, 6, 5, 1, 8, 9, 18, 17, 7]
[17, 16, 20, 19, 18, 9, 8, 7, 6, 15, 14, 13, 12, 11, 10, 2, 1, 5, 4, 3]
[18, 17, 16, 20, 19, 11, 10, 9, 8, 7, 6, 15, 14, 13, 12, 3, 2, 1, 5, 4]
[18, 19, 11, 10, 9, 8, 7, 17, 16, 20, 13, 12, 3, 2, 1, 5, 6, 15, 14, 4]
[11, 12, 3, 2, 10, 9, 18, 19, 20, 13, 14, 4, 5, 1, 8, 7, 17, 16, 15, 6]
[15, 14, 13, 20, 16, 17, 7, 6, 5, 4, 3, 12, 11, 19, 18, 9, 8, 1, 2, 10]
[19, 11, 10, 9, 18, 17, 16, 20, 13, 12, 3, 2, 1, 8, 7, 6, 15, 14, 4, 5]
[12, 11, 19, 20, 13, 14, 4, 3, 2, 10, 9, 18, 17, 16, 15, 6, 5, 1, 8, 7]
[20, 16, 15, 14, 13, 12, 11, 19, 18, 17, 7, 6, 5, 4, 3, 2, 10, 9, 8, 1]
[13, 14, 4, 3, 12, 11, 19, 20, 16, 15, 6, 5, 1, 2, 10, 9, 18, 17, 7, 8]
[5, 6, 7, 8, 1, 2, 3, 4, 14, 15, 16, 17, 18, 9, 10, 11, 12, 13, 20, 19]
[14, 4, 3, 12, 13, 20, 16, 15, 6, 5, 1, 2, 10, 11, 19, 18, 17, 7, 8, 9]

W przypadku każdej innej sieci po prostu zamień każde wystąpienie ina iliczbę th na wejściu (gdzie ijest oparta na 1).

Powiązane wyzwania

Tabela liderów

Pierwszy post z serii generuje tabelę wyników.

Aby upewnić się, że Twoje odpowiedzi się pojawią, zacznij każdą odpowiedź od nagłówka, używając następującego szablonu Markdown:

## Language Name, N bytes

gdzie Njest rozmiar twojego zgłoszenia. Jeśli poprawić swój wynik, to może zachować stare porachunki w nagłówku, uderzając je przez. Na przykład:

## Ruby, <s>104</s> <s>101</s> 96 bytes

(Język nie jest obecnie wyświetlany, ale fragment go wymaga i analizuje, a w przyszłości mogę dodać tabelę wyników według języków).

Martin Ender
źródło
Jeśli chodzi o naszą dyskusję, dodałem słowo „must”, aby było jaśniej. Myślę, że to zamyka lukę, z której korzystałem. Mimo to uważam, że zastosowane przeze mnie podejście (choć nieprawidłowe) jest interesujące.
Level River St
To prawie dokładnie tak, jak mój post w piaskownicy!
Rɪᴋᴇʀ
@RikerW Tak właśnie myślałem, kiedy je piaskowiłeś. ;) (W tym czasie mój był bezpośrednio pod twoim. Myślałem, że ten zainspirował twój.) Twój jest oczywiście o wiele prostszy (co jest prawdopodobnie dobrą rzeczą).
Martin Ender,
Nigdy nie widziałem twojego. To dziwne, myślałem, że przeczytałem wszystkie na pierwszej stronie. Ale nie, stworzyłem mój niezależnie.
Rɪᴋᴇʀ
Czy to nie powinno być zatytułowane „rozwinąć d20”?
Tytus

Odpowiedzi:

7

Rubinowy, 160 bajtów (rev B)

17 bajtów zapisanych dzięki sugestiom Martina Büttnera.

->a,n{n.times{k=rand 60
%w{ABCD@GHIJKLMNEFPQRSO PFENOSRQHG@DCMLKJIAB GFPQHIA@DENOSRJKBCML}.map{|b|k.times{a=b.chars.map{|i|a[i.ord-64]}}}
k>29&&a.reverse!
p a}}

Rubinowy, 177 bajtów (rev A)

->n,a{n.times{k=rand(60)
h=->b{k.times{|j|a=(0..19).map{|i|a[b[i].ord-64]}}}
h['ABCD@GHIJKLMNEFPQRSO']
h['PFENOSRQHG@DCMLKJIAB']
h['GFPQHIA@DENOSRJKBCML']
k>29&&a.reverse!
p a}}

Wyjaśnienie

Możliwe jest wygenerowanie wszystkich możliwych orientacji poprzez obrót wokół jednej powierzchni (3-krotnie), jednego wierzchołka (5-krotnie) i dwóch krawędzi (2-krotnie). Ale nie tylko twarz i krawędzie. Osie muszą mieć prawidłową relację, a obroty muszą być wykonywane we właściwej kolejności, inaczej mogą się zdarzyć dziwne rzeczy.

Tak to zrobiłem (choć rozumiem, że Martin zrobił to inaczej).

Wszystkie orientacje czworościanu mogą być generowane przez kombinacje następujących trzech operacji symetrii:

a) Obrót wokół dwóch 2-krotnych osi w prawo przez punkty środkowe krawędzi (są one ustawione pod kątem prostym do siebie. Jeśli pomnożymy je razem, odkryjemy trzecią 2-krotną oś przez pozostałą parę krawędzi.)

b) Obrót wokół 3-krotnej osi ukośnej do 2-krotnej osi, przechodzący przez wierzchołek i powierzchnię.

Symetria dwudziestościanu jest nadzbiorem symetrii czworościanu. Na poniższym obrazku z https://en.wikipedia.org/wiki/Icosahedron żółte i czerwone twarze definiują dwie różne czworościany (lub alternatywnie pojedynczy ośmiościan), podczas gdy krawędzie wspólne dla dwóch niebieskich ścian są w trzech parach po kąty proste (i leżą na powierzchniach sześcianu).

Wszystkie orientacje dwudziestościanu mogą być generowane przez te operacje symetrii wspomniane powyżej plus dodatkowa 5-krotna operacja.

wprowadź opis zdjęcia tutaj

Obroty wokół trzech z czterech wyżej wymienionych osi są reprezentowane przez magiczne sznurki między ''znakami. Obrót wokół drugiej 2-krotnej osi jest inny i można to zrobić po prostu przez odwrócenie tablicy a[].

Ungolfed w programie testowym:

f=->a,n{
  n.times{                     #Iterate n times, using the result from the previous iteration to generate the next
    k=rand(60)                 #pick a random number

    h=->b{                     #helper function taking a string representing a transformation
      k.times{|j|              #which is performed on a using the number of times according to k
        a=(0..19).map{|i|a[b[i].ord-64]}
      }
    }

    #Rotate about axes k times (one 5-fold, one 3-fold, two 2-fold)
    #The first three axes have coprime rotation orders
    #And the rotations themselves take care of the modulus operation so no need to add it.
    #The second 2-fold rotation is equivalent to reversing the order
    #And is applied to the last 30 numbers as it is not coprime with the first 2-fold rotation.

    h['ABCD@GHIJKLMNEFPQRSO']  #rotate k times about 5-fold axis
    h['PFENOSRQHG@DCMLKJIAB']  #rotate k times about 3-fold axis
    h['GFPQHIA@DENOSRJKBCML']  #rotate k times about 2-fold axis
    k>29&&a.reverse!
    p a
  }
}

z=(1..20).map{|i|i} 
f[z,50]

Alternatywne rozwiązanie 131 bajtów (niepoprawne ze względu na binarne podejście losowe, daje jedynie przybliżony prawidłowy rozkład).

->a,n{(n*99).times{|i|s=['@DEFGHIABCMNOPQRJKLS','ABCD@GHIJKLMNEFPQRSO'][rand(2)] 
a=(0..19).map{|i|a[s[i].ord-64]}
i%99==98&&p(a)}}

Jest to szyfrowanie (podobnie jak programy używane do szyfrowania kostki Rubika).

Specyficzne rotacje, których używam, to dwie z najbardziej oczywistych:

-Obrót o 120 stopni (około powierzchni 1 i 20 na pierwszym schemacie)

-Obrót o 72 stopnie (około wierzchołków wspólnych dla 1,2,3,4,5 i 16,17,18,19,20 na pierwszym schemacie).

rzucamy monetą 99 razy i za każdym razem wykonujemy jedną z tych dwóch rotacji w zależności od tego, czy jest to główka czy reszka.

Zauważ, że naprzemienne ich deterministycznie prowadzi do dość krótkich sekwencji. Na przykład przy prawidłowych zmysłach obrotu można uzyskać obrót o 180 stopni z okresem zaledwie 2.

Level River St
źródło
Wydaje się, że rzut monetą, aby wybrać operację, da coś bliższego rozkładowi dwumianowemu niż rozkładowi jednorodnemu.
Sparr,
@Sparr miałoby to miejsce, gdyby przestrzeń stanu była większa niż losowy spacer. Ale w tym przypadku losowy spacer w zaledwie 6 krokach może otworzyć aż 2 ^ 6 = 64 możliwości (nie policzyłem ich), a nasza przestrzeń stanów wynosi tylko 60. Po 99 krokach (2 ^ 99 różnych ścieżek) wszystko powinno być co najmniej tak równomiernie rozłożone, jak pojedyncza próbka PRNG użyta do wygenerowania liczb.
Level River St
@ MartinBüttner Dzięki za wskazówki, zastosowałem te, które działają. b.mapnie działa bezpośrednio, muszę b.chars.mapgo uruchomić (BTW, który nie działa na moim komputerze, ponieważ mam Ruby 1.9.3, ale działa na Ideone.) To uczciwa oszczędność. Nie sądzę, aby zmiana magicznych ciągów znaków niedrukowalnych w celu zapisania -64testamentu działała: %w{}interpretuje \n(jak również spację) jako separator między ciągami w wygenerowanej tablicy. Nie mam pojęcia, co zrobi z innymi niedrukowalnymi znakami ASCII.
Level River St
@Martin było to trudniejsze niż się spodziewałem - na początku byłem zaskoczony, gdy mój kod nie działał poprawnie, potem zrobiłem sobie przerwę i nagle zdałem sobie sprawę, że symetria 2-krotna i 3-krotna musiała mieć taki sam wzajemny związek jak na czworościanie (musiałem zmienić trójkątną powierzchnię, wokół której się obracałem, na inną trójkątną).
Level River St
1
Gratulujemy bycia pierwszym użytkownikiem z nowo odblokowaną odznaką geometrii . :)
Martin Ender,
2

Kod maszynowy IA-32, 118 bajtów

Hexdump:

60 33 c0 51 8b 74 24 28 8b fa 6a 05 59 f3 a5 e8
21 00 00 00 20 c4 61 cd 6a 33 00 84 80 ad a8 33
32 00 46 20 44 8e 48 61 2d 2c 33 32 4a 00 21 20
a7 a2 90 8c 00 5b b1 04 51 0f c7 f1 83 e1 1f 49
7e f7 51 8b f2 56 8d 7c 24 e0 b1 14 f3 a4 5f 8b
f3 ac 8b ee d4 20 86 cc e3 0a 56 8d 74 04 e0 f3
a4 5e eb ed 59 e2 db 8b dd 59 e2 cc 59 83 c2 14
e2 91 61 c2 04 00

Jest trochę długi, więc niektóre wyjaśnienia są najważniejsze. Kiedyś niemal ten sam algorytm jak istniejącego Odpowiedź poziom rzeki św . Aby moja odpowiedź była inna, wziąłem inne podstawowe permutacje, które niekoniecznie mają intuicyjne znaczenie geometryczne, ale są w jakiś sposób wygodniejsze.

Kod musi generować permutację, która jest sekwencyjnym zastosowaniem następujących elementów:

  1. Permutacja rzędu 3, którą nazywam p3, zastosowała 0 ... 2 razy
  2. Permutacja rzędu 2, którą nazywam q2, zastosowała 0 lub 1 razy
  3. Permutacja rzędu 5, którą nazywam p5, zastosowała 0 ... 4 razy
  4. Kolejna permutacja rzędu 2, którą nazywam p2, zastosowała 0 lub 1 razy

Istnieje wiele możliwych wyborów dla tych permutacji. Jednym z nich jest:

p3 = [0   4   5   6   7   8   9   1   2   3  13  14  15  16  17  18  10  11  12  19]
q2 = [4   5   6   7   0   1   2   3  13  14  15  16  17   8   9  10  11  12  19  18]
p5 = [6   7   0   4   5  14  15  16  17   8   9   1   2   3  13  12  19  18  10  11]
p2 = [1   0   7   8   9  10  11   2   3   4   5   6  16  17  18  19  12  13  14  15]

Ten wybór jest lepszy niż inne, ponieważ tutaj permutacje mają długie serie indeksów sekwencyjnych, które można skompresować za pomocą kodowania długości przebiegu - tylko 29 bajtów dla 4 permutacji.

Aby uprościć generowanie liczb losowych, wybrałem moce (ile razy stosuje się każdą permutację) z zakresu 1 ... 30 dla wszystkich. Prowadzi to do znacznie dodatkowej pracy w kodzie, ponieważ np. p3Staje się permutacją tożsamości za każdym razem, gdy jest ona mnożona 3 razy. Jednak kod jest w ten sposób mniejszy i dopóki zakres jest podzielny przez 30, wynik będzie miał jednolity rozkład.

Ponadto moce powinny być dodatnie, aby operacja dekodowania przebiegała co najmniej raz.

Kod ma 4 zagnieżdżone pętle; zarys wygląda następująco:

void doit(int n, uint8_t* output, const uint8_t input[20])
{    
    uint8_t temp[20];

    n_loop: for i_n = 0 ... n
    {
        memcpy(output, input, 20);
        expr_loop: for i_expr = 0 ... 3
        {
            power = rand(1 ... 30);
            power_loop: for i_power = 0 ... power
            {
                memcpy(temp, output, 20);
                output_index = 0;
                perm_loop: do while length > 0
                {
                    index = ...; // decode it
                    length = ...; // decode it
                    memcpy(output + output_index, temp + index, length);
                    output_index += length;
                }
            }
        }
        output += 20;
    }
}

Mam nadzieję, że ten pseudo-kod jest wyraźniejszy niż poniższy kod asemblera.

_declspec(naked) void __fastcall doit(int n, uint8_t* output, const uint8_t* input)
{
    _asm {
        pushad
        xor eax, eax

        n_loop:
            push ecx

            ; copy from input to output
            mov esi, [esp + 0x28]
            mov edi, edx
            push 5
            pop ecx
            rep movsd

            call end_of_data
#define rl(index, length) _emit(length * 32 + index)
            rl(0, 1)
            rl(4, 6)
            rl(1, 3)
            rl(13, 6)
            rl(10, 3)
            rl(19, 1)
            _emit(0)

            rl(4, 4)
            rl(0, 4)
            rl(13, 5)
            rl(8, 5)
            rl(19, 1)
            rl(18, 1)
            _emit(0)

            rl(6, 2)
            rl(0, 1)
            rl(4, 2)
            rl(14, 4)
            rl(8, 2)
            rl(1, 3)
            rl(13, 1)
            rl(12, 1)
            rl(19, 1)
            rl(18, 1)
            rl(10, 2)
            _emit(0)

            rl(1, 1)
            rl(0, 1)
            rl(7, 5)
            rl(2, 5)
            rl(16, 4)
            rl(12, 4)
            _emit(0)

            end_of_data:
            pop ebx ; load the address of the encoded data
            mov cl, 4

            expr_loop:
                push ecx

                make_rand:
                rdrand ecx
                and ecx, 31
                dec ecx
                jle make_rand

                ; input: ebx => encoding of permutation
                ; output: ebp => encoding of next permutation
                power_loop:
                    push ecx

                    ; copy from output to temp
                    mov esi, edx
                    push esi
                    lea edi, [esp - 0x20]
                    mov cl, 20
                    rep movsb
                    pop edi

                    ; ebx => encoding of permutation
                    ; edi => output
                    mov esi, ebx
                    perm_loop:
                        ; read a run-length
                        lodsb
                        mov ebp, esi

                        _emit(0xd4)             ; divide by 32, that is, split into
                        _emit(32)               ; index (al) and length (ah)
                        xchg cl, ah             ; set ecx = length; also makes eax = al
                        jecxz perm_loop_done    ; zero length => done decoding
                        push esi
                        lea esi, [esp + eax - 0x20]
                        rep movsb
                        pop esi
                        jmp perm_loop

                    perm_loop_done:
                    pop ecx
                    loop power_loop

                mov ebx, ebp
                pop ecx
                loop expr_loop

            pop ecx
            add edx, 20
            loop n_loop

        popad
        ret 4
    }
}

Niektóre zabawne szczegóły implementacji:

  • Użyłem wcięcia, jak w językach wysokiego poziomu; w przeciwnym razie kod byłby niezrozumiałym bałaganem
  • Używam calli kolejnepop dostęp do danych (zakodowanych permutacji) przechowywanych w kodzie
  • The jecxzInstrukcja wygodnie pozwala mi użyć zerowy bajt jako zakończenie procesu dekodowania run-length
  • Na szczęście liczba 30 (2 * 3 * 5) jest prawie potęgą 2. Pozwala mi to użyć krótkiego kodu do wygenerowania liczby z zakresu 1 ... 30:

            and ecx, 31
            dec ecx
            jle make_rand
    
  • Korzystam z instrukcji „podziału ogólnego” ( aam), aby oddzielić bajt na pola bitowe (długość 3-bitowa i indeks 5-bitowy); na szczęście w tej pozycji w kodzie cl = 0, więc korzystam z obu „stron”xchg

anatolig
źródło