Genera N números aleatorios y únicos dentro de un rango

¿Cuál es una forma eficiente de generar N números únicos dentro de un rango dado usando C #? Por ejemplo, genere 6 números únicos entre 1 y 50. Una forma perezosa sería simplemente usar Random.Next() en un bucle y almacenar ese número en una matriz / lista, luego repetir y verificar si ya existe o no, etc. ¿Hay una mejor manera de generar un grupo de números aleatorios, pero únicos? Para agregar más contexto, me gustaría seleccionar N elementos aleatorios de una colección, usando su índice.

Gracias

Tome una matriz de 50 elementos: {1, 2, 3, .... 50} Mezcle la matriz usando cualquiera de los algoritmos estándar de matrices aleatoriamente mezcladas. Los primeros seis elementos de la matriz modificada es lo que estás buscando. HTH

Para 6 de 50, no estoy muy seguro de preocuparme por la eficiencia, ya que la probabilidad de un duplicado es relativamente baja (30% en general, según mis cálculos de respaldo del sobre). Podrías simplemente recordar los números anteriores que habías generado y tirarlos, algo así como (pseudo-código):

 n[0] = rnd(50) for each i in 1..5: n[i] = n[0] while n[1] == n[0]: n[1] = rnd(50) while n[2] == any of (n[0], n[1]): n[2] = rnd(50) while n[3] == any of (n[0], n[1], n[2]): n[3] = rnd(50) while n[4] == any of (n[0], n[1], n[2], n[3]): n[4] = rnd(50) while n[5] == any of (n[0], n[1], n[2], n[3], n[4]): n[5] = rnd(50) 

Sin embargo, esto se descompondrá a medida que pasas de 6-de-50 a 48-de-50, o 6-de-6, ya que los duplicados comienzan a ser mucho más probables. Esto se debe a que el grupo de números disponibles se reduce y terminas tirando más y más.

Para una solución muy eficiente que le da un subconjunto de sus valores con cero posibilidad de duplicados (y ninguna clasificación inicial innecesaria), Fisher-Yates es el camino a seguir.

 dim n[50] // gives n[0] through n[9] for each i in 0..49: n[i] = i // initialise them to their indexes nsize = 50 // starting pool size do 6 times: i = rnd(nsize) // give a number between 0 and nsize-1 print n[i] nsize = nsize - 1 // these two lines effectively remove the used number n[i] = n[nsize] 

Simplemente seleccionando un número aleatorio del grupo, reemplazándolo con el número superior de ese grupo, y luego reduciendo el tamaño del grupo, se obtiene una mezcla sin tener que preocuparse por un gran número de intercambios iniciales.

Esto es importante si el número es alto ya que no introduce un retraso de arranque innecesario.

Por ejemplo, examine la siguiente verificación de banco, eligiendo 10 de 10:

 <------ n[] ------> 0 1 2 3 4 5 6 7 8 9 nsize rnd(nsize) output ------------------- ----- ---------- ------ 0 1 2 3 4 5 6 7 8 9 10 4 4 0 1 2 3 9 5 6 7 8 9 7 7 0 1 2 3 9 5 6 8 8 2 2 0 1 8 3 9 5 6 7 6 6 0 1 8 3 9 5 6 0 0 5 1 8 3 9 5 2 8 5 1 9 3 4 1 1 5 3 9 3 0 5 9 3 2 1 3 9 1 0 9 

Puede ver que la agrupación se reduce a medida que avanza y, como siempre está reemplazando la utilizada por una no utilizada, nunca tendrá una repetición.

El uso de los resultados devueltos de eso como índices en su colección garantizará que no se seleccionarán elementos duplicados.

Para conjuntos grandes de números únicos, colóquelos en una lista.

  Random random = new Random(); List uniqueInts = new List(10000); List ranInts = new List(500); for (int i = 1; i < 10000; i++) { uniqueInts.Add(i); } for (int i = 1; i < 500; i++) { int index = random.Next(uniqueInts.Count) + 1; ranInts.Add(uniqueInts[index]); uniqueInts.RemoveAt(index); } 

Luego genere aleatoriamente un número de 1 a myInts.Count. Almacene el valor myInt y elimínelo de la Lista. No es necesario barajar la lista ni mirar para ver si el valor ya existe.

 var random = new Random(); var intArray = Enumerable.Range(0, 4).OrderBy(t => random.Next()).ToArray(); 

Esta matriz contendrá 5 números aleatorios de 0 a 4.

o

  var intArray = Enumerable.Range(0, 10).OrderBy(t => random.Next()).Take(5).ToArray(); 

Esta matriz contendrá 5 números aleatorios entre 0 y 10.

 int firstNumber = intArray[0]; int secondNumber = intArray[1]; int thirdNumber = intArray[2]; int fourthNumber = intArray[3]; int fifthNumber = intArray[4]; 

en lugar de utilizar List use Dictionary !!

En caso de que ayude a alguien más, prefiero asignar el número mínimo de elementos necesarios. A continuación, utilizo un HashSet, que garantiza que los nuevos elementos sean únicos. Esto debería funcionar también con colecciones muy grandes, hasta los límites de lo que HashSet juega bien.

  public static IEnumerable GetRandomNumbers(int numValues, int maxVal) { var rand = new Random(); var yieldedValues = new HashSet(); int counter = 0; while (counter < numValues) { var r = rand.Next(maxVal); if (yieldedValues.Add(r)) { counter++; yield return r; } } } 

generar nulos aleatorios únicos del 1 al 40:

salida confirmada:

 class Program { static int[] a = new int[40]; static Random r = new Random(); static bool b; static void Main(string[] args) { int t; for (int i = 0; i < 20; i++) { lab: t = r.Next(1, 40); for(int j=0;j<20;j++) { if (a[j] == t) { goto lab; } } a[i] = t; Console.WriteLine(a[i]); } Console.Read(); } } 

muestra de salida:

7 38 14 18 13 29 28 26 22 8 24 19 35 39 33 32 20 2 15 37