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:
Genere un número pseudoaleatorio de 64 bits .k
Cuente los bits en , almacenando el resultado en .kb
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
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:nxk…x1
∑1≤i≤k(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:
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)=(x−1y)+(x−1y−1)
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;
}
¿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++)
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.k 1 c
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 .1 0
Deje que sea el número de bits que quedan por determinar, sea el número de bits que quedan por usar.x y
Sabemos que , y podemos usarlo para determinar adecuadamente el siguiente bit del número en cada paso:(xy)=(x−1y)+(x−1y−1)
fuente
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:
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.
fuente
Puedes hacer lo siguiente:
1) Generar un número aleatorio, entre y .k 1 64
2) Establezca th a .k 0 1
3) Repita los pasos 1 y 2 vecesn
fuente
1
s a la matriz. Tampoco parece generar una distribución uniforme (y ni siquiera números que satisfagan las restricciones) cuando múltiplesk
s chocan