Aleatorizar una lista<T>

Resuelto mirezus asked hace 16 años • 32 respuestas

¿Cuál es la mejor manera de aleatorizar el orden de una lista genérica en C#? Tengo un conjunto finito de 75 números en una lista a la que me gustaría asignar un orden aleatorio para poder sortearlos para una aplicación de tipo lotería.

mirezus avatar Nov 08 '08 02:11 mirezus
Aceptado

Mezcla cualquiera (I)Listcon un método de extensión basado en la mezcla aleatoria de Fisher-Yates :

private static Random rng = new Random();  

public static void Shuffle<T>(this IList<T> list)  
{  
    int n = list.Count;  
    while (n > 1) {  
        n--;  
        int k = rng.Next(n + 1);  
        T value = list[k];  
        list[k] = list[n];  
        list[n] = value;  
    }  
}

Uso:

List<Product> products = GetProducts();
products.Shuffle();

El código anterior utiliza el muy criticado método System.Random para seleccionar candidatos de intercambio. Es rápido pero no tan aleatorio como debería ser. Si necesita una mejor calidad de aleatoriedad en sus mezclas, use el generador de números aleatorios en System.Security.Cryptography así:

using System.Security.Cryptography;
...
public static void Shuffle<T>(this IList<T> list)
{
    RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
    int n = list.Count;
    while (n > 1)
    {
        byte[] box = new byte[1];
        do provider.GetBytes(box);
        while (!(box[0] < n * (Byte.MaxValue / n)));
        int k = (box[0] % n);
        n--;
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

Una comparación simple está disponible en este blog (WayBack Machine).

Editar: Desde que escribí esta respuesta hace un par de años, muchas personas me han comentado o escrito para señalar el gran defecto tonto en mi comparación. Por supuesto que tienen razón. No hay nada malo con System.Random si se usa de la forma prevista. En mi primer ejemplo anterior, creo una instancia de la variable rng dentro del método Shuffle, lo que genera problemas si el método se va a llamar repetidamente. A continuación se muestra un ejemplo completo y fijo basado en un comentario realmente útil recibido hoy de @weston aquí en SO.

Programa.cs:

using System;
using System.Collections.Generic;
using System.Threading;

namespace SimpleLottery
{
  class Program
  {
    private static void Main(string[] args)
    {
      var numbers = new List<int>(Enumerable.Range(1, 75));
      numbers.Shuffle();
      Console.WriteLine("The winning numbers are: {0}", string.Join(",  ", numbers.GetRange(0, 5)));
    }
  }

  public static class ThreadSafeRandom
  {
      [ThreadStatic] private static Random Local;

      public static Random ThisThreadsRandom
      {
          get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
      }
  }

  static class MyExtensions
  {
    public static void Shuffle<T>(this IList<T> list)
    {
      int n = list.Count;
      while (n > 1)
      {
        n--;
        int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
      }
    }
  }
}
grenade avatar Aug 11 '2009 20:08 grenade

Si solo necesitamos mezclar elementos en un orden completamente aleatorio (solo para mezclar los elementos en una lista), prefiero este código simple pero efectivo que ordena los elementos por guía...

var shuffledcards = cards.OrderBy(_ => Guid.NewGuid()).ToList();

Como la gente ha señalado en los comentarios, no se garantiza que los GUID sean aleatorios, por lo que deberíamos utilizar un generador de números aleatorios real en su lugar:

private static Random rng = new Random();
...
var shuffledcards = cards.OrderBy(_ => rng.Next()).ToList();
user453230 avatar Nov 23 '2010 23:11 user453230