Me he estado preguntando cuál sería la mejor manera de obtener una buena aleatoriedad en bash, es decir, cuál sería un procedimiento para obtener un entero positivo aleatorio entre MIN
y MAX
tal que
- El rango puede ser arbitrariamente grande (o al menos, digamos, hasta 2 32 -1);
- Los valores están distribuidos uniformemente (es decir, sin sesgo);
- Es eficiente
Una forma eficiente de obtener aleatoriedad en bash es usar la $RANDOM
variable. Sin embargo, esto solo muestra un valor entre 0 y 2 15 -1, que puede no ser lo suficientemente grande para todos los propósitos. Las personas generalmente usan un módulo para llevarlo al rango que desean, por ejemplo,
MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))
Esto, además, crea un sesgo a menos que $MAX
ocurra dividir 2 15 -1 = 32767. Por ejemplo, si $MIN
es 0 y $MAX
es 9, entonces los valores de 0 a 7 son ligeramente más probables que los valores de 8 y 9, ya $RANDOM
que nunca serán 32768 o 32769. Este sesgo empeora a medida que aumenta el rango, por ejemplo, si $MIN
es 0 y $MAX
es 9999, a continuación, los números 0 a 2767 tener una probabilidad de 4 / 32767 , mientras que los números 2768 a 9999 sólo tienen una probabilidad de 3 / 32767 .
Entonces, aunque el método anterior cumple la condición 3, no cumple las condiciones 1 y 2.
El mejor método que se me ocurrió hasta ahora para tratar de satisfacer las condiciones 1 y 2 fue usar /dev/urandom
lo siguiente:
MIN=0
MAX=1234567890
while
rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
[ -z $rnd ] && rnd=0
(( $rnd < $MIN || $rnd > $MAX ))
do :
done
Básicamente, solo recopile la aleatoriedad de /dev/urandom
(podría considerar usar /dev/random
en su lugar si se desea un generador de números pseudoaleatorios criptográficamente fuerte, y si tiene mucho tiempo, o tal vez un generador de números aleatorios de hardware), elimine cada carácter que no sea un dígito decimal, doble la salida a la longitud de $MAX
y corta los ceros iniciales. Si solo obtuvimos 0, entonces $rnd
está vacío, por lo que en este caso se establece rnd
en 0
. Compruebe si el resultado está fuera de nuestro rango y, de ser así, repita. Forcé el "cuerpo" del bucle while en la guardia aquí para forzar la ejecución del cuerpo al menos una vez, con el espíritu de emular un do ... while
bucle, ya rnd
que no está definido para empezar.
Creo que cumplí las condiciones 1 y 2 aquí, pero ahora arruiné la condición 3. Es un poco lento. Toma hasta un segundo más o menos (décima de segundo cuando tengo suerte). En realidad, ni siquiera se garantiza que el ciclo termine (aunque la probabilidad de terminación converge a 1 a medida que aumenta el tiempo).
¿Hay una manera eficiente de obtener enteros aleatorios imparciales, dentro de un rango preespecificado y potencialmente grande, en bash? (Continuaré investigando cuando el tiempo lo permita, ¡pero mientras tanto pensé que alguien aquí podría tener una idea genial!)
Tabla de respuestas
La idea más básica (y por lo tanto portátil) es generar una cadena de bits aleatoria el tiempo suficiente. Hay diferentes formas de generar una cadena de bits aleatoria, ya sea usando la
$RANDOM
variable incorporada de bash o usandood
y/dev/urandom
(o/dev/random
). Si el número aleatorio es mayor que$MAX
, comience de nuevo.Alternativamente, es posible utilizar herramientas externas.
- La solución de Perl
- Pro: bastante portátil, simple, flexible
- Contra: no para números muy grandes por encima de 2 32 -1
- La solución de Python
- Pro: simple, flexible, funciona incluso para grandes cantidades
- Contra: menos portátil
- La solución zsh
- Pro: bueno para las personas que usan zsh de todos modos
- Contra: probablemente incluso menos portátil
- La solución de Perl
fuente
rand=$(command)
hacer algo sicommand
devuelve un iteger que cumple con sus requisitos?dd if=/dev/urandom 2>/dev/null
y canalizando esood -t d
(evita el desvío a través de base64), pero no me queda claro cómo ocurre la conversión y si es realmente imparcial. Si puede ampliar su idea a un guión eficiente y funcional y explicar por qué no hay sesgo, sería una gran respuesta. :)python
operl
tu idioma favorito, pero esto no está disponible en todas partes. Prefiero algo más portátil. Bueno,awk
la función aleatoria estaría bien, supongo. Pero cuanto más portátil, mejor :)perl -e 'print int(rand(2**32-1))');
. Eso es bastante portátil y será muy rápido. Awk no lo cortará, ya que la mayoría de las implementaciones comienzan desde la misma semilla. Entonces obtienes el mismo número aleatorio en ejecuciones posteriores. Solo cambia dentro de la misma ejecución.Respuestas:
Veo otro método interesante desde aquí .
Esta también parece ser una buena opción. Lee 4 bytes del dispositivo aleatorio y los formatea como un entero sin signo entre
0
y2^32-1
.fuente
/dev/urandom
menos que sepa que lo necesita/dev/random
;/dev/random
bloques en Linux.od
comandos son diferentes? Ambos imprimen enteros sin signo de 4 bytes: primero - desde openssl, segundo - desde/dev/random
./dev/urandom
lugar de/dev/random
: no veo ninguna razón para usar/dev/random
, y puede ser muy costoso / lento o ralentizar otras partes del sistema. (Siéntase libre de volver a editar y explicar si realmente es necesario.)I
significasizeof(int)
que puede ser menor que4
en principio. por cierto,od -DAn
falla(2**32-1)
perood -N4 -tu4 -An
sigue funcionando.Gracias a todos por todas sus excelentes respuestas. Terminé con la siguiente solución, que me gustaría compartir.
Antes de entrar en más detalles sobre por qué y cómo, aquí está el tl; dr : mi brillante nuevo script :-)
Guarde eso
~/bin/rand
y tendrá a su disposición una dulce función aleatoria en bash que puede muestrear un número entero en un rango arbitrario dado. El rango puede contener enteros negativos y positivos y puede tener hasta 2 60 -1 de longitud:Todas las ideas de los otros que respondieron fueron geniales. Las respuestas de terdon , JF Sebastian y jimmij utilizaron herramientas externas para realizar la tarea de manera simple y eficiente. Sin embargo, preferí una verdadera solución bash para la máxima portabilidad, y tal vez un poco, simplemente por amor a bash;)
Las respuestas de Ramesh y l0b0 utilizadas
/dev/urandom
o/dev/random
en combinación conod
. Eso es bueno, sin embargo, sus enfoques tenían la desventaja de que solo podían muestrear enteros aleatorios en el rango de 0 a 2 8n -1 para algunos n, ya que este método muestrea bytes, es decir, cadenas de bits de longitud 8. Estos son saltos bastante grandes con creciente n.Finalmente, la respuesta de Falco describe la idea general de cómo esto podría hacerse para rangos arbitrarios (no solo potencias de dos). Básicamente, para un rango dado
{0..max}
, podemos determinar cuál es la siguiente potencia de dos, es decir, exactamente cuántos bits se requieren para representarmax
como una cadena de bits. Luego podemos muestrear tantos bits y ver si este bistring, como entero, es mayor quemax
. Si es así, repita. Dado que muestreamos tantos bits como sea necesario para representarmax
, cada iteración tiene una probabilidad mayor o igual al 50% de tener éxito (50% en el peor de los casos, 100% en el mejor de los casos). Entonces esto es muy eficiente.Mi script es básicamente una implementación concreta de la respuesta de Falco, escrita en puro bash y altamente eficiente, ya que utiliza las operaciones bit a bit integradas de bash para muestrear cadenas de bits de la longitud deseada. También honra una idea de Eliah Kagan que sugiere utilizar la
$RANDOM
variable incorporada al concatenar cadenas de bits resultantes de invocaciones repetidas de$RANDOM
. De hecho, implementé las posibilidades de uso/dev/urandom
y$RANDOM
. Por defecto, el script anterior usa$RANDOM
. (Y bueno, si usamos/dev/urandom
, necesitamos od y tr , pero estos están respaldados por POSIX).¿Entonces, cómo funciona?
Antes de entrar en esto, dos observaciones:
Resulta que bash no puede manejar enteros mayores de 2 63 -1. Ver por ti mismo:
Parece que bash usa internamente enteros de 64 bits con signo para almacenar enteros. Entonces, en 2 63 "se envuelve" y obtenemos un número entero negativo. Por lo tanto, no podemos esperar obtener un rango mayor que 2 63 -1 con cualquier función aleatoria que usemos. Bash simplemente no puede manejarlo.
Siempre que queramos muestrear un valor en un rango arbitrario entre
min
ymax
posiblementemin != 0
, simplemente podemos muestrear un valor entre0
y en sumax-min
lugar y luego agregarlomin
al resultado final. Esto funciona incluso si esmin
posible quemax
sea negativo , pero debemos tener cuidado de muestrear un valor entre0
y el valor absoluto demax-min
. Entonces, podemos centrarnos en cómo muestrear un valor aleatorio entre0
y un entero positivo arbitrariomax
. El resto es fácil.Paso 1: Determine cuántos bits se necesitan para representar un número entero (el logaritmo)
Entonces, para un valor dado
max
, queremos saber cuántos bits se necesitan para representarlo como una cadena de bits. Esto es para que luego podamos muestrear aleatoriamente solo tantos bits como sean necesarios, lo que hace que el script sea tan eficiente.Veamos. Como con
n
bits, podemos representar hasta el valor 2 n -1, entonces el númeron
de bits necesarios para representar un valor arbitrariox
es el techo (log 2 (x + 1)). Por lo tanto, necesitamos una función para calcular el techo de un logaritmo a la base 2. Es bastante explicativo:Necesitamos la condición,
n>0
por lo que si crece demasiado, se envuelve y se vuelve negativa, se garantiza que el ciclo terminará.Paso 2: muestrear una cadena de bits aleatoria de longitud
n
Las ideas más portátiles son usar
/dev/urandom
(o incluso/dev/random
si hay una razón sólida) o la$RANDOM
variable incorporada de bash . Veamos$RANDOM
primero cómo hacerlo .Opción A: usar
$RANDOM
Esto utiliza la idea mencionada por Eliah Kagan. Básicamente, dado que
$RANDOM
muestrea un entero de 15 bits, podemos usarlo$((RANDOM<<15|RANDOM))
para muestrear un entero de 30 bits. Eso significa, desplazar una primera invocación de$RANDOM
15 bits hacia la izquierda, y aplicar una invocación a nivel de bit o con una segunda invocación de$RANDOM
, concatenando efectivamente dos cadenas de bits muestreadas independientemente (o al menos tan independientes como va el incorporado de bash$RANDOM
).Podemos repetir esto para obtener un entero de 45 bits o 60 bits. Después de que bash ya no puede manejarlo, pero esto significa que podemos muestrear fácilmente un valor aleatorio entre 0 y 2 60 -1. Entonces, para muestrear un número entero de n bits, repetimos el procedimiento hasta que nuestra cadena de bits aleatoria, cuya longitud crece en pasos de 15 bits, tenga una longitud mayor o igual que n. Finalmente, cortamos los bits que son demasiado desplazándonos adecuadamente a la derecha, y terminamos con un entero aleatorio de n bits.
Opción B: uso
/dev/urandom
Alternativamente, podemos usar
od
y/dev/urandom
para muestrear un número entero de n bits.od
leerá bytes, es decir, cadenas de bits de longitud 8. Del mismo modo que en el método anterior, muestreamos tantos bytes que el número equivalente de bits muestreados es mayor o igual que n, y cortamos los bits que son demasiado.El número más bajo de bytes necesarios para obtener al menos n bits es el múltiplo más bajo de 8 que es mayor o igual que n, es decir, piso ((n + 7) / 8).
Esto solo funciona con enteros de hasta 56 bits. El muestreo de un byte más nos daría un número entero de 64 bits, es decir, un valor de hasta 2 64 -1, que bash no puede manejar.
Poner las piezas juntas: obtener enteros aleatorios en rangos arbitrarios
Nos puede muestrear
n
bits bitstrings ahora, pero queremos enteros de la muestra en un rango de0
amax
, de manera uniforme al azar , dondemax
puede ser arbitrario, no necesariamente una potencia de dos. (No podemos usar el módulo ya que eso crea un sesgo).El punto principal por el que tratamos de muestrear tantos bits como sea necesario para representar el valor
max
, es que ahora podemos usar de forma segura (y eficiente) un bucle para muestrear repetidamente unan
cadena de bits de un bit hasta que muestreemos un valor que es más bajo o igual amax
. En el peor de los casos (max
es una potencia de dos), cada iteración termina con una probabilidad del 50%, y en el mejor de los casos (max
es una potencia de dos menos uno), la primera iteración termina con certeza.Terminando las cosas
Finalmente, queremos muestrear enteros entre
min
ymax
, dondemin
ymax
pueden ser arbitrarios, incluso negativos. Como se mencionó anteriormente, esto ahora es trivial.Pongámoslo todo en un script bash. Haga algunos análisis de argumentos ... Queremos dos argumentos
min
ymax
, o solo un argumentomax
, donde losmin
valores predeterminados sean0
.... y, finalmente, para muestrear uniformemente al azar un valor entre
min
ymax
, muestreamos un entero aleatorio entre0
y el valor absoluto demax-min
, y lo sumamosmin
al resultado final. :-)Inspirado por esto , podría intentar usar dieharder para probar y comparar este PRNG, y poner mis hallazgos aquí. :-)
fuente
sizeof(int) == 8
(64 bits) debido a--format=u
random.Random
clase usa 53bit? generador para devolver números aleatorios grandes arbitrarios (invocaciones múltiples),random.SystemRandom
hace lo mismo usandoos.urandom()
que puede implementarse usando/dev/urandom
.--format=u8
, codifico la suposiciónsizeof(int)==8
. Por otro lado, si se usa--format=uL
no hay problema: no creo que haya una plataforma que tenga enteros de 64 bits pero que todavía defina entradas largas como algo más bajo. Entonces, básicamente, diría que--format=uL
permite una mayor flexibilidad. ¿Cuáles son tus pensamientos?long long
que puede ser de 64 bits mientras que int = long = 32bit en algunas plataformas. No debe reclamar el rango 0..2 ** 60 si no puede garantizarlo en todas las plataformas. Por otro lado, bash podría no admitir este rango en tales plataformas (no lo sé, tal vez usa maxint_t y luego u8 es más correcto si desea afirmar el rango fijo (od
no admite especificar maxint si el rango suyo es cualquiera que sea el rango dependiente de la plataforma bash?) Si el rango bash depende del tamaño de largo, entonces uL podría ser más apropiado). ¿Desea el rango completo que bash admite en todos los sistemas operativos o un rango fijo?¿Puede ser zsh?
Es posible que desee utilizar semillas también con
rand48(seed)
. Veaman zshmodules
yman 3 erand48
para una descripción detallada si está interesado.fuente
python
está disponible en Redhat, sistemas basados en Debian.fuente
Si desea un número del 0 al (2 ^ n) -1 donde n mod 8 = 0 , simplemente puede obtener n / 8 bytes
/dev/random
. Por ejemplo, para obtener la representación decimal de un azarint
puede:Si desea tomar solo n bits , primero puede tomar el tope (n / 8) bytes y desplazarse a la derecha a la cantidad que desee. Por ejemplo, si quieres 15 bits:
Si está absolutamente seguro de que no le importa la calidad de la aleatoriedad y desea garantizar un tiempo de ejecución mínimo que puede usar en
/dev/urandom
lugar de/dev/random
. ¡Asegúrese de saber lo que está haciendo antes de usar/dev/urandom
!fuente
n
bytes aleatorios/dev/urandom
y formatee usandood
. Similar en espíritu a esta respuesta . Ambos son igualmente buenos :) Aunque ambos tienen la desventaja de tener un rango fijo de 0 a 2 ^ (n * 8) -1 bits, donde n es el número de bytes. Preferiría un método para un rango arbitrario , hasta 2 ^ 32-1, pero también algo más bajo. Esto crea la dificultad de sesgo./dev/urandom
lugar de/dev/random
: no veo ninguna razón para usarlo/dev/random
, y puede ser realmente costoso / lento o ralentizar otras partes del sistema. (Siéntase libre de volver a editar y explicar si realmente es necesario.)/dev/urandom
resultados son mucho peores que/dev/random
ese urandom no es utilizable en la mayoría de los casos. Una vez/dev/urandom
se inicializa (al inicio del sistema); Sus resultados son tan buenos como/dev/random
para casi todas las aplicaciones en Linux. En algunos sistemas, aleatorio y urandom son lo mismo.--format=u
debe ser reemplazado por--format=u4
porquesizeof(int)
puede ser menor que4
en teoría./dev/random
y/dev/urandom
son insatisfactorios, y que "Linux debería añadir un generador de números aleatorios seguro que bloquea hasta que se haya recogido la entropía semilla adecuada y, posteriormente, se comporta comourandom
".Suponiendo que no se oponga al uso de herramientas externas, esto debería cumplir con sus requisitos:
Está utilizando la
rand
función de perl que toma un límite superior como parámetro. Puedes configurarlo como quieras. Qué tan cerca está esto de la aleatoriedad verdadera en la definición matemática abstracta está más allá del alcance de este sitio, pero debería estar bien a menos que lo necesite para un cifrado extremadamente sensible o similar. Quizás incluso allí, pero no me aventuraré a opinar.fuente
1^32-1
pero debes modificarlo para números más grandes.Debería obtener el más cercano (2 ^ X) -1 igual o mayor que el máximo deseado y obtener el número de bits. Luego simplemente llame / dev / random varias veces y agregue todos los bits juntos hasta que tenga suficiente, truncando todos los bits que son demasiado. Si el número resultante es mayor que su repetición máxima. En el peor de los casos, tiene una probabilidad superior al 50% de obtener un número aleatorio por debajo de su Máximo, por lo que (en el peor de los casos) atenderá dos llamadas en promedio.
fuente
/dev/urandom
, pero en ambas respuestas es siempre un múltiplo de 8 bits. Truncar los bits que son demasiado para los rangos más bajos antes de formatear con decimalod
es una buena idea para mejorar la eficiencia, ya que el bucle solo tiene un número esperado de 2 iteraciones, como bien explica. Esto, combinado con cualquiera de las respuestas mencionadas, es probablemente el camino a seguir.Su respuesta es interesante pero bastante larga.
Si desea números arbitrariamente grandes, puede unir varios números aleatorios en un asistente:
Si el problema es parcial, simplemente elimínelo.
Uniendo estas funciones juntas
fuente