982 votes

Randomiser une liste<T> en C#

Quelle est la meilleure façon de rendre aléatoire l'ordre d'une liste générique en C# ? J'ai un ensemble fini de 75 numéros dans une liste à laquelle je voudrais attribuer un ordre aléatoire, afin de les tirer au sort pour une application de type loterie.

4 votes

Il y a un problème ouvert pour intégrer cette fonctionnalité à .NET : github.com/dotnet/corefx/issues/461

5 votes

Vous pourriez être intéressé par ce paquet NuGet qui contient des méthodes d'extension pour le brassage de IList<T> et IEnumerable<T> à l'aide de l'algorithme de Fisher-Yates mentionné ci-dessous

0 votes

@Natan fyi, ils l'ont tué.

1269voto

grenade Points 10089

Mélangez n'importe quel (I)List avec une méthode d'extension basée sur le Le remaniement Fisher-Yates :

public static void Shuffle<T>(this IList<T> list)  
{  
    Random rng = new Random();  
    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;  
    }  
}

Utilisation :

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

Le code ci-dessus utilise la méthode très critiquée System.Random pour sélectionner les candidats à l'échange. C'est rapide mais pas aussi aléatoire que cela devrait l'être. Si vous avez besoin d'une meilleure qualité d'aléatoire dans vos mélanges, utilisez le générateur de nombres aléatoires dans System.Security.Cryptography comme suit :

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;
    }
}

Une comparaison simple est disponible à l'adresse suivante http://thegrenade.blogspot.com/2010/02/when-random-is-too-consistent.html

Edit : Depuis que j'ai écrit cette réponse il y a quelques années, de nombreuses personnes m'ont fait des commentaires ou m'ont écrit pour souligner le gros défaut stupide de ma comparaison. Ils ont bien sûr raison. Il n'y a rien de mal avec System.Random s'il est utilisé de la façon dont il a été conçu. Dans mon premier exemple ci-dessus, j'instancie la variable rng à l'intérieur de la méthode Shuffle, ce qui pose des problèmes si la méthode est appelée à plusieurs reprises. Vous trouverez ci-dessous un exemple complet et corrigé basé sur un commentaire très utile reçu aujourd'hui de @weston ici sur SO.

Programme.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;
      }
    }
  }
}

0 votes

Joli ! J'adore les méthodes d'extension :D

2 votes

Notez que les performances sont nettement inférieures sur les LinkedLists par rapport aux ArrayLists. Assurez-vous toujours de lui envoyer un tableau indexable de performance O(1).

0 votes

@Jedi si c'était un problème important, il serait peut-être avantageux de créer une liste ou un tableau de stockage secondaire, d'y effectuer le brassage et de remplacer le contenu de la liste par celui-ci.

400voto

user453230 Points 867

Si nous avons seulement besoin de mélanger des éléments dans un ordre complètement aléatoire (juste pour mélanger les éléments dans une liste), je préfère ce code simple mais efficace qui ordonne les éléments par guid...

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

1 votes

Il s'agit de tirer parti d'un Linq pour réorganiser la liste. NewGuid crée une Guide (une valeur de 16 octets). OrderBy utilise la fonction fournie pour affecter un objet comparable à chaque carte, puis renvoie une nouvelle collection ordonnée par les Guids. Comme les Guids sont très longs, les chances de collision (même Guid pour deux objets différents) sont très faibles.

0 votes

Comment convertir ceci en une liste ? list = (List<string>)list.OrderBy() est une conversion invalide.

0 votes

@Mark list.OrderyBy(...).ToList() ;

125voto

ShitalShah Points 2213

Je suis un peu surpris par toutes les versions maladroites de ce simple algorithme ici. Fisher-Yates (ou Knuth shuffle) est un peu délicat mais très compact. Si vous allez sur Wikipedia (ou dans les livres d'algorithmes standards tels que CLRS), vous verrez une version de cet algorithme qui a la boucle for inversée et beaucoup de gens ne semblent pas vraiment comprendre pourquoi elle est inversée. La raison principale est que cette version de l'algorithme suppose que le générateur de nombres aléatoires à votre disposition possède les deux propriétés suivantes :

  1. Il accepte n comme unique paramètre d'entrée.
  2. Il renvoie un nombre de 0 à n inclusivement .

Cependant, le générateur de nombres aléatoires .Net ne satisfait pas la propriété #2. Le site Random.Next(n) renvoie à la place un nombre compris entre 0 et n-1 inclus. Si vous essayez d'utiliser le for-loop en sens inverse, vous devrez appeler Random.Next(n+1) qui ajoute une opération supplémentaire.

Cependant, le générateur de nombres aléatoires .Net possède une autre fonction intéressante. Random.Next(a,b) qui retourne a à b-1 inclus. Cela correspond en fait parfaitement à l'implémentation de cet algorithme qui a une boucle for normale. Donc, sans plus attendre, voici l'implémentation correcte, efficace et compacte :

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for(var i=0; i < list.Count; i++)
        list.Swap(i, rnd.Next(i, list.Count));
}

public static void Swap<T>(this IList<T> list, int i, int j)
{
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

5 votes

Ce code ne fonctionne pas comme prévu. Le dernier chiffre est toujours 0 ou list.Count-1 .

1 votes

@ShitalShah Le code actuel dans votre réponse ne donne pas de résultats corrects, car il ne s'agit pas d'un mélange Fisher-Yates correct. Il devrait être corrigé, ainsi que le code dans le lien.

3 votes

Ce code est cassé. Si vous utilisez une liste de chaînes de caractères pour 3 lettres, "A", "B" et "C", CBA et BCA n'apparaîtraient jamais avec cette fonction, à cause de cette ligne : list.Swap(0, rnd.Next(0, i)); En le remplaçant par le texte suivant, on obtient une fonction pseudo-aléatoire fonctionnelle et sans biais : list.Swap(i-1, rnd.Next(0, i));

80voto

Denis Points 365

Méthode étendue pour IEnumerable :

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
    Random rnd = new Random();
    return source.OrderBy<T, int>((item) => rnd.Next());
}

8 votes

Cet algorithme présente deux problèmes importants : -- OrderBy utilise une variante de QuickSort pour trier les éléments par leurs clés (ostensiblement aléatoires). Les performances de QuickSort sont O(N log N) En revanche, un mélange Fisher-Yates est O(N) . Pour une collection de 75 éléments, ce n'est peut-être pas un gros problème, mais la différence deviendra prononcée pour les collections plus importantes.

11 votes

... -- Random.Next() peut produire une distribution raisonnablement pseudo-aléatoire des valeurs, mais elle ne pas garantir que les valeurs seront uniques. La probabilité de clés dupliquées croît (de manière non linéaire) avec N jusqu'à ce qu'il atteigne la certitude lorsque N atteint 2^32+1. Le site OrderBy QuickSort est un stable Ainsi, si plusieurs éléments se voient attribuer la même valeur d'index pseudo-aléatoire, leur ordre dans la séquence de sortie sera le suivant même comme dans la séquence d'entrée ; un biais est donc introduit dans le "shuffle".

31 votes

JohnBeyer : Il y a des problèmes bien plus importants que cette source de biais. Il n'y a que quatre milliards de graines possibles au hasard, ce qui est beaucoup, beaucoup moins que le nombre de mélanges possibles d'un jeu de taille moyenne. Seule une infime partie des mélanges possibles peut être générée. Ce biais éclipse le biais dû aux collisions accidentelles.

13voto

Adam Tegen Points 8563
    public static List<T> Randomize<T>(List<T> list)
    {
        List<T> randomizedList = new List<T>();
        Random rnd = new Random();
        while (list.Count > 0)
        {
            int index = rnd.Next(0, list.Count); //pick a random item from the master list
            randomizedList.Add(list[index]); //place it at the end of the randomized list
            list.RemoveAt(index);
        }
        return randomizedList;
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X