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:

  1. Wygeneruj pseudolosowy 64-bitowy numer .k
  2. Policz bity w , przechowując wynik w .kb
  3. 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

Odpowiedzi:


12

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 kx 1nxkx1

1ik(xija)

Na przykład 7-bitowe słowo:

ja(0000111)=(2)3))+(12))+(01)=0
i(0001101)= ( 3
ja(0001011)=(3)3))+(12))+(01)=1
ja(0001101)=(3)3))+(2)2))+(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:

uint64_t decode(uint64_t ones, uint64_t ordinal)
{
    uint64_t bits = 0;
    for (uint64_t bit = 63; ones > 0; --bit)
    {
        uint64_t nCk = choose(bit, ones);
        if (ordinal >= nCk)
        {
            ordinal -= nCk;
            bits |= 1 << bit;
            --ones;
        }
    }
    return bits;
}

Pamiętaj, że ponieważ potrzebujesz tylko współczynników dwumianowych do 64, możesz je wstępnie obliczyć.


  • Cover, T., Enumerative Source Encoding . Transakcje IEEE dotyczące teorii informacji, tom IT-19, nr 1, styczeń 1973.

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.do=(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)

whjalmix>0

jafax>y

jafak>(x-1y):ss+1,kk-(x-1y),yy-1

else:ss+"0"

else:ss+"1",yy1

xx1


2

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;
}

Zrobiłem porównanie skuteczności różnych metod i ten jest zwykle najszybszym chyba k jest znany jako bardzo małe.


0

Możesz wykonać następujące czynności:

1) Wygeneruj liczbę losową, od do .1 64k164

2) Ustaw th na .0 1k01

3) Powtórz kroki 1 i 2 razyn

64 0ZA[] to tablica bitowa ze wszystkimi s640

for(i=1 to n)
{
    k=ran(1,65-i) % random number between 1 and 65-i
    for(x=1;x<65;x++)
    {
        if(A[x]==0)k--;
        if(k==0)break;
    }
    A[x]=1;
}

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. ZA[x]=1jafa(ZA[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 + + )ja=1,k=1ZAZA[1]==0trumik--;k=0ZA[1]=1faor(x=0;x<64;x++)
User Not Found
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.