PRNG do generowania liczb z n dokładnie ustawionymi bitami
12
Obecnie piszę kod do generowania danych binarnych. W szczególności muszę wygenerować liczby 64-bitowe przy określonej liczbie ustawionych bitów; dokładniej, procedura powinna zająć około i zwrócić pseudolosową 64-bitową liczbę z dokładnie bitami ustawionymi na , a resztą ustawioną na 0.0 < n < 64n1
Moje obecne podejście obejmuje coś takiego:
Wygeneruj pseudolosowy 64-bitowy numer .k
Policz bity w , przechowując wynik w .kb
Jeśli , wyprowadza ; w przeciwnym razie przejdź do 1.b = nk
To działa, ale wydaje się nieeleganckie. Czy istnieje jakiś algorytm PRNG, który może generować numery z bitów ustawionych bardziej elegancko niż to?n
Potrzebujesz losowej liczby od 0 do . Problem polega zatem na przekształceniu tego w wzór bitowy.(64n)−1
Jest to znane jako kodowanie wyliczeniowe i jest to jeden z najstarszych wdrożonych algorytmów kompresji. Prawdopodobnie najprostszy algorytm pochodzi od Thomasa Covera. Opiera się na prostej obserwacji, że jeśli masz słowo o długości bitów, gdzie ustawione bity to w najbardziej znaczącej kolejności bitów, to pozycja tego słowa w porządku leksykograficznym wszystkich słów o tej właściwości jest:x k … x 1nxk…x1
∑1≤i≤k(xii)
Na przykład 7-bitowe słowo:
i ( 0000111 ) = ( 23)) + ( 12)) + ( 01) =0
i(0001101)= ( 3
i ( 0001011 ) = ( 33)) + ( 12)) + ( 01) =1
i ( 0001101 ) = ( 33)) + ( 22)) + ( 01) =2
...i tak dalej.
Aby uzyskać wzór bitowy z porządkowej, po prostu dekodujesz kolejno każdy bit. Coś takiego w języku C:
Piękny i elegancki! Kodowanie numeryczne wygląda na coś bardzo przydatnego - czy są na to jakieś dobre zasoby (najlepiej w formie podręcznika)?
Koz Ross,
Czy to faktycznie daje lepszą wydajność w praktyce? (Oczywiście zależy to od szybkości RNG.) Jeśli nie, nie ma sensu używać bardziej złożonego kodu.
Gilles „SO - przestań być zły”,
1
@Giles Zinterpretowałem to jako pytanie informatyczne, ponieważ jest to cs.se. Podałem tylko kod źródłowy, ponieważ zdarzyło mi się, że znajdowałem go w implementacji tablicy RRR. (Zobacz, na przykład, alexbowe.com/rrr, aby uzyskać wyjaśnienie, co to znaczy.)
pseudonim
1
@Gilles Aby odpowiedzieć na twoje pytanie, wdrożyłem zarówno moją naiwną metodę, jak i metodę podaną przez Pseudonim w Forth. Naiwna metoda, nawet przy użyciu bardzo prostego xorshift PRNG, wymagała około 20 sekund na liczbę , podczas gdy metoda Pseudonim była niemal natychmiastowa. Użyłem do tego tabel wstępnie obliczonych dwumianów.
Koz Ross,
1
@KozRoss Jeśli wygenerujesz n liczb bitowych i poszukasz liczb z zestawem k bitów, byłyby one dość rzadkie, gdyby k było daleko od n / 2; to by to wyjaśniało.
gnasher729
3
Bardzo podobny do odpowiedzi Pseudonim uzyskanej w inny sposób.
Całkowitą liczbę dostępnych kombinacji można uzyskać metodą gwiazdek i słupków , więc będzie to musiało wynosić . Całkowita liczba 64-bitowych liczb, z których próbujesz próbkować swoją liczbę, byłaby oczywiście znacznie wyższa.c = ( 64n)
Potrzebujesz zatem funkcji, która może poprowadzić cię od pseudolosowej liczby , od do , do odpowiedniej kombinacji 64-bitowej.1 ck1do
Trójkąt Pascala może ci w tym pomóc, ponieważ wartość każdego węzła reprezentuje dokładnie liczbę ścieżek od tego węzła do pierwiastka trójkąta, a każda ścieżka może być reprezentowana przez jeden z ciągów, których szukasz, jeśli wszystkie skręty w lewo są oznaczone , a każdy skręt w prawo .010
Niech będzie liczbą bitów pozostałych do ustalenia, a liczbą pozostałych do użycia.yxy
Wiemy, że , i możemy go użyć, aby poprawnie określić następny bit liczby na każdym kroku:( xy) = ( x-1y) + ( x-1y- 1)
w h ja l ex > 0
i fx > y
i fk > ( x - 1y) :s ← s+ „ 1 ” ,k ← k - ( x - 1y),y←y−1
Inną dość elegancką metodą jest użycie bisekcji, jak opisano w tej odpowiedzi na przepełnienie stosu . Pomysł polega na zachowaniu dwóch słów, z których jedno ma co najwyżej k bitów, a drugie co najmniej k bitów, i używa losowości, aby przesunąć jedno z nich w kierunku uzyskania dokładnie k bitów. Oto kod źródłowy, który to ilustruje:
word randomKBits(int k) {
word min = 0;
word max = word(~word(0)); // all 1s
int n = 0;
while (n != k) {
word x = randomWord();
x = min | (x & max);
n = popcount(x);
if (n > k)
max = x;
else
min = x;
}
return min;
}
Proza wydaje się nie pasować do twojego kodu? Kod nigdy nie przypisuje 1s do tablicy. Również nie wydaje się generować jednolitego rozkładu (a nawet liczb, które spełniają ograniczenia), gdy kzderza się wiele s
Bergi
@Bergi Ya zapomniałem linii ... dodał ją teraz. Obsługiwane jest wielokrotne zderzenie k. Patrz, pierwsza liczba jest wybierana między 1 a 64, druga między 1 a „pozostałą” 63. Więc pomija 1 podczas liczenia ... zobaczlinia. I to jest jednolita dystrybucja. A [ x ] = 1jaf( A [ x ] = = 0 ) k - - ;
Nie znaleziono użytkownika
Ach, rozumiem teraz. Algorytm prozy nie wspominał o pominięciu.
Bergi,
@ArghyaChakraborty Czy używasz tam indeksowania 1?
Koz Ross,
@KozRoss Zacznij od tego, co się stanie, jeśli (oczywiście będą zerami) Więc sprawdzi i uzyska znaczenieco daje . Ustawia poza pętlą. Więc tak, jest to indeksowanie 1. Żeby było 0 oparty wszystko co musisz zrobić, to zmienić wewnętrzna celuA A [ 1 ] = = 0 t r u e k - - ; k = 0 A [ 1 ] = 1 f o r ( x = 0 ; x < 64 ; x + + )i = 1 , k = 1ZAA [ 1 ] = = 0t r u ek - - ;k = 0A [ 1 ] = 1fao r( x = 0 ; x < 64 ; x + + )
Bardzo podobny do odpowiedzi Pseudonim uzyskanej w inny sposób.
Całkowitą liczbę dostępnych kombinacji można uzyskać metodą gwiazdek i słupków , więc będzie to musiało wynosić . Całkowita liczba 64-bitowych liczb, z których próbujesz próbkować swoją liczbę, byłaby oczywiście znacznie wyższa.c = ( 64n)
Potrzebujesz zatem funkcji, która może poprowadzić cię od pseudolosowej liczby , od do , do odpowiedniej kombinacji 64-bitowej.1 ck 1 do
Trójkąt Pascala może ci w tym pomóc, ponieważ wartość każdego węzła reprezentuje dokładnie liczbę ścieżek od tego węzła do pierwiastka trójkąta, a każda ścieżka może być reprezentowana przez jeden z ciągów, których szukasz, jeśli wszystkie skręty w lewo są oznaczone , a każdy skręt w prawo .01 0
Niech będzie liczbą bitów pozostałych do ustalenia, a liczbą pozostałych do użycia.yx y
Wiemy, że , i możemy go użyć, aby poprawnie określić następny bit liczby na każdym kroku:( xy) = ( x-1y) + ( x-1y- 1)
źródło
Inną dość elegancką metodą jest użycie bisekcji, jak opisano w tej odpowiedzi na przepełnienie stosu . Pomysł polega na zachowaniu dwóch słów, z których jedno ma co najwyżej k bitów, a drugie co najmniej k bitów, i używa losowości, aby przesunąć jedno z nich w kierunku uzyskania dokładnie k bitów. Oto kod źródłowy, który to ilustruje:
Zrobiłem porównanie skuteczności różnych metod i ten jest zwykle najszybszym chyba k jest znany jako bardzo małe.
źródło
Możesz wykonać następujące czynności:
1) Wygeneruj liczbę losową, od do .1 64k 1 64
2) Ustaw th na .0 1k 0 1
3) Powtórz kroki 1 i 2 razyn
64 0A [ ] to tablica bitowa ze wszystkimi s64 0
źródło
1
s do tablicy. Również nie wydaje się generować jednolitego rozkładu (a nawet liczb, które spełniają ograniczenia), gdyk
zderza się wiele s