PRNG per generare esattamente numeri con n bit impostati


12

Attualmente sto scrivendo del codice per generare dati binari. Ho specificamente bisogno di generare numeri a 64 bit con un dato numero di bit impostati; più precisamente, la procedura dovrebbe richiedere alcuni 0<n<64 e restituire un numero pseudo-casuale a 64 bit con esattamente n bit impostati su 1 e il resto impostato su 0.

Il mio approccio attuale prevede qualcosa del genere:

  1. Genera un numero pseudocasuale di 64 bit k .
  2. Contare i bit in k , memorizzando il risultato in b .
  3. Se b=n , output k ; altrimenti vai a 1.

Funziona, ma sembra non elegante. Esiste un qualche tipo di algoritmo PRNG che può generare numeri con n bit impostati in modo più elegante di questo?

Risposte:


12

Ciò di cui hai bisogno è un numero casuale compreso tra 0 e . Il problema è quindi quello di trasformarlo nel modello di bit.(64n)1

Questo è noto come codifica enumerativa ed è uno dei più antichi algoritmi di compressione distribuiti. Probabilmente l'algoritmo più semplice è di Thomas Cover. Si basa sulla semplice osservazione che se si dispone di una parola lunga bit, in cui i bit impostati sono nell'ordine dei bit più significativo, la posizione di questa parola nell'ordinamento lessicografico di tutte le parole con questa proprietà è:x kx 1nxkx1

1ik(xii)

Quindi, ad esempio, per una parola a 7 bit:

i(0001011)= ( 3

i(0000111)=(23)+(12)+(01)=0
i(0001101)= ( 3
i(0001011)=(33)+(12)+(01)=1
i(0001101)=(33)+(22)+(01)=2

...e così via.

Per ottenere il modello di bit dall'ordinale, basta decodificare ogni bit a turno. Qualcosa del genere, in un linguaggio di tipo 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;
}

Si noti che poiché sono necessari solo coefficienti binomiali fino a 64, è possibile pre-calcolarli.


  • Cover, T., Codifica della fonte enumerativa . Transazioni IEEE sulla teoria dell'informazione, vol IT-19, n. 1, gennaio 1973.

Bello ed elegante! La codifica enumerativa sembra qualcosa di molto utile - ci sono buone risorse su di esso (preferibilmente in forma di libro di testo)?
Koz Ross,

Questo in realtà offre prestazioni migliori nella pratica? (Naturalmente dipende dalla velocità dell'RNG.) In caso contrario, non ha senso utilizzare un codice più complesso.
Gilles 'SO- smetti di essere malvagio' il

1
@Giles L'ho interpretato come una domanda di informatica, dato che si tratta di cs.se. Ho dato solo il codice sorgente perché mi è capitato di trovarlo in giro da un'implementazione di array RRR. (Vedi, ad esempio, alexbowe.com/rrr per una spiegazione di cosa significhi.)
Pseudonimo del

1
@Gilles Per dare seguito alla tua domanda, ho implementato sia il mio metodo ingenuo sia quello fornito da Pseudonym in Forth. Il metodo ingenuo, anche quando si utilizzava un PRNG xorshift molto semplice, prendeva qualcosa nell'ordine di 20 secondi per numero , mentre il metodo dello pseudonimo era quasi istantaneo. Per questo ho usato tabelle di binomi pre-calcolati.
Koz Ross,

1
@KozRoss Se generi n numeri di bit e cerchi numeri con k bit impostati, sarebbero piuttosto rari se k fosse lontano da n / 2; questo lo spiegherebbe.
gnasher729,

3

Molto simile alla risposta dello pseudonimo, ottenuta con altri mezzi.

Il numero totale di combinazioni disponibili è accessibile con il metodo stelle e barre , quindi dovrà essere . Il numero totale di numeri a 64 bit da cui proveresti a campionare il tuo numero sarebbe ovviamente molto più alto di quello.c=(64n)

Ciò di cui hai bisogno allora è una funzione che può portarti da un numero pseudocasuale , che va da a , alla corrispondente combinazione a 64 bit.k1c

Il triangolo di Pascal può aiutarti in questo, perché il valore di ogni nodo rappresenta esattamente il numero di percorsi da quel nodo alla radice del triangolo e ogni percorso può essere fatto per rappresentare una delle stringhe che stai cercando, se tutte le curve a sinistra sono etichettato con un e ogni giro a destra con uno .10

Quindi lascia che sia il numero di bit rimasti da determinare e sia il numero di bit rimasti da usare.xy

Sappiamo che e possiamo usarlo per determinare correttamente il prossimo bit del numero ad ogni passo:(xy)=(x1y)+(x1y1)

whilex>0

ifx>y

ifk>(x1y):ss+"1",kk(x1y),yy1

else:ss+"0"

else:ss+"1",yy1

xx1


2

Un altro metodo abbastanza elegante è usare la bisection come descritto in questa risposta di StackOverflow . L'idea è di mantenere due parole, una nota per avere al massimo k bit impostati e una nota per avere almeno k bit impostati, e usare la casualità per spostare una di queste verso esattamente k bit. Ecco un po 'di codice sorgente per illustrarlo:

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

Ho fatto un confronto delle prestazioni di vari metodi e questo è in genere il più veloce a meno che k non sia noto per essere molto piccolo.


0

Puoi fare quanto segue:

1) Genera un numero casuale, compreso tra e .k164

2) Impostare ° al .k01

3) Ripetere i passaggi 1 e 2 volten

A[] è un array a bit con tutti gli 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;
}

La prosa non sembra corrispondere al tuo codice? Il codice non assegna mai 1s all'array. Inoltre, non sembra generare una distribuzione uniforme (e nemmeno numeri che soddisfino i vincoli) quando si kscontrano più s
Bergi,

@Bergi Ya ha dimenticato la riga ... ora l'ha aggiunta. E viene gestita la collisione multipla di k. Vedi il primo numero viene scelto tra 1 e 64, il secondo tra 1 e "rimanente" 63. Quindi salta l'1 durante il conteggio ... vedilinea. Ed è una distribuzione uniforme. A[x]=1if(A[x]==0)k;
Utente non trovato

Ah, vedo adesso. L'algoritmo in prosa non ha menzionato il salto.
Bergi,

@ArghyaChakraborty Stai utilizzando l'indicizzazione basata su 1 lì?
Koz Ross,

@KozRoss Inizia con cosa succede se (ovviamente saranno tutti zero) Quindi, controllerà e otterrà il significatoche dà . Quindi, imposta all'esterno del loop. Quindi sì, si tratta di indicizzazione basata su 1. Per rendere 0 sulla base di tutto ciò che dovete fare è cambiare l'interno aA A [ 1 ] = = 0 t r u e k - - ; k = 0 A [ 1 ] = 1 f o r ( x = 0 ; x < 64 ; x + + )i=1,k=1AA[1]==0truek;k=0A[1]=1for(x=0;x<64;x++)
Utente non trovato
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.