Muchos programas necesitan valores (aparentemente) aleatorios para simular eventos del mundo real.
Entre los ejemplos más comunes y conocidos se incluyen:
('H', 'T').Generar valores verdaderamente aleatorios con un ordenador es un reto técnico sorprendentemente difícil, así que es posible que veas estos resultados denominados «seudoaleatorios».
Importante: Este concepto no cubre los números aleatorios criptográficamente seguros, que son un reto mucho más difícil.
Sin embargo, bibliotecas bien diseñadas como el módulo Random de la biblioteca estándar de Julia son rápidas, flexibles y dan resultados lo bastante buenos para la mayoría de las aplicaciones de modelado, simulación y juegos.
Julia reparte la funcionalidad aleatoria entre varios lugares:
Base, que están siempre 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.
Se trata de una distribución uniform en la que todos los valores son igual de probables, como se explica en la sección Trabajo 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 como corresponda.
El ejemplo de abajo 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
Si el único argumento es un tipo, rand usará typemin y typemax como límites.
¡Esto probablemente no sea lo que quieres!
Para obtener números enteros aleatorios, podemos indicar 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 el «muestreo con reemplazo», que se explica con más detalle a continuación.
Para obtener valores de coma flotante dentro de un rango, normalmente tendrás que especificar un tamaño de paso. De lo contrario, el paso será 1.0 por defecto, algo que rara vez resulta útil.
julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
3.19
2.53
3.14
3.13
Como alternativa, indica 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 de la bolsa. 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 el muestreo sin reemplazo en Julia, hay un par de opciones.
La más sencilla (y, dentro de Exercism, la única opción) es usar Random.shuffle() para poner las entradas en orden aleatorio y luego usar 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 de él.
Para hacer el muestreo con reemplazo «como es debido», instala el paquete StatsBase.jl.
Este 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.
A esto se le llama distribución uniform.
También llamada distribución «normal» o curva «en forma 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 esto 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 «en forma 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 pronunciada y estrecha, 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 estándar.
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», este tipo de comentarios tienen que ver con la desviación estándar.
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 según una gaussiana con media 0 y desviación estándar 1.
De nuevo, puede que quieras escalar la salida bruta de randn para ajustar la desviación estándar, y desplazarla para ajustar la media.
El ejemplo de abajo la convierte a una media de 30 y una desviación estándar 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 apreciar a simple vista, mirando 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 en un gráfico para que se vea más claro.
Random
Este módulo contiene el siguiente nivel de funcionalidad, omitido de Base para ayudar a minimizar el tamaño de la configuración predeterminada de Julia.
Random complementa rand y randn de Base con versiones mutables, rand! y randn!.
Una incorporación útil es randstring, que genera un string de una longitud dada.
De forma predeterminada, usa letras mayúsculas y minúsculas además de los dígitos del 0 al 9, pero se pueden especificar otras opciones.
julia> using Random
julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"
Además, existe una función bitrand que genera 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 modificar 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 resulta útil tener los índices barajados en lugar de las entradas.
Para eso, 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 de arriba da los mismos resultados que shuffle(1:6).
Mary es una gran fan de la serie de televisión Star Trek: The Next Generation. A menudo juega a juegos de rol de lápiz y papel, en los que ella y sus amigos fingen ser la tripulación de la nave estelar Enterprise. El personaje de Mary es el capitán Picard, lo que significa que tiene que llevar el diario del capitán. Le encanta la parte creativa del juego, pero no le gusta generar datos aleatorios sobre la marcha.
Ayuda a Mary creando generadores aleatorios para los datos que aparecen habitualmente en el diario del capitán.
La nave estelar Enterprise se encuentra con muchos planetas en sus viajes. Los planetas del universo de Star Trek se dividen en categorías según sus propiedades. Por ejemplo, la Tierra es un planeta de clase M. Todas las clases planetarias posibles son: D, H, J, K, L, M, N, R, T y Y.
Implementa la función random_planet. Debe devolver una de las clases planetarias al azar.
julia> random_planet_class()
'K'
¡La Enterprise (número de registro NCC-1701) no es la única nave estelar que surca el espacio! Cuando se encuentra con otra nave estelar, Mary necesita anotar el número de registro de esa nave estelar.
Los números de registro empiezan por el prefijo «NCC-» y después usan un número del 1000 al 9999 (ambos inclusive).
Implementa la función random_ship_registry_number, que devuelve un número de registro de nave estelar aleatorio.
julia> random_ship_registry_number()
"NCC-1947"
¿De qué sirve un diario si no incluye fechas?
Una fecha estelar es un número de coma flotante. Las aventuras de la nave estelar Enterprise de la primera temporada de The Next Generation transcurren entre las fechas estelares 41000.0 y 42000.0. El «4» hace referencia al siglo XXIV y el «1», a la primera temporada.
Implementa la función random_stardate, que devuelve un número de coma flotante entre 41000.0 (incluido) y 42000.0.
julia> random_stardate()
41458.15721310934
Ha habido quejas sobre que las fechas estelares tienen demasiados decimales, así que Mary decide redondear los diarios a un decimal.
Aunque esto podría hacerse redondeando los valores de random_stardate(), te das cuenta de que hay un enfoque mucho más sencillo.
Implementa la función random_stardate_v2, que devuelve un número de coma flotante entre 41000.0 y 42000.0 (ambos inclusive), con un solo decimal.
julia> random_stardate_v2()
41732.6
Una versión del juego implica interactuar con otras naves estelares, y Mary necesita una selección.
Hay un array completo de posibles naves estelares, pero en cada partida solo se necesitan unas pocas, y deben elegirse al azar.
Implementa la función pick_starships(starships, number_needed), que recibe un vector de números de registro de naves estelares únicos y devuelve un vector de longitud number_needed.
Los valores del resultado deben tomarse de starships, sin duplicados.
julia> pick_starships(["NCC-5011", "NCC-1228", "NCC-7039", "NCC-3978", "NCC-1476"], 2)
2-element Vector{String}:
"NCC-1228"
"NCC-5011"
Hemos restringido el argumento starships a un vector.
¿Cómo implementarías pick_starships() para que funcione con otros iterables, como tuplas o conjuntos?
No hay tests para esta parte.
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.