PRNG para generar números con n bits establecidos exactamente


12

Actualmente estoy escribiendo un código para generar datos binarios. Necesito específicamente generar números de 64 bits con un número determinado de bits establecidos; más precisamente, el procedimiento debería tomar algunos y devolver un número pseudoaleatorio de 64 bits con exactamente bits establecidos en , y el resto establecido en 0.0<n<64n1

Mi enfoque actual implica algo como esto:

  1. Genere un número pseudoaleatorio de 64 bits .k
  2. Cuente los bits en , almacenando el resultado en .kb
  3. Si , salida ; de lo contrario, vaya a 1.b=nk

Esto funciona, pero parece poco elegante. ¿Hay algún tipo de algoritmo PRNG que pueda generar números con bits establecidos de manera más elegante que esto?n

Respuestas:


12

Lo que necesita es un número aleatorio entre 0 y . El problema entonces es convertir esto en el patrón de bits.(64n)1

Esto se conoce como codificación enumerativa, y es uno de los algoritmos de compresión implementados más antiguos. Probablemente el algoritmo más simple es de Thomas Cover. Se basa en la simple observación de que si tiene una palabra que tiene bits de longitud, donde los bits establecidos son en el orden de bits más significativo, entonces la posición de esta palabra en el orden lexicográfico de todas las palabras con esta propiedad es:nxkx1

1ik(xii)

Entonces, por ejemplo, para una palabra de 7 bits:

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

...y así.

Para obtener el patrón de bits del ordinal, simplemente decodifica cada bit por turno. Algo como esto, en un lenguaje 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;
}

Tenga en cuenta que dado que solo necesita coeficientes binomiales de hasta 64, puede calcularlos previamente.


  • Cover, T., Enumerative Source Encoding . IEEE Transactions on Information Theory, Vol. IT-19, No 1, enero de 1973.

Hermosa y elegante! La codificación enumerativa parece algo muy útil: ¿hay algún buen recurso (preferiblemente en forma de libro de texto)?
Koz Ross

¿Esto realmente da un mejor rendimiento en la práctica? (Por supuesto, depende de la velocidad del RNG). Si no, entonces no tiene sentido usar código más complejo.
Gilles 'SO- deja de ser malvado'

1
@Giles Interpreté esto como una pregunta de informática, ya que se trata de cs.se. Solo di el código fuente porque resultaba que lo tenía por una implementación de matriz RRR. (Ver, por ejemplo, alexbowe.com/rrr para una explicación de lo que eso significa.)
Seudónimo

1
@Gilles Para dar seguimiento a su pregunta, implementé tanto mi método ingenuo como el proporcionado por Pseudonym en Forth. El método ingenuo, incluso cuando se usa un PRNG xorshift muy simple, tomó algo del orden de 20 segundos por número , mientras que el método del seudónimo fue casi instantáneo. Usé tablas de binomios precalculados para esto.
Koz Ross

1
@KozRoss Si genera números de n bits y busca números con k bits establecidos, serían bastante raros si k está lejos de n / 2; eso lo explicaría.
gnasher729

3

Muy similar a la respuesta del seudónimo, obtenida por otros medios.

El número total de combinaciones disponibles es accesible por el método de barras y estrellas , por lo que deberá ser . El número total de números de 64 bits de los que intentaría muestrear su número sería obviamente mucho más alto que eso.c=(64n)

Entonces, lo que necesita es una función que lo pueda llevar desde un número pseudoaleatorio , que va de a , a la combinación de 64 bits correspondiente.k1c

El triángulo de Pascal puede ayudarlo con eso, porque el valor de cada nodo representa exactamente el número de rutas desde ese nodo hasta la raíz del triángulo, y cada ruta se puede hacer para representar una de las cadenas que está buscando, si todos los giros a la izquierda son etiquetado con un , y cada giro a la derecha con un .10

Deje que sea ​​el número de bits que quedan por determinar, sea ​​el número de bits que quedan por usar.xy

Sabemos que , y podemos usarlo para determinar adecuadamente el siguiente bit del número en cada paso:(xy)=(x1y)+(x1y1)

whilex>0

ifx>y

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

else:ss+"0"

else:ss+"1",yy1

xx1


2

Otro método bastante elegante es usar la bisección como se describe en esta respuesta de stackoverflow . La idea es mantener dos palabras, una que tenga como máximo un conjunto de k bits y otra que tenga al menos un conjunto de k bits, y usar la aleatoriedad para mover una de estas hacia tener exactamente k bits. Aquí hay un código fuente para ilustrarlo:

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

Hice una comparación de rendimiento de varios métodos y este suele ser el más rápido a menos que se sepa que k es muy pequeño.


0

Puedes hacer lo siguiente:

1) Generar un número aleatorio, entre y .k164

2) Establezca th a .k01

3) Repita los pasos 1 y 2 vecesn

A[] es una matriz de bits con todos los 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 no parece coincidir con tu código? El código nunca asigna 1s a la matriz. Tampoco parece generar una distribución uniforme (y ni siquiera números que satisfagan las restricciones) cuando múltiples ks chocan
Bergi

@ Bergi Ya olvidó la línea ... la agregó ahora. Y se maneja la colisión múltiple de k. Ver primer número elegido entre 1 y 64, segundo entre 1 y "restante" 63. Por lo tanto, omite el 1 mientras cuenta ... vea ellínea. Y es distribución uniforme. A[x]=1if(A[x]==0)k;
Usuario no encontrado

Ah, ya veo ahora. El algoritmo de prosa no mencionó la omisión.
Bergi

@ArghyaChakraborty ¿Está utilizando indexación basada en 1 allí?
Koz Ross

@KozRoss Comience con lo que sucede si (por supuesto, será todo ceros) Entonces, verificará y obtendrá el significadolo que da . Entonces, establece fuera del ciclo. Entonces sí, es una indexación basada en 1. Para hacerlo basado en 0, todo lo que tiene que hacer es cambiar el interno ai=1,k=1AA[1]==0truek;k=0A[1]=1for(x=0;x<64;x++)
Usuario no encontrado el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.