Eu tenho me perguntado qual seria a melhor maneira de obter uma boa aleatoriedade no bash, ou seja, qual seria um procedimento para obter um número inteiro positivo aleatório entre MIN
e MAX
tal que
- O intervalo pode ser arbitrariamente grande (ou pelo menos, digamos, até 2 32 -1);
- Os valores são distribuídos uniformemente (ou seja, sem viés);
- É eficiente.
Uma maneira eficiente de obter aleatoriedade no bash é usar a $RANDOM
variável No entanto, isso apenas mostra um valor entre 0 e 2 15 -1, que pode não ser grande o suficiente para todos os fins. As pessoas normalmente usam um módulo para colocá-lo no intervalo que desejam, por exemplo,
MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))
Além disso, isso cria um viés, a menos $MAX
que divida 2 15 -1 = 32767. Por exemplo, se $MIN
for 0 e $MAX
9, os valores de 0 a 7 são um pouco mais prováveis que os valores 8 e 9, como $RANDOM
nunca serão 32768 ou 32769. Esse viés piora à medida que o intervalo aumenta, por exemplo, se $MIN
é 0 e $MAX
é 9999, em seguida, os números 0 a 2767 tem uma probabilidade de 4 / 32767 , enquanto que os números de 2768 até 9999 só tem uma probabilidade de 3 / 32767 .
Portanto, embora o método acima preencha a condição 3, ele não preenche as condições 1 e 2.
O melhor método que eu criei até agora na tentativa de satisfazer as condições 1 e 2 foi usar /dev/urandom
o seguinte:
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
Basicamente, basta coletar aleatoriedade de /dev/urandom
(pode considerar usar /dev/random
um gerador de números pseudoaleatórios criptograficamente fortes, e se você tiver muito tempo, ou talvez um gerador de números aleatórios de hardware), exclua todos os caracteres que não sejam um dígito decimal, dobre a saída para o comprimento $MAX
e corte os zeros à esquerda. Se por acaso obtivemos apenas 0, então $rnd
está vazio, portanto, neste caso, defina rnd
como 0
. Verifique se o resultado está fora do nosso alcance e, em caso afirmativo, repita. Eu forcei o "corpo" do loop while para a guarda aqui, a fim de forçar a execução do corpo pelo menos uma vez, no espírito de emular um do ... while
loop, já que rnd
é indefinido para começar.
Acho que cumpri as condições 1 e 2 aqui, mas agora estraguei a condição 3. É meio lento. Leva mais ou menos um segundo (décimo de segundo quando tenho sorte). Na verdade, nem sequer é garantido que o loop termine (embora a probabilidade de término converja para 1 à medida que o tempo aumenta).
Existe uma maneira eficiente de obter números inteiros aleatórios imparciais, dentro de um intervalo pré-especificado e potencialmente grande, no bash? (Continuarei a investigar quando o tempo permitir, mas, enquanto isso, acho que alguém aqui pode ter uma ideia interessante!)
Tabela de respostas
A idéia mais básica (e, portanto, portátil) é gerar uma sequência de bits aleatória apenas o tempo suficiente. Existem diferentes maneiras de gerar uma sequência de bits aleatória, usando a
$RANDOM
variável interna do bash ou usandood
e/dev/urandom
(ou/dev/random
). Se o número aleatório for maior que$MAX
, inicie novamente.Como alternativa, é possível usar ferramentas externas.
- A solução Perl
- Pro: bastante portátil, simples, flexível
- Contra: não para números muito grandes acima de 2 32 -1
- A solução Python
- Pro: simples, flexível, funciona mesmo para grandes números
- Contra: menos portátil
- A solução zsh
- Pro: bom para quem usa o zsh de qualquer maneira
- Contra: provavelmente ainda menos portátil
- A solução Perl
fonte
rand=$(command)
fazer secommand
retornar um iteger que atenda aos seus requisitos?dd if=/dev/urandom 2>/dev/null
e canalizando issood -t d
(evita o desvio através da base64), mas não está claro para mim como a conversão acontece e se é realmente imparcial. Se você puder expandir sua ideia para um script eficiente e funcional e explicar por que não há viés, seria uma ótima resposta. :)python
ouperl
ou seu idioma favorito, mas isso não está disponível em todos os lugares. Eu preferiria algo mais portátil. Bem,awk
a função aleatória seria boa, eu acho. Mas o mais portátil, o melhor :)perl -e 'print int(rand(2**32-1))');
. Isso é bastante portátil e será muito rápido. O Awk não funciona, pois a maioria das implementações começa na mesma semente. Então você obtém o mesmo número aleatório nas execuções subsequentes. Apenas muda dentro da mesma execução.Respostas:
Eu vejo outro método interessante daqui .
Este também parece ser uma boa opção. Ele lê 4 bytes do dispositivo aleatório e os formata como um número inteiro não assinado entre
0
e2^32-1
.fonte
/dev/urandom
menos que saiba que precisa/dev/random
;/dev/random
blocos no Linux.od
comandos são diferentes. Ambos apenas imprimem números inteiros não assinados de 4 bytes: 1º - do openssl, 2º - do/dev/random
./dev/urandom
vez de/dev/random
- não vejo razão para usar/dev/random
, e pode ser muito caro / lento ou retardar outras partes do sistema. (Sinta-se fazer livre editar para trás e explicar se é realmente necessário.)I
significasizeof(int)
que pode ser menor do que4
em princípio. Aliás,od -DAn
falha ,(2**32-1)
masod -N4 -tu4 -An
continua a funcionar.Obrigado a todos pelas ótimas respostas. Acabei com a seguinte solução, que gostaria de compartilhar.
Antes de entrar em mais detalhes sobre os porquês e comos, aqui está o tl; dr : meu brilhante novo script :-)
Salve isso
~/bin/rand
e você terá à sua disposição uma função aleatória doce no bash que pode amostrar um número inteiro em um determinado intervalo arbitrário. O intervalo pode conter números inteiros negativos e positivos e pode ter até 2 60 -1 de comprimento:Todas as idéias dos outros respondentes foram ótimas. As respostas de terdon , JF Sebastian e jimmij usaram ferramentas externas para executar a tarefa de maneira simples e eficiente. No entanto, eu preferi uma solução verdadeira do bash para máxima portabilidade e, talvez um pouco, simplesmente por amor ao bash;)
As respostas de Ramesh e l0b0 usadas
/dev/urandom
ou/dev/random
em combinação comod
. Isso é bom, no entanto, suas abordagens tiveram a desvantagem de poder apenas amostrar números inteiros aleatórios no intervalo de 0 a 2 8n -1 para alguns n, já que esse método faz uma amostra de bytes, ou seja, bitstrings de comprimento 8. Esses são saltos bastante grandes com crescente n.Finalmente, a resposta de Falco descreve a idéia geral de como isso pode ser feito para intervalos arbitrários (não apenas para poderes de dois). Basicamente, para um determinado intervalo
{0..max}
, podemos determinar qual é a próxima potência de dois, ou seja, exatamente quantos bits são necessários para representarmax
como uma cadeia de bits . Em seguida, podemos amostrar apenas esses bits e ver se esse bistring, como um inteiro, é maior quemax
. Se sim, repita. Como amostramos o número de bits necessário para representarmax
, cada iteração tem uma probabilidade maior ou igual a 50% de êxito (50% no pior caso, 100% no melhor caso). Então, isso é muito eficiente.Meu script é basicamente uma implementação concreta da resposta de Falco, escrita em bash puro e altamente eficiente, pois usa as operações bit a bit internas do bash para obter amostras de strings de bit do comprimento desejado. Além disso, ele homenageia uma idéia de Eliah Kagan que sugere o uso da
$RANDOM
variável incorporada concatenando as cadeias de bits resultantes de repetidas invocações de$RANDOM
. Na verdade, eu implementei as possibilidades de usar/dev/urandom
e$RANDOM
. Por padrão, o script acima usa$RANDOM
. (E ok, se estiver usando/dev/urandom
, precisamos de od e tr , mas estes são suportados pelo POSIX.)Então, como isso funciona?
Antes de entrar nisso, duas observações:
Acontece que o bash não pode manipular números inteiros maiores que 2 63 -1. Veja por si mesmo:
Parece que o bash usa internamente números inteiros assinados de 64 bits para armazenar números inteiros. Então, em 2 63, ele "se envolve" e obtemos um número inteiro negativo. Portanto, não podemos esperar obter um intervalo maior que 2 63 -1 com qualquer função aleatória que usamos. O Bash simplesmente não consegue lidar com isso.
Sempre que quisermos amostrar um valor em um intervalo arbitrário entre
min
emax
commin != 0
, possivelmente , podemos simplesmente amostrar um valor entre0
e aomax-min
invés disso e depois adicionarmin
ao resultado final. Isso funciona mesmo quemin
e possivelmente tambémmax
seja negativo , mas precisamos ter cuidado para amostrar um valor entre0
e o valor absoluto demax-min
. Portanto, podemos nos concentrar em como amostrar um valor aleatório entre0
e um número inteiro positivo arbitráriomax
. O resto é fácil.Etapa 1: determinar quantos bits são necessários para representar um número inteiro (o logaritmo)
Portanto, para um determinado valor
max
, queremos saber quantos bits são necessários para representá-lo como uma cadeia de bits. Isso é para que mais tarde possamos amostrar aleatoriamente apenas quantos bits forem necessários, o que torna o script tão eficiente.Vamos ver. Como com
n
bits, podemos representar até o valor 2 n -1, então o númeron
de bits necessários para representar um valor arbitráriox
é teto (log 2 (x + 1)). Portanto, precisamos de uma função para calcular o teto de um logaritmo para a base 2. É bastante auto-explicativo:Precisamos da condição
n>0
para que, se ela crescer muito, contornar e se tornar negativa, o loop seja garantido para terminar.Etapa 2: experimente um bitstring aleatório de comprimento
n
As idéias mais portáteis são usar
/dev/urandom
(ou mesmo/dev/random
se houver um motivo forte) ou a$RANDOM
variável interna do bash . Vamos ver como fazer isso$RANDOM
primeiro.Opção A: Usando
$RANDOM
Isso usa a idéia mencionada por Eliah Kagan. Basicamente, uma vez que
$RANDOM
cria um número inteiro de 15 bits, podemos usar$((RANDOM<<15|RANDOM))
para amostrar um número inteiro de 30 bits. Isso significa que, desloque uma primeira invocação de$RANDOM
15 bits para a esquerda e aplique uma invocação bit a bit ou com uma segunda invocação$RANDOM
, concaturando efetivamente duas seqüências de bits com amostragem independente (ou pelo menos tão independente quanto o built-in do bash$RANDOM
).Podemos repetir isso para obter um número inteiro de 45 ou 60 bits. Depois que o bash não aguenta mais, mas isso significa que podemos facilmente amostrar um valor aleatório entre 0 e 2 60 -1. Portanto, para amostrar um número inteiro de n bits, repetimos o procedimento até que nossa cadeia de bits aleatória, cujo comprimento cresça em etapas de 15 bits, tenha um comprimento maior ou igual a n. Por fim, cortamos os bits que são demais, deslocando-se bit a bit apropriadamente para a direita e terminamos com um número inteiro aleatório de n bits.
Opção B: Usando
/dev/urandom
Como alternativa, podemos usar
od
e/dev/urandom
amostrar um número inteiro de n bits.od
lerá bytes, isto é, cadeias de bits de comprimento 8. Da mesma forma que no método anterior, coletamos tantos bytes que o número equivalente de bits amostrados é maior ou igual a n e eliminamos os bits que são demais.O menor número de bytes necessários para obter pelo menos n bits é o múltiplo mais baixo de 8 que é maior ou igual a n, ou seja, floor ((n + 7) / 8).
Isso funciona apenas com números inteiros de 56 bits. A amostragem de mais um byte nos daria um número inteiro de 64 bits, ou seja, um valor de até 2 64 -1, que o bash não pode manipular.
Juntando as peças: obtenha números aleatórios em intervalos arbitrários
Podemos provar
n
bits bitstrings agora, mas queremos inteiros de amostra em um intervalo de0
paramax
, uniformemente ao acaso , ondemax
pode ser arbitrária, não necessariamente uma potência de dois. (Não podemos usar o módulo, pois isso cria um viés.)O ponto principal por que tentamos tanto amostrar quantos bits são necessários para representar o valor
max
é que agora podemos usar com segurança (e eficientemente) um loop para amostrar repetidamente uman
cadeia de bits de bits até obtermos um valor menor ou igual amax
. No pior caso (max
é um poder de dois), cada iteração termina com uma probabilidade de 50% e, no melhor dos casos (max
é um poder de dois menos um), a primeira iteração termina com certeza.Embrulhando as coisas
Finalmente, queremos amostrar números inteiros entre
min
emax
, ondemin
emax
podem ser arbitrários e até negativos. Como mencionado anteriormente, isso agora é trivial.Vamos colocar tudo em um script bash. Faça algum argumento para analisar coisas ... Queremos dois argumentos
min
emax
, ou apenas um argumentomax
, onde omin
padrão é0
.... e, finalmente, para amostrar uniformemente aleatoriamente um valor entre
min
emax
, amostramos um número inteiro aleatório entre0
e o valor absoluto demax-min
e adicionamosmin
ao resultado final. :-)Inspirado por isso , posso tentar usar o dieharder para testar e comparar esse PRNG e colocar minhas conclusões aqui. :-)
fonte
sizeof(int) == 8
(64 bits) devido a--format=u
random.Random
classe usa 53bit? gerador para retornar números aleatórios grandes e arbitrários (várias invocações),random.SystemRandom
faz o mesmo usando oos.urandom()
que pode ser implementado usando/dev/urandom
.--format=u8
, codifico a suposiçãosizeof(int)==8
. Por outro lado, se usado,--format=uL
não há problema: não acho que exista uma plataforma que tenha números inteiros de 64 bits, mas que ainda defina ints longos como algo mais baixo. Então, basicamente, eu diria que--format=uL
permite mais flexibilidade. Quais são seus pensamentos?long long
que pode ser de 64 bits, enquanto int = long = 32 bits em algumas plataformas. Você não deve reivindicar o intervalo 0..2 ** 60 se não puder garantir em todas as plataformas. Por outro lado, o bash pode não suportar esse intervalo em tais plataformas (não sei, talvez ele use maxint_t e u8 esteja mais correto se você deseja afirmar o intervalo fixo (od
não suporta especificar maxint se o seu intervalo for qualquer que seja o intervalo dependente da plataforma do bash?) Se o intervalo do bash depender do tamanho de um longo, então uL pode ser mais apropriado). Deseja a gama completa que o bash suporta em todos os sistemas operacionais ou uma faixa fixa?Pode ser zsh?
Você pode querer usar sementes também
rand48(seed)
. Vejaman zshmodules
eman 3 erand48
para uma descrição detalhada, se estiver interessado.fonte
python
está disponível em sistemas baseados no Debian, Redhat.fonte
Se você deseja um número de 0 a (2 ^ n) -1, onde n mod 8 = 0, você pode simplesmente obter n / 8 bytes
/dev/random
. Por exemplo, para obter a representação decimal de uma forma aleatória,int
você pode:Se você deseja obter apenas n bits, pode primeiro pegar bytes de teto (n / 8) e mudar para a quantidade desejada. Por exemplo, se você quiser 15 bits:
Se você tem certeza absoluta de que não se importa com a qualidade da aleatoriedade e deseja garantir um tempo de execução mínimo, pode usar em
/dev/urandom
vez de/dev/random
. Certifique-se de saber o que está fazendo antes de usar/dev/urandom
!fonte
n
bytes aleatórios/dev/urandom
e formate usandood
. Semelhante em espírito como esta resposta . Ambos são igualmente bons :) Embora ambos tenham a desvantagem de ter um intervalo fixo de 0 a 2 ^ (n * 8) -1 bits, em que n é o número de bytes. Eu preferiria um método para um intervalo arbitrário , até 2 ^ 32-1, mas também qualquer coisa menor. Isso cria a dificuldade de viés./dev/urandom
vez de/dev/random
- não vejo razão para usar/dev/random
, e pode ser muito caro / lento ou retardar outras partes do sistema. (Sinta-se fazer livre editar para trás e explicar se é realmente necessário.)/dev/urandom
resultados são muito piores do/dev/random
que o urandom não é utilizável na maioria dos casos. Uma vez/dev/urandom
inicializado (no início do sistema); seus resultados são tão bons quanto/dev/random
para quase todos os aplicativos no Linux. Em alguns sistemas, aleatório e urandom são os mesmos.--format=u
deve ser substituído por,--format=u4
porquesizeof(int)
pode ser menor do que4
na teoria./dev/random
e/dev/urandom
são insatisfatórios, e que "Linux deve adicionar um RNG seguro que bloqueia até que recolheu entropia semente adequada e, posteriormente, se comporta comourandom
".Supondo que você não se oponha ao uso de ferramentas externas, isso deve atender aos seus requisitos:
Ele está usando a
rand
função perl, que aceita um limite superior como parâmetro. Você pode configurá-lo para o que quiser. O quão perto isso está da verdadeira aleatoriedade na definição matemática abstrata está além do escopo deste site, mas deve ser bom, a menos que você precise para criptografia extremamente sensível ou algo semelhante. Talvez até lá, mas não vou arriscar uma opinião.fonte
1^32-1
mas você precisa ajustá-lo para números maiores.Você deve obter o mais próximo (2 ^ X) -1 igual ou maior que o máximo desejado e obter o número de bits. Em seguida, basta chamar / dev / random várias vezes e anexar todos os bits até que você tenha o suficiente, truncando todos os bits que são demais. Se o número resultante for maior que sua repetição máxima. Na pior das hipóteses, você tem mais de 50% de chance de obter um número aleatório abaixo do seu Máximo; portanto, para a pior das hipóteses, você atenderá duas chamadas em média.
fonte
/dev/urandom
, mas em ambas as respostas é sempre um múltiplo de 8 bits. Truncar os bits que são demais para faixas mais baixas antes de formatar para decimalod
é uma boa idéia para melhorar a eficiência, pois o loop tem apenas um número esperado de 2 iterações, como você explica. Isso, combinado com qualquer uma das respostas mencionadas, é provavelmente o caminho a percorrer.Sua resposta é interessante, mas bastante longa.
Se você quiser números arbitrariamente grandes, poderá juntar vários números aleatórios em um auxiliar:
Se o problema for tendencioso, remova-o.
Unindo essas funções
fonte