Muchos programas necesitan valores (aparentemente) aleatorios para simular sucesos del mundo real.
Entre los ejemplos habituales y conocidos se incluyen:
('H', 'T').Generar valores verdaderamente aleatorios con un ordenador es un reto técnico sorprendentemente difícil, así que puede que veas estos resultados denominados «pseudoaleatorios».
Importante: este concepto no abarca los números aleatorios criptográficamente seguros, que son un reto mucho más difícil.
Sin embargo, las bibliotecas bien diseñadas como el módulo Random de la biblioteca estándar de Julia son rápidas, flexibles y ofrecen resultados lo bastante buenos para la mayoría de las aplicaciones de modelado, simulación y juegos.
Julia divide la funcionalidad aleatoria en varios lugares:
Base, que siempre están disponibles.Random.Random forma parte de la biblioteca estándar y es probable que esté preinstalado, pero tendrás que añadir using Random al principio de tu programa para traer su contenido al espacio de nombres.
rand()
Lo que hace esta función depende de los argumentos que le pases. Hay muchas opciones.
Sin argumentos, genera un número decimal entre 0 (incluido) y 1.
Esta es una distribución uniform en la que todos los valores son igual de probables, como se explica en la sección Trabajar con distribuciones, más abajo.
Un único argumento entero genera un vector de esa longitud.
julia> rand()
0.10261774967264703
julia> rand(5)
5-element Vector{Float64}:
0.24134501977563894
0.5664193284851202
0.9804412082089355
0.6229551330613335
0.47589221741904664
Para obtener un rango diferente, basta con desplazar y escalar el resultado de forma adecuada.
En el ejemplo siguiente se usa broadcasting para la resta, algo que se trata en el concepto Operaciones con vectores.
El .- simplemente aplica esta aritmética a cada elemento del vector.
# 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
Con un tipo como único argumento, rand usará typemin y typemax como límites.
¡Probablemente no sea lo que quieres!
Para números enteros aleatorios, podemos proporcionar un rango y, opcionalmente, cuántos valores generar.
julia> rand(Int64)
-9159538335234594326 # not very useful
julia> rand(1:10, 5)
5-element Vector{Int64}:
1
1
1
4
7
En el ejemplo rand(1:10, 5) de arriba, fíjate en que hay valores repetidos (por casualidad), porque cada elección es independiente.
Esto es un «muestreo con reemplazo», que se explica con más detalle a continuación.
Para valores de coma flotante en un rango, normalmente tendrás que especificar un tamaño de paso. De lo contrario, el paso será 1.0 de forma predeterminada, lo cual rara vez es útil.
julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
3.19
2.53
3.14
3.13
Como alternativa, proporciona un array o una tupla, y rand devolverá una entrada aleatoria:
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"
Imagina que tenemos una bolsa con 3 bolas rojas y 4 bolas verdes, y sacamos una bola al azar. Para sacar una segunda bola, hay dos posibilidades:
El escenario 1 es con reemplazo, el escenario 2 es sin reemplazo, y dan resultados diferentes.
Para simular un muestreo sin reemplazo en Julia, hay un par de opciones.
La más sencilla (y, dentro de Exercism, la única opción): usa Random.shuffle() para poner las entradas en orden aleatorio y luego usa los primeros n elementos.
Esto va bien para problemas pequeños, pero puede no escalar bien a colecciones grandes: shuffle necesita generar el array completo, aunque solo quieras una pequeña fracción.
Para hacer un muestreo con reemplazo «como es debido», instala el paquete StatsBase.jl.
Ese proporciona la función sample() con una gama completa de opciones.
Es razonable esperar que se añada una funcionalidad similar a Random en una versión futura, para que forme parte de la biblioteca estándar (los ejemplos de código de este documento se probaron con Julia 1.11).
Hasta ahora nos hemos centrado en casos en los que todos los resultados son igual de probables.
Por ejemplo, rand(1:100) tiene la misma probabilidad de dar cualquier número entero del 1 al 100.
Muchas situaciones del mundo real son mucho menos simples que esta.
Por eso, los estadísticos han creado una amplia variedad de distributions para describir matemáticamente los resultados del «mundo real».
La función rand() descrita arriba se usa cuando todas las probabilidades son iguales.
Esto se llama distribución [uniform][uniform-distribution].
También llamada distribución «normal» o «curva de campana», es una forma muy común de describir la imprecisión de los valores medidos.
Por ejemplo, supón que la fábrica en la que trabajas acaba de comprar 10.000 tornillos que deberían ser idénticos.
Quieres configurar el robot de la fábrica para que los manipule, así que pesas una muestra de 100 y descubres que tienen un peso medio (o mean) de 4,731 g.
Es muy poco probable que eso signifique que todos pesan exactamente 4,731 g.
Quizá descubras que los valores van de 4,627 a 4,794 g, pero se agrupan en torno a 4,731 g.
Esta es la Gaussian distribution, en la que las probabilidades alcanzan su máximo en la media y disminuyen de forma simétrica a ambos lados (de ahí lo de «campana»).
Para simular esto por software, necesitamos alguna forma de especificar la anchura de la curva (normalmente, los tornillos caros se agruparán más estrechamente en torno a la media que los baratos).
Por convención, esto se hace con la standard deviation: valores pequeños para una curva estrecha y puntiaguda, y grandes para una curva baja y ancha.
A los matemáticos les encantan las letras griegas, así que usamos μ («mu») para representar la media y σ («sigma») para representar la desviación típica.
Así, si lees que «el 95 % de los valores están dentro de 2σ de μ» o que «el bosón de Higgs se ha detectado con una confianza de 5 sigma», esos comentarios se refieren a la desviación típica.
Habrá más que decir sobre esto en el concepto Statistics.
randn()
Abreviatura de «random normal», es similar a la variante de coma flotante de rand(), salvo que los valores se distribuyen como una gaussiana con media 0 y desviación típica 1.
De nuevo, puede que quieras escalar la salida bruta de randn para la desviación típica y desplazarla para la media.
El ejemplo siguiente convierte a una media de 30 y una desviación típica de 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
Es difícil ver a simple vista en la salida que la salida bruta se agrupa más cerca de cero que en una distribución uniforme. Si lo dudas, genera 1000 o más y represéntalos gráficamente para que sea más evidente.
Random
Este módulo contiene el siguiente nivel de funcionalidad, omitido en Base para ayudar a minimizar el tamaño de la configuración predeterminada de Julia.
Random complementa rand y randn de Base con versiones mutadoras, rand! y randn!.
Una adición útil es randstring, que genera un string de una longitud dada.
De forma predeterminada, usa letras mayúsculas y minúsculas más los dígitos del 0 al 9, pero se pueden especificar otras opciones.
julia> using Random
julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"
Además, hay una función bitrand para generar un BitArray aleatorio de la longitud especificada.
julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
1
1
0
0
1
Para barajar aleatoriamente las entradas de un Vector tenemos shuffle; también shuffle! para mutar el vector de entrada in situ.
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)
A veces es útil tener los índices barajados en su lugar.
Para ello, usa randperm(n), donde n es la longitud de la secuencia.
julia> randperm(6)
6-element Vector{Int64}:
6
2
4
1
3
5
En efecto, el ejemplo anterior da los mismos resultados que shuffle(1:6).
Entre las funciones relacionadas están randsubseq, para extraer entradas con una probabilidad fija, y randcycle, para permutaciones cíclicas.
Estas requieren ciertos conocimientos especializados, así que consulta la documentación si te interesan.
En Random hay varios algoritmos de generación de números aleatorios (RNG) integrados de serie, y cualquiera con las habilidades matemáticas adecuadas puede añadir más.
¡Estas cosas quedan muy fuera del alcance de este documento!
Un motivo más habitual para trabajar con RNG es especificar una seed, lo que tiene el efecto de hacer que la secuencia de salidas «aleatorias» sea reproducible de una ejecución a la siguiente.
Esa reproducibilidad no es apropiada en el código de producción, pero puede ayudar con las pruebas y la depuración.
Fuera de Exercism, hay muchos paquetes instalables relacionados con la aleatoriedad, la probabilidad y la estadística.
Para obtener más información, consulta el concepto Statistics.
StatsBase.jl
La mayoría de las funciones de StatsBase son bastante técnicas y no son relevantes para este documento.
La excepción es StatsBase.sample, que proporciona una implementación completa del muestreo con o sin reemplazo (véase una sección anterior, más arriba).
También hay funciones para el muestreo ponderado (no uniforme).
Distributions.jl
Las distribuciones uniform y normal (o gaussiana) se han descrito antes.
El módulo Random también contiene randexp para muestrear de la distribución exponencial, que está relacionada con la (muy común) distribución de Poisson.
Para una gama de opciones mucho más amplia, existe el paquete Distributions.jl para quienes tengan una formación adecuada en estadística.