web-dev-qa-db-fra.com

Aléatoire une liste <T>

Quel est le meilleur moyen de rendre aléatoire l'ordre d'une liste générique en C #? J'ai une série finie de 75 numéros dans une liste que je voudrais assigner à un ordre aléatoire, afin de les dessiner pour une application de type loterie.

753
mirezus

Mélangez les (I)List avec une méthode d'extension basée sur le shuffle 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;  
    }  
}

Usage:

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

Le code ci-dessus utilise la méthode très critiquée System.Random pour sélectionner des candidats au swap. C'est rapide mais pas aussi aléatoire que cela devrait ê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 sur ce blog (WayBack Machine).

Edit: Depuis que cette réponse a été écrite il y a quelques années, beaucoup de gens m'ont commenté ou écrit pour souligner le gros défaut ridicule de ma comparaison. Ils ont bien sûr raison. Il n'y a rien de mal à System.Random s'il est utilisé comme prévu. Dans mon premier exemple ci-dessus, j'instancie la variable rng à l'intérieur de la méthode Shuffle, ce qui pose problème si la méthode doit être appelée à plusieurs reprises. Vous trouverez ci-dessous un exemple complet et basé sur un commentaire très utile reçu aujourd'hui de @weston ici à SO.

Program.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;
      }
    }
  }
}
1019
grenade

Si nous avons seulement besoin de mélanger les é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 ...

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

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

  1. Il accepte n comme paramètre d'entrée unique.
  2. Il retourne un nombre compris entre 0 et n inclus.

Cependant, le générateur de nombres aléatoires .Net ne vérifie pas la propriété n ° 2. La Random.Next(n) renvoie à la place le nombre compris entre 0 et n-1. Si vous essayez d'utiliser 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 a une autre fonction intéressante Random.Next(a,b) qui renvoie a en b-1 inclus. Cela s’adapte parfaitement à la mise en œuvre de cet algorithme qui a une boucle for normale. Donc, sans plus tarder, voici la mise en œuvre correcte, efficace et compacte:

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for(var i=0; i < list.Count - 1; 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;
}
102
Shital Shah

Méthode d'extension pour IEnumerable:

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
    Random rnd = new Random();
    return source.OrderBy<T, int>((item) => rnd.Next());
}
71
Denis
    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;
    }
10
Adam Tegen

EDIT Le RemoveAt est une faiblesse de ma version précédente. Cette solution surmonte cela.

public static IEnumerable<T> Shuffle<T>(
        this IEnumerable<T> source,
        Random generator = null)
{
    if (generator == null)
    {
        generator = new Random();
    }

    var elements = source.ToArray();
    for (var i = elements.Length - 1; i >= 0; i--)
    {
        var swapIndex = generator.Next(i + 1);
        yield return elements[swapIndex];
        elements[swapIndex] = elements[i];
    }
}

Notez le Random generator facultatif. Si l'implémentation de structure de base de Random n'est pas adaptée aux threads ou suffisamment forte sur le plan cryptographique pour vos besoins, vous pouvez injecter votre implémentation dans l'opération.

On trouvera dans cette réponse une implémentation appropriée pour une implémentation Random forte sur le plan cryptographique et sécurisée au niveau des threads.


Voici une idée, prolongez IList de manière (espérons-le) efficace.

public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
    var choices = Enumerable.Range(0, list.Count).ToList();
    var rng = new Random();
    for(int n = choices.Count; n > 1; n--)
    {
        int k = rng.Next(n);
        yield return list[choices[k]];
        choices.RemoveAt(k);
    }

    yield return list[choices[0]];
}
8
Jodrell

Vous pouvez y parvenir en utilisant cette méthode d'extension simple

public static class IEnumerableExtensions
{

    public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
    {
        Random r = new Random();

        return target.OrderBy(x=>(r.Next()));
    }        
}

et vous pouvez l'utiliser en procédant comme suit

// use this on any collection that implements IEnumerable!
// List, Array, HashSet, Collection, etc

List<string> myList = new List<string> { "hello", "random", "world", "foo", "bar", "bat", "baz" };

foreach (string s in myList.Randomize())
{
    Console.WriteLine(s);
}
5
Shehab Fawzy

L'idée est d'obtenir un objet anonyme avec un élément et un ordre aléatoire, puis de réorganiser les éléments en fonction de cet ordre et de la valeur renvoyée:

var result = items.Select(x => new { value = x, order = rnd.Next() })
            .OrderBy(x => x.order).Select(x => x.value).ToList()
4
Andrey Kucher

C’est ma méthode préférée de mélange lorsqu’il est souhaitable de ne pas modifier l’original. C'est une variante de l'algorithme algorithme "inside-out" de Fisher – Yates qui fonctionne sur toutes les séquences énumérables (la longueur de sourcen'a pas besoin d'être connue dès le début).

public static IList<T> NextList<T>(this Random r, IEnumerable<T> source)
{
  var list = new List<T>();
  foreach (var item in source)
  {
    var i = r.Next(list.Count + 1);
    if (i == list.Count)
    {
      list.Add(item);
    }
    else
    {
      var temp = list[i];
      list[i] = item;
      list.Add(temp);
    }
  }
  return list;
}

Cet algorithme peut également être implémenté en allouant une plage allant de 0 à length - 1 et en épuisant les index de manière aléatoire en échangeant l'index choisi de manière aléatoire avec le dernier index jusqu'à ce que tous les index aient été choisis exactement une fois. Ce code ci-dessus accomplit exactement la même chose mais sans l’allocation supplémentaire. Ce qui est plutôt chouette.

En ce qui concerne la classe Randomname__, il s’agit d’un générateur de nombres à usage général (et si j’exécutais une loterie, j’envisagerais d’utiliser quelque chose de différent). Il repose également sur une valeur de départ basée sur le temps par défaut. Une petite solution au problème consiste à ensemencer la classe Randomavec le RNGCryptoServiceProviderou à utiliser le RNGCryptoServiceProviderdans une méthode semblable à celle-ci (voir ci-dessous) pour générer une valeur aléatoire à virgule flottante double choisie de manière uniforme, mais le fonctionnement d'une loterie requiert une grande précision. la nature de la source aléatoire.

var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);

Le point de générer un double aléatoire (entre 0 et 1 exclusivement) est d'utiliser l'échelle pour une solution entière. Si vous devez choisir quelque chose dans une liste basée sur un double aléatoire xqui sera toujours 0 <= x && x < 1 est simple.

return list[(int)(x * list.Count)];

Prendre plaisir!

3
John Leidegren

Si vous avez un nombre fixe (75), vous pouvez créer un tableau avec 75 éléments, puis énumérer votre liste, en déplaçant les éléments vers des positions aléatoires dans le tableau. Vous pouvez générer le mappage du numéro de liste en index de tableau à l’aide de Fisher-Yates shuffle .

3
dmo

J'utilise habituellement:

var list = new List<T> ();
fillList (list);
var randomizedList = new List<T> ();
var rnd = new Random ();
while (list.Count != 0)
{
    var index = rnd.Next (0, list.Count);
    randomizedList.Add (list [index]);
    list.RemoveAt (index);
}
3
albertein

Si cela ne vous dérange pas d'utiliser deux Lists, c'est probablement la façon la plus simple de le faire, mais probablement pas la plus efficace ou la plus imprévisible:

List<int> xList = new List<int>() { 1, 2, 3, 4, 5 };
List<int> deck = new List<int>();

foreach (int xInt in xList)
    deck.Insert(random.Next(0, deck.Count + 1), xInt);
2
Xelights

Vous pouvez le faire en triant et la comparaison se fera en utilisant aléatoire

var Rand = new Random();
var list = new List<T>();
list.Sort((a,b)=>Rand.Next(-1,2));
1
Miri Gold
 public Deck(IEnumerable<Card> initialCards) 
    {
    cards = new List<Card>(initialCards);
    public void Shuffle() 
     }
    {
        List<Card> NewCards = new List<Card>();
        while (cards.Count > 0) 
        {
            int CardToMove = random.Next(cards.Count);
            NewCards.Add(cards[CardToMove]);
            cards.RemoveAt(CardToMove);
        }
        cards = NewCards;
    }

public IEnumerable<string> GetCardNames() 

{
    string[] CardNames = new string[cards.Count];
    for (int i = 0; i < cards.Count; i++)
    CardNames[i] = cards[i].Name;
    return CardNames;
}

Deck deck1;
Deck deck2;
Random random = new Random();

public Form1() 
{

InitializeComponent();
ResetDeck(1);
ResetDeck(2);
RedrawDeck(1);
 RedrawDeck(2);

}



 private void ResetDeck(int deckNumber) 
    {
    if (deckNumber == 1) 
{
      int numberOfCards = random.Next(1, 11);
      deck1 = new Deck(new Card[] { });
      for (int i = 0; i < numberOfCards; i++)
           deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14)));
       deck1.Sort();
}


   else
    deck2 = new Deck();
 }

private void reset1_Click(object sender, EventArgs e) {
ResetDeck(1);
RedrawDeck(1);

}

private void shuffle1_Click(object sender, EventArgs e) 
{
    deck1.Shuffle();
    RedrawDeck(1);

}

private void moveToDeck1_Click(object sender, EventArgs e) 
{

    if (listBox2.SelectedIndex >= 0)
    if (deck2.Count > 0) {
    deck1.Add(deck2.Deal(listBox2.SelectedIndex));

}

    RedrawDeck(1);
    RedrawDeck(2);

}
1
sumit laddha

Voici un Shuffler efficace qui renvoie un tableau d'octets de valeurs mélangées. Il ne mélange jamais plus que nécessaire. Il peut être redémarré à l'endroit où il s'était arrêté auparavant. Mon implémentation réelle (non montrée) est un composant MEF qui permet à un utilisateur spécifié de remplacer le mélangeur.

    public byte[] Shuffle(byte[] array, int start, int count)
    {
        int n = array.Length - start;
        byte[] shuffled = new byte[count];
        for(int i = 0; i < count; i++, start++)
        {
            int k = UniformRandomGenerator.Next(n--) + start;
            shuffled[i] = array[k];
            array[k] = array[start];
            array[start] = shuffled[i];
        }
        return shuffled;
    }

`

0
BSalita
    List<T> OriginalList = new List<T>();
    List<T> TempList = new List<T>();
    Random random = new Random();
    int length = OriginalList.Count;
    int TempIndex = 0;

    while (length > 0) {
        TempIndex = random.Next(0, length);  // get random value between 0 and original length
        TempList.Add(OriginalList[TempIndex]); // add to temp list
        OriginalList.RemoveAt(TempIndex); // remove from original list
        length = OriginalList.Count;  // get new list <T> length.
    }

    OriginalList = new List<T>();
    OriginalList = TempList; // copy all items from temp list to original list.
0
Sultan Salman

Une simple modification de réponse acceptée qui renvoie une nouvelle liste au lieu de fonctionner sur place et accepte le plus général IEnumerable<T> comme le font beaucoup d'autres méthodes Linq.

private static Random rng = new Random();

/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
    var source = list.ToList();
    int n = source.Count;
    var shuffled = new List<T>(n);
    shuffled.AddRange(source);
    while (n > 1) {
        n--;
        int k = rng.Next(n + 1);
        T value = shuffled[k];
        shuffled[k] = shuffled[n];
        shuffled[n] = value;
    }
    return shuffled;
}
0
Extragorey

Voici un moyen sûr de faire cela:

public static class EnumerableExtension
{
    private static Random globalRng = new Random();

    [ThreadStatic]
    private static Random _rng;

    private static Random rng 
    {
        get
        {
            if (_rng == null)
            {
                int seed;
                lock (globalRng)
                {
                    seed = globalRng.Next();
                }
                _rng = new Random(seed);
             }
             return _rng;
         }
    }

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
    {
        return items.OrderBy (i => rng.Next());
    }
}
0