Muchos programas necesitan valores (aparentemente) aleatorios para simular eventos del mundo real.
Algunos ejemplos comunes y familiares incluyen:
('H', 'T').Generar valores verdaderamente aleatorios con una computadora es un desafío técnico sorprendentemente difícil, por lo que es posible que veas estos resultados referidos como «pseudoaleatorio».
Importante: Este concepto no cubre los números aleatorios criptográficamente seguros, que son un desafío 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 que son más que suficientes 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 es parte de la biblioteca estándar y probablemente esté preinstalado, pero necesitarás agregar using Random al inicio de tu programa para incorporar sus contenidos al espacio de nombres.
rand()
Lo que hace esta función depende de los argumentos que le des. Hay muchas opciones.
Sin argumentos, genera un número de punto flotante entre 0 (inclusive) y 1.
Esta es una distribución uniform con todos los valores igualmente 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 un rango diferente, solo desplaza y escala el resultado de forma adecuada.
El ejemplo de abajo usa broadcasting para la resta, que se cubre en el concepto de 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 esto no sea lo que quieres.
Para 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, observa que hay valores repetidos (de forma coincidente), porque cada elección es independiente.
Esto es «muestreo con reemplazo», que se explica con más detalle abajo.
Para valores de punto flotante en un rango, normalmente necesitarás especificar un tamaño de paso. De lo contrario, el paso será 1.0 de forma predeterminada, lo que 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 de la bolsa. Para obtener 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) es usar Random.shuffle() para poner las entradas en orden aleatorio y luego usar los primeros n elementos.
Esto está bien para problemas pequeños, pero puede no escalar bien a colecciones grandes: shuffle necesita generar el array completo, incluso si solo quieres una pequeña fracción de él.
Para hacer un muestreo con reemplazo «correctamente», instala el paquete StatsBase.jl.
Eso proporciona la función sample() con una gama completa de opciones.
Podemos esperar razonablemente que se agregue 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 donde todos los resultados son igualmente probables.
Por ejemplo, rand(1:100) tiene la misma probabilidad de dar cualquier entero del 1 al 100.
Muchas situaciones del mundo real son mucho menos simples que esto.
Como resultado, los estadísticos han creado una amplia variedad de distributions para describir matemáticamente resultados «del mundo real».
La función rand() descrita arriba se usa cuando todas las probabilidades son iguales.
Esto se llama distribución uniform.
También llamada distribución «normal» o curva «de campana», esta es una forma muy común de describir la imprecisión en valores medidos.
Por ejemplo, supón que la fábrica donde trabajas acaba de comprar 10,000 pernos que deberían ser idénticos.
Quieres configurar el robot de la fábrica para que los maneje, así que pesas una muestra de 100 y descubres que tienen un peso promedio (o mean) de 4.731 g.
Es extremadamente improbable que esto signifique que todos pesan exactamente 4.731 g.
Quizás descubras que los valores van de 4.627 a 4.794 g, pero se agrupan alrededor de 4.731 g.
Esta es la Gaussian distribution, para la cual las probabilidades alcanzan su máximo en la media y decaen simétricamente a ambos lados (de ahí «en forma de campana»).
Para simular esto en software, necesitamos alguna forma de especificar el ancho de la curva (típicamente, los pernos caros se agruparán más estrechamente alrededor de la media que los pernos baratos).
Por convención, esto se hace con la standard deviation: valores pequeños para una curva pronunciada y estrecha, 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 «el bosón de Higgs se ha detectado con una confianza de 5 sigma», esos comentarios se refieren a 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 punto flotante de rand(), excepto que los valores se distribuyen como una gaussiana con media 0 y desviación estándar 1.
De nuevo, es posible que quieras escalar la salida sin procesar de randn para la desviación estándar y desplazarla para la media.
El ejemplo de abajo convierte a media 30 y desviación estándar 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 notar, al mirar la salida, que la salida sin procesar se agrupa más cerca de cero que en una distribución uniforme. Si lo dudas, genera 1000 o más y gráfica los valores para que sea más evidente.
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 mutadoras, rand! y randn!.
Una adición útil es randstring, que genera un string de longitud dada.
De forma predeterminada, usa letras mayúsculas y minúsculas, además de 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 longitud especificada.
julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
1
1
0
0
1
Para mezclar 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 mezclados en su lugar.
Para esto, 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 juegos de rol de lápiz y papel, en los que ella y sus amigos fingen ser la tripulación de la Starship Enterprise. El personaje de Mary es el capitán Picard, lo que significa que tiene que llevar la bitácora del capitán. Le encanta la parte creativa del juego, pero no le gusta generar datos aleatorios en el momento.
Ayuda a Mary creando generadores aleatorios para los datos que aparecen comúnmente en la bitácora del capitán.
La Starship 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 que anda por ahí! Cuando se encuentra con otra nave, Mary necesita registrar el número de registro de esa nave.
Los números de registro empiezan con el prefijo «NCC-» y luego usan un número del 1000 al 9999 (inclusive).
Implementa la función random_ship_registry_number que devuelve un número de registro de nave aleatorio.
julia> random_ship_registry_number()
"NCC-1947"
¿De qué sirve una bitácora si no incluye fechas?
Una fecha estelar es un número de punto flotante. Las aventuras de la Starship Enterprise de la primera temporada de The Next Generation tienen lugar entre las fechas estelares 41000.0 y 42000.0. El «4» representa el siglo XXIV y el «1», la primera temporada.
Implementa la función random_stardate que devuelve un número de punto flotante entre 41000.0 (inclusive) y 42000.0.
julia> random_stardate()
41458.15721310934
Ha habido quejas de que las fechas estelares tienen demasiados decimales, así que Mary decide redondear las bitácoras 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 simple.
Implementa la función random_stardate_v2 que devuelve un número de punto flotante entre 41000.0 y 42000.0 (inclusive), con un solo decimal.
julia> random_stardate_v2()
41732.6
Una versión del juego implica interactuar con otras naves, y Mary necesita una selección.
Hay una lista completa de naves posibles, pero en cada partida solo se necesitan unas pocas, y deben elegirse al azar.
Implementa la función pick_starships(starships, number_needed), que toma un vector de números de registro de naves ú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"
Restringimos el argumento starships para que sea 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.