metodos - ¿Cómo puedo generar una lista de n números aleatorios únicos en Ruby?
numeros aleatorios en ruby (14)
Método 1
Usando el enfoque de Kent, es posible generar una matriz de longitud arbitraria manteniendo todos los valores en un rango limitado:
# Generates a random array of length n.
#
# @param n length of the desired array
# @param lower minimum number in the array
# @param upper maximum number in the array
def ary_rand(n, lower, upper)
values_set = (lower..upper).to_a
repetition = n/(upper-lower+1) + 1
(values_set*repetition).sample n
end
Método 2
Otro método, posiblemente más eficiente , modificado a partir de la misma respuesta de Kent:
def ary_rand2(n, lower, upper)
v = (lower..upper).to_a
(0...n).map{ v[rand(v.length)] }
end
Salida
puts (ary_rand 5, 0, 9).to_s # [0, 8, 2, 5, 6] expected
puts (ary_rand 5, 0, 9).to_s # [7, 8, 2, 4, 3] different result for same params
puts (ary_rand 5, 0, 1).to_s # [0, 0, 1, 0, 1] repeated values from limited range
puts (ary_rand 5, 9, 0).to_s # [] no such range :)
Esto es lo que tengo hasta ahora:
myArray.map!{ rand(max) }
Obviamente, sin embargo, a veces los números en la lista no son únicos. ¿Cómo puedo asegurarme de que mi lista solo contenga números únicos sin tener que crear una lista más grande de la cual simplemente elijo los n números únicos?
Editar:
Realmente me gustaría ver esto hecho sin ciclo, si es posible.
¿Qué tal una obra de teatro sobre esto? Números aleatorios únicos sin necesidad de usar Set o Hash.
x = 0
(1..100).map{|iter| x += rand(100)}.shuffle
Aquí hay una solución:
Supongamos que quiere que estos números aleatorios estén entre r_min
y r_max
. Para cada elemento en su lista, genere un número aleatorio r
, y haga una list[i]=list[i-1]+r
. Esto le daría números aleatorios que aumentan monótonamente, garantizando la singularidad siempre que
-
r+list[i-1]
no pasa demasiado -
r
> 0
Para el primer elemento, usaría r_min
lugar de list[i-1]
. Una vez que haya terminado, puede mezclar la lista para que los elementos no estén tan obviamente en orden.
El único problema con este método es cuando r_max
y todavía tienes más elementos para generar. En este caso, puede reiniciar r_min
y r_max
en 2 elementos adyacentes que ya ha computado, y simplemente repita el proceso. Esto ejecuta efectivamente el mismo algoritmo en un intervalo en el que ya no se usan números. Puedes seguir haciendo esto hasta que llenes la lista.
Basado en la solución anterior de Kent Fredric, esto es lo que terminé usando:
def n_unique_rand(number_to_generate, rand_upper_limit)
return (0..rand_upper_limit - 1).sort_by{rand}[0..number_to_generate - 1]
end
Gracias Kent.
En lugar de agregar los elementos a una lista / matriz, agréguelos a un conjunto.
Esto usa Set:
require ''set''
def rand_n(n, max)
randoms = Set.new
loop do
randoms << rand(max)
return randoms.to_a if randoms.size >= n
end
end
No hay bucles con este método
Array.new(size) { rand(max) }
require ''benchmark''
max = 1000000
size = 5
Benchmark.realtime do
Array.new(size) { rand(max) }
end
=> 1.9114e-05
Podría usar un hash para rastrear los números aleatorios que ha usado hasta ahora:
seen = {}
max = 100
(1..10).map { |n|
x = rand(max)
while (seen[x])
x = rand(max)
end
x
}
Por lo que es bueno saber de antemano el valor máximo, puede hacerlo de esta manera:
class NoLoopRand
def initialize(max)
@deck = (0..max).to_a
end
def getrnd
return @deck.delete_at(rand(@deck.length - 1))
end
end
y puede obtener datos aleatorios de esta manera:
aRndNum = NoLoopRand.new(10)
puts aRndNum.getrnd
Obtendrás nil
cuando todos los valores se agoten desde el mazo.
Ruby 1.9 ofrece el método de muestra Array # que devuelve un elemento o elementos seleccionados al azar de una matriz. Los resultados de #sample no incluirán el mismo elemento Array dos veces.
(1..999).to_a.sample 5 # => [389, 30, 326, 946, 746]
Cuando se compara con el enfoque to_a.sort_by
, el método de sample
parece ser significativamente más rápido. En un escenario simple, sort_by
con la sample
y obtuve los siguientes resultados.
require ''benchmark''
range = 0...1000000
how_many = 5
Benchmark.realtime do
range.to_a.sample(how_many)
end
=> 0.081083
Benchmark.realtime do
(range).sort_by{rand}[0...how_many]
end
=> 2.907445
Sí, es posible hacer esto sin un bucle y sin hacer un seguimiento de los números que se han elegido. Se llama registro de desplazamiento de retroalimentación lineal: crea secuencia de números aleatorios sin repeticiones
Si tiene una lista finita de posibles números aleatorios (es decir, de 1 a 100), entonces la solución de Kent es buena.
De lo contrario, no hay otra buena manera de hacerlo sin bucle. El problema es que DEBES hacer un ciclo si obtienes un duplicado. Mi solución debe ser eficiente y el bucle no debe ser demasiado más que el tamaño de su matriz (es decir, si desea 20 números aleatorios únicos, podría tomar 25 iteraciones en promedio). Aunque el número de iteraciones empeora, más números necesidad y el máximo es menor. Aquí está mi código anterior modificado para mostrar cuántas iteraciones se necesitan para la entrada dada:
require ''set''
def rand_n(n, max)
randoms = Set.new
i = 0
loop do
randoms << rand(max)
break if randoms.size > n
i += 1
end
puts "Took #{i} iterations for #{n} random numbers to a max of #{max}"
return randoms.to_a
end
Podría escribir este código para MIRAR más como Array.map si quieres :)
Solo para darle una idea sobre la velocidad, ejecuté cuatro versiones de esto:
- Usando Sets, como la sugerencia de Ryan.
- Usando una matriz un poco más grande de lo necesario, ¡entonces haciendo uniq! al final.
- Usando un Hash, como sugirió Kyle.
- Creando una Matriz del tamaño requerido, luego ordenándola al azar, como la sugerencia de Kent (pero sin el extraño "- 0.5", que no hace nada).
Todos son rápidos a pequeña escala, así que los hice crear una lista de 1,000,000 de números. Estos son los tiempos, en segundos:
- Conjuntos: 628
- Array + uniq: 629
- Hash: 645
- Arreglo fijo + ordenación: 8
Y no, ese último no es un error tipográfico. Entonces, si te preocupa la velocidad, y está bien que los números sean enteros de 0 a lo que sea, entonces mi código exacto fue:
a = (0...1000000).sort_by{rand}
(0..50).to_a.sort{ rand() - 0.5 }[0..x]
(0..50).to_a
se puede reemplazar con cualquier matriz. 0 es "valor mínimo", 50 es "valor máximo" x es "cuántos valores deseo"
por supuesto, es imposible que x sea mayor que max-min :)
En expansión de cómo funciona esto
(0..5).to_a ==> [0,1,2,3,4,5]
[0,1,2,3,4,5].sort{ -1 } ==> [0, 1, 2, 4, 3, 5] # constant
[0,1,2,3,4,5].sort{ 1 } ==> [5, 3, 0, 4, 2, 1] # constant
[0,1,2,3,4,5].sort{ rand() - 0.5 } ==> [1, 5, 0, 3, 4, 2 ] # random
[1, 5, 0, 3, 4, 2 ][ 0..2 ] ==> [1, 5, 0 ]
Notas al pie:
Vale la pena mencionar que en el momento en que se respondió originalmente esta pregunta, en septiembre de 2008, ese Array#shuffle
o no estaba disponible o no estaba ya disponible para mí, de ahí la aproximación en Array#sort
Y como resultado, hay un aluvión de ediciones sugeridas.
Asi que:
.sort{ rand() - 0.5 }
Puede ser mejor y más breve en las implementaciones de ruby modernas que usan
.shuffle
Adicionalmente,
[0..x]
Puede escribirse más obviamente con Array#take
as:
.take(x)
Por lo tanto, la manera más fácil de producir una secuencia de números aleatorios en un rubí moderno es:
(0..50).to_a.shuffle.take(x)