(He hecho la pregunta en stackoverflow.com aquí pero tal vez sea mejor preguntar aquí por la parte estadística. Siéntase libre de corregir mi pregunta, arreglar las etiquetas, redirigirme...)
Necesito un generador para muchos (hasta un trillón), $10^{12}$ ) números únicos y pseudoaleatorios de 64 bits, es decir, el rango $[0..2^{64})$ . Debe ser lo más "justo" posible, tener la misma distribución estadística que si se generara con un pseudo-RNG normal y luego se ordenara. El problema es que la clasificación $10^{12}$ Los números son lentos. El caso de uso es replicar una prueba que se ejecutó para BBHash (en el papel 4.5 Indexación de un trillón de llaves).
La solución más sencilla (errónea, en mi opinión), que no es aleatoria, es generar una secuencia uniforme como 0, 1000, 2000,... . Mirando el código fuente, esto parece ser lo que hace el test BBHash. Otra solución es iterar sobre todos los números, y con una probabilidad aproximada de ${10^{12}}/{2^{64}}$ devuelve el número, en caso contrario lo omite. Pero esto es demasiado lento, y no devolverá exactamente tantos resultados (y ajustando la probabilidad sobre la marcha parece que la distribución no sería del todo correcta).
Creo que hay tres enfoques:
Lagunas aleatorias
Tal vez la solución en " Un algoritmo eficiente para el muestreo aleatorio secuencial " funcionaría, pero es lento porque para cada iteración hay que calcular muchos números en coma flotante. Además, la suma de esos huecos, después de $10^{12}$ puede no aterrizar donde debería debido al redondeo (no me gustaría intentarlo).
Rangos de tamaño aleatorio con número fijo de entradas
Con un generador de números aleatorios, divide el rango $[0..2^{64})$ en un millón de subrangos de tamaño aleatorio que deben contener un millón de entradas cada uno. Pero cómo y qué distribución utilizar. A continuación, generar un millón de números en ese rango utilizando un pseudo-RNG.
Número aleatorio de entradas para cada (sub)rango fijo
Crear un árbol: Para cada nivel de bits (empezando por el bit más significativo), genera recursivamente un número aleatorio de cuántas entradas deben tener el bit en ese nivel puesto a 0, usando la distribución normal. El resto de entradas tienen el bit de ese nivel a 1. En cada nivel de recursión, esto reducirá el rango a la mitad aproximadamente. Deténgase, por ejemplo, cuando haya menos de 1 millón de entradas, y entonces cambie a usar un pseudo-RNG en memoria y ordene esos números (o use un campo de bits).
Puedo calcular la probabilidad de que, para $x$ lanzamientos de monedas, más de $y$ son cabezas, utilizando el distribución normal (con un CDF aproximación). Y luego elegir un número al azar y hacer una búsqueda binaria. ¿Es esa la forma correcta, la mejor (la más fácil, la más rápida)? Aquí está el código que utilizo hasta ahora (código Java, pero el algoritmo y las fórmulas deberían ser relativamente fáciles de entender incluso si no sabes Java):
public static void main(String... args) {
Random r = new Random();
Iterator<Long> it = randomSequence(r, 10, 32);
while(it.hasNext()) {
System.out.println(it.next());
}
}
/**
* Random sequence generator.
*
* @param r the random generator
* @param size the number of entries to generate
* @param shift the number of bits of the result
* @return the iterator
*/
static Iterator<Long> randomSequence(final Random r, final long size, final int shift) {
if (size < 5) {
// small lists are generated using a regular hash set
TreeSet<Long> set = new TreeSet<Long>();
while (set.size() < size) {
set.add(r.nextLong() & ((2L << shift) - 1));
}
return set.iterator();
}
// large lists are created recursively
return new Iterator<Long>() {
long remaining = size, zeros = randomHalf(r, size);
Iterator<Long> lowBits0 = randomSequence(r, zeros, shift - 1);
Iterator<Long> lowBits1;
@Override
public boolean hasNext() {
return remaining > 0;
}
@Override
public Long next() {
remaining--;
if (lowBits0.hasNext()) {
return lowBits0.next();
}
if (lowBits1 == null) {
lowBits1 = randomSequence(r, size - zeros, shift - 1);
}
return (1L << shift) + lowBits1.next();
}
};
}
/**
* Get the number of entries that are supposed to be below the half,
* according to the probability theory. For example, for a number of coin
* flips, how many are heads.
*
* @param r the random generator
* @param samples the total number of entries
* @return the number of entries that should be used for one half
*/
static long randomHalf(Random r, long samples) {
long low = 0, high = samples;
double x = r.nextDouble();
while (low + 1 < high) {
long mid = (low + high) / 2;
double p = probabilityBucketAtMost(samples, mid);
if (x > p) {
low = mid;
} else {
high = mid;
}
}
return (low + high) / 2;
}
static double probabilityBucketAtMost(long flips, long heads) {
// https://www.fourmilab.ch/rpkp/experiments/statistics.html
long x = heads;
long n = flips;
double variance = Math.sqrt(n/4);
// mean
long mu = n / 2;
// https://en.wikipedia.org/wiki/Normal_distribution
// Numerical approximations for the normal CDF
// the probability that the value of a standard normal random variable X is <= x
return phi((x - mu) / variance);
}
static double phi(double x) {
return 0.5 * (1 + Math.signum(x) * Math.sqrt(1 - Math.exp(-2 * x * x / Math.PI)));
}
2 votos
La naturaleza de esta cuestión es oscura. Gran parte de ella parece centrarse en el tratamiento de la aritmética de alta precisión. En parte se habla de "bloques", cuyo significado no está definido, y en ninguna parte se explica qué propiedades deben tener esos "números aleatorios": ¿deben ser independientes? ¿uniformes? ¿Están limitados a un intervalo predefinido? ¿Qué puede significar "utilizar la probabilidad de que el valor actual coincida"? Parece que algunos aspectos de esta situación son interesantes, pero si quieres una respuesta, por favor, haz ediciones que aclaren lo que necesitas.
0 votos
¿Necesita generar exactamente $10^{12}$ valores o sólo hay que seleccionar cada valor de forma independiente con probabilidad $10^{12}/2^{64}$ ?
0 votos
Mi opinión es que la forma más rápida de hacerlo es ordenar las variantes aleatorias en paralelo, a medida que se producen.
1 votos
@whuber Necesito exactamente $10^{12}$ . Esto es para replicar una prueba que hicieron para BBHash donde generaron exactamente esa cantidad de números. En su código, los números son no al azar, por lo que no es una prueba realista.
0 votos
@Kodiologist ¿te refieres a ordenar en disco? Esa cantidad de números son 8 TB. Primero, no tengo un disco así (bueno, se puede hacer en Amazon EC2, lo sé). Segundo, cómo asegurar que no hay duplicados (no se puede simplemente volver a hacer la prueba; la probabilidad de duplicados es simplemente demasiado grande. Tendrías que generar más números, volver a hacer la prueba,...). Y lleva mucho tiempo. 2 horas sólo para leer las claves, sin ordenarlas. Así que, no, no me gustaría ordenar.