Muitos programas precisam de valores (aparentemente) aleatórios para simular eventos do mundo real.
Exemplos comuns e familiares incluem:
('H', 'T').Gerar valores verdadeiramente aleatórios com um computador é um desafio técnico surpreendentemente difícil, então você pode ver esses resultados serem chamados de “pseudoaleatórios”.
Importante: este Conceito não aborda números aleatórios criptograficamente seguros, que são um desafio muito mais difícil.
No entanto, bibliotecas bem projetadas, como o módulo Random da biblioteca padrão do Julia, são rápidas, flexíveis e dão resultados amplamente bons o suficiente para a maioria das aplicações em modelagem, simulação e jogos.
O Julia divide a funcionalidade de aleatoriedade em vários lugares:
Base, que estão sempre disponíveis.Random.Random faz parte da biblioteca padrão e provavelmente já vem instalado, mas você vai precisar adicionar using Random no topo do seu programa para trazer o conteúdo dele para o seu namespace.
rand()
O que essa função faz depende dos argumentos que você passa para ela. Existem muitas opções.
Sem argumentos, ela gera um número de ponto flutuante entre 0 (inclusive) e 1.
Essa é uma distribuição uniform, em que todos os valores têm a mesma probabilidade, como veremos na seção Trabalhando com distribuições, mais abaixo.
Um único argumento inteiro gera um vetor desse comprimento.
julia> rand()
0.10261774967264703
julia> rand(5)
5-element Vector{Float64}:
0.24134501977563894
0.5664193284851202
0.9804412082089355
0.6229551330613335
0.47589221741904664
Para um intervalo diferente, basta deslocar e escalar o resultado de forma adequada.
O exemplo abaixo usa broadcasting na subtração, assunto abordado no Conceito de Operações com Vetores.
O .- simplesmente aplica essa aritmética a cada elemento do vetor.
# numbers between -1.0 and +1.0
julia> (rand(5) .- 0.5) * 2
5-element Vector{Float64}:
-0.5303906759076336
0.9635682226775855
-0.048823697086981754
0.465842804648374
0.9880834344780736
Quando o único argumento é um tipo, rand usa typemin e typemax como limites.
Provavelmente não é isso que você quer!
Para números inteiros aleatórios, podemos informar um intervalo e, opcionalmente, quantos valores gerar.
julia> rand(Int64)
-9159538335234594326 # not very useful
julia> rand(1:10, 5)
5-element Vector{Int64}:
1
1
1
4
7
No exemplo rand(1:10, 5) acima, repare que há valores repetidos (por coincidência), porque cada escolha é independente.
Isso é a “amostragem com reposição”, que veremos em mais detalhes abaixo.
Para valores de ponto flutuante em um intervalo, normalmente você precisa especificar o tamanho do passo. Caso contrário, o passo será 1.0 por padrão, o que raramente é útil.
julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
3.19
2.53
3.14
3.13
Como alternativa, informe um array ou uma tupla, e rand retornará uma entrada aleatória:
julia> rand([4, 9, 16, 25])
16
# coin flip
julia> rand(['H', 'T'])
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
# mixed types in tuple
julia> rand( (1, 3.2, "name"), 2 )
2-element Vector{Any}:
1
"name"
Imagine que temos um saco com 3 bolas vermelhas e 4 bolas verdes e tiramos uma bola do saco ao acaso. Para tirar uma segunda bola, há duas possibilidades:
O cenário 1 é com reposição, o cenário 2 é sem reposição, e eles dão resultados diferentes.
Para simular amostragem sem reposição no Julia, há algumas opções.
A mais simples (e, dentro do Exercism, a única opção): use Random.shuffle() para colocar as entradas em ordem aleatória e depois use os primeiros n elementos.
Isso funciona bem para problemas pequenos, mas pode não escalar bem para coleções grandes: shuffle precisa gerar o array inteiro, mesmo que você queira apenas uma pequena fração dele.
Para fazer a amostragem com reposição “do jeito certo”, instale o pacote StatsBase.jl.
Ele fornece a função sample() com uma gama completa de opções.
É razoável esperar que funcionalidades semelhantes sejam adicionadas ao Random em uma versão futura, para que façam parte da biblioteca padrão (os exemplos de código deste documento foram testados com o Julia 1.11).
Até agora, concentramos nossa atenção nos casos em que todos os resultados têm a mesma probabilidade.
Por exemplo, rand(1:100) tem a mesma probabilidade de dar qualquer número inteiro de 1 a 100.
Muitas situações do mundo real são bem menos simples do que isso.
Por isso, os estatísticos criaram uma grande variedade de distributions para descrever matematicamente resultados “do mundo real”.
A função rand() descrita acima é usada quando todas as probabilidades são iguais.
Isso é chamado de distribuição [uniform][uniform-distribution].
Também chamada de distribuição “normal” ou curva “em forma de sino”, é uma forma muito comum de descrever a imprecisão em valores medidos.
Por exemplo, suponha que a fábrica onde você trabalha acabou de comprar 10.000 parafusos que deveriam ser idênticos.
Você quer configurar o robô da fábrica para lidar com eles, então pesa uma amostra de 100 e descobre que eles têm um peso médio (ou mean) de 4,731 g.
É extremamente improvável que isso signifique que todos pesem exatamente 4,731 g.
Talvez você descubra que os valores variam de 4,627 a 4,794 g, mas se concentram em torno de 4,731 g.
Essa é a Gaussian distribution, em que as probabilidades atingem o pico na média e caem simetricamente dos dois lados (daí a forma de “sino”).
Para simular isso em software, precisamos de alguma forma de especificar a largura da curva (normalmente, parafusos caros se concentram mais em torno da média do que parafusos baratos!).
Por convenção, isso é feito usando standard deviation: valores pequenos para uma curva estreita e pontiaguda, grandes para uma curva baixa e larga.
Os matemáticos adoram letras gregas, então usamos μ (‘mu’) para representar a média e σ (‘sigma’) para representar o desvio padrão.
Assim, se você ler que “95% dos valores estão dentro de 2σ de μ” ou que “o bóson de Higgs foi detectado com confiança de 5 sigmas”, comentários assim se referem ao desvio padrão.
Haverá mais sobre isso no Conceito Statistics.
randn()
Abreviação de “random normal”, é semelhante à variante de ponto flutuante de rand(), exceto que os valores são distribuídos como uma gaussiana com média 0 e desvio padrão 1.
Novamente, talvez você queira escalar a saída bruta de randn para ajustar o desvio padrão, e deslocá-la para ajustar a média.
O exemplo abaixo converte para média 30 e desvio padrão 5.
julia> raw = randn(5)
5-element Vector{Float64}:
3.0762588867281475
1.5101100620253902
-0.5914858221637778
0.684175554069735
-0.8416433926114673
julia> raw * 5 .+ 30
5-element Vector{Float64}:
45.38129443364074
37.55055031012695
27.04257088918111
33.420877770348675
25.791783036942665
É difícil perceber, olhando a saída, que os valores brutos se concentram mais perto de zero do que no caso de uma distribuição uniforme. Se você duvidar, gere 1000 ou mais e faça um gráfico para deixar isso mais evidente.
Random
Este módulo contém o próximo nível de funcionalidades, deixadas de fora de Base para ajudar a minimizar o tamanho da configuração padrão do Julia.
Random complementa rand e randn de Base com versões que modificam os valores no lugar, rand! e randn!.
Uma adição útil é randstring, que gera uma string de um comprimento qualquer.
Por padrão, ela usa letras maiúsculas e minúsculas mais os dígitos de 0 a 9, mas outras opções podem ser especificadas.
julia> using Random
julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"
Além disso, há a função bitrand, que gera um BitArray aleatório de comprimento especificado.
julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
1
1
0
0
1
Para embaralhar aleatoriamente as entradas de um Vector, temos shuffle; e também shuffle! para modificar o vetor de entrada no lugar.
julia> v = ['A', '1', '2', 'J', 'Q', 'K'];
julia> shuffle(v)
6-element Vector{Char}:
'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
'1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
'2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)
# shuffles are random:
julia> shuffle(v)
6-element Vector{Char}:
'2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)
'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
'1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)
Às vezes é útil ter os índices embaralhados em vez disso.
Para isso, use randperm(n), em que n é o comprimento da sequência.
julia> randperm(6)
6-element Vector{Int64}:
6
2
4
1
3
5
Na prática, o exemplo acima dá os mesmos resultados que shuffle(1:6).
Funções relacionadas incluem randsubseq, para extrair entradas com probabilidade fixa, e randcycle, para permutações cíclicas.
Elas exigem algum conhecimento especializado, então consulte a documentação se tiver interesse nelas.
Vários algoritmos de gerador de números aleatórios (RNG) já vêm embutidos no Random como padrão, e qualquer pessoa com as habilidades matemáticas adequadas pode adicionar mais.
Essas coisas estão bem além do escopo deste documento!
Um motivo mais comum para trabalhar com RNGs é especificar uma seed, o que tem o efeito de tornar a sequência de saídas “aleatórias” reprodutível de uma execução para a outra.
Essa reprodutibilidade não é apropriada em código de produção, mas pode ajudar em testes e depuração.
Fora do Exercism, há muitos pacotes instaláveis relacionados a aleatoriedade, probabilidade e estatística.
Para saber um pouco mais, veja o Conceito Statistics.
StatsBase.jl
A maioria das funções de StatsBase é bastante técnica e não é relevante para este documento.
A exceção é StatsBase.sample, que oferece uma implementação completa de amostragem com ou sem reposição (veja uma seção anterior, acima).
Há também funções para amostragem ponderada (não uniforme).
Distributions.jl
As distribuições uniform e normal (ou gaussiana) foram descritas acima.
O módulo Random também contém randexp, para amostrar da Distribuição Exponencial, que está relacionada à (muito comum) Distribuição de Poisson.
Para uma variedade muito maior de opções, existe o pacote Distributions.jl, para quem tem uma base adequada em estatística.