Een lijst willekeurig maken<T>

Wat is de beste manier om de volgorde van een generieke lijst in C# willekeurig te maken? Ik heb een eindige reeks van 75 getallen in een lijst waaraan ik een willekeurige volgorde wil toewijzen om ze te kunnen trekken voor een loterijtoepassing.


Antwoord 1, autoriteit 100%

Shuffle elke (I)Listmet een extensiemethode gebaseerd op de Fisher-Yates shuffle:

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

Gebruik:

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

De bovenstaande code gebruikt de veel bekritiseerde System.Random-methode om ruilkandidaten te selecteren. Het is snel, maar niet zo willekeurig als het zou moeten zijn. Als je een betere kwaliteit van willekeur in je shuffles nodig hebt, gebruik dan de generator voor willekeurige getallen in System.Security.Cryptography als volgt:

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

Een eenvoudige vergelijking is beschikbaar op deze blog(WayBack Machine).

Bewerken: sinds het schrijven van dit antwoord een paar jaar geleden, hebben veel mensen commentaar op me gegeven of geschreven om mij te wijzen op de grote dwaze fout in mijn vergelijking. Ze hebben natuurlijk gelijk. Er is niets mis met System.Random als het wordt gebruikt zoals het bedoeld is. In mijn eerste voorbeeld hierboven, instantieer ik de rng-variabele in de Shuffle-methode, die om problemen vraagt als de methode herhaaldelijk wordt aangeroepen. Hieronder is een vast, volledig voorbeeld gebaseerd op een zeer nuttige opmerking die vandaag is ontvangen van @weston hier op 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;
      }
    }
  }
}

Antwoord 2, autoriteit 31%

Als we items alleen in een volledig willekeurige volgorde hoeven te schudden (alleen om de items in een lijst te mixen), geef ik de voorkeur aan deze eenvoudige maar effectieve code die items per gids bestelt…

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

Zoals mensen in de opmerkingen hebben opgemerkt, zijn GUID’s niet gegarandeerd willekeurig, dus we zouden in plaats daarvan een echte generator voor willekeurige getallen moeten gebruiken:

private static Random rng = new Random();
...
var shuffledcards = cards.OrderBy(a => rng.Next()).ToList();

Antwoord 3, autoriteit 10%

Ik ben een beetje verrast door alle onhandige versies van dit eenvoudige algoritme hier. Fisher-Yates (of Knuth shuffle) is een beetje lastig maar erg compact. Waarom is het lastig? Omdat u moet letten op de vraag of uw generator voor willekeurige getallen r(a,b)een waarde retourneert waarbij binclusief of exclusief is. Ik heb ook de Wikipedia-beschrijvingaangepast zodat mensen niet blindelings volg daar pseudocode en creëer moeilijk te detecteren bugs. Voor .Net retourneert Random.Next(a,b)een getal exclusief b, dus zonder verder oponthoud, hier is hoe het kan worden geïmplementeerd in C#/.Net:

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for(var i=list.Count; i > 0; i--)
        list.Swap(0, rnd.Next(0, i));
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

Probeer deze code .


Antwoord 4, Autoriteit 6%

Extension-methode voor IEnumerable:

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

Antwoord 5, Autoriteit 2%

Idee is anoniem object met item en willekeurige volgorde en dan vervolgens items opnieuw instellen door deze bestelling en retourwaarde:

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

Antwoord 6

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

Antwoord 7

bewerken
De RemoveAtis een zwakte in mijn vorige versie. Deze oplossing overwint dat.

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

OPMERKING De optionele Random generator, als de implementatie van de basiskader van Randomniet draadveilig of cryptografisch sterk genoeg is voor uw behoeften, kunt u uw implementatie in de bediening.

Een geschikte implementatie voor een draadveilig cryptografisch sterk RandomImplementatie is te vinden in dit antwoord.


Hier is een idee, uitbreiden Ilist in een (hopelijk) efficiënte manier.

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


Antwoord 8

Dit is mijn favoriete methode van een shuffle wanneer het wenselijk is om het origineel niet te wijzigen. Het is een variant van de fisher-yates “Inside- “Algoritme die werkt op elke tumele reeks (de lengte van sourcehoeft niet van start te zijn).

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

Dit algoritme kan ook worden geïmplementeerd door een bereik van 0toe te wijzen aan length - 1en willekeurig de indices uitputten door de willekeurig gekozen index met de laatste index tot alles te verwijderen indices zijn precies één keer gekozen. Deze hierboven code volbrengt precies hetzelfde, maar zonder de extra toewijzing. Wat behoorlijk netjes is.

Met betrekking tot de RandomKlasse is het een generator van het algemene doelnummer (en als ik een loterij had, zou ik overwegen iets anders te gebruiken). Het is standaard gebaseerd op een op tijd gebaseerde zaadwaarde. Een kleine verlichting van het probleem is om de Randomklasse met de RNGCryptoServiceProviderof u kunt de RNGCryptoServiceProviderin een dergelijke methode gebruiken (zie hieronder) om uniform gekozen willekeurige dubbele zwevende puntwaarden te genereren, maar het uitvoeren van een loterij behaagt vrijwel het begrijpen van willekeurigheid en de aard van de bron van willekeur.

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

Het punt van het genereren van een willekeurige dubbele (tussen 0 en 1 uitsluitend) is om op schaal te gebruiken tot een geheel getal. Als u iets van een lijst moet kiezen op basis van een willekeurige dubbele xdie altijd zal zijn 0 <= x && x < 1is rechtdoor.

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

Geniet!


Antwoord 9

Als je het niet erg vindt om twee Listste gebruiken, dan is dit waarschijnlijk de gemakkelijkste manier om het te doen, maar waarschijnlijk niet de meest efficiënte of onvoorspelbare:

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

Antwoord 10

U kunt dat bereiken door deze eenvoudige uitbreidingsmethode te gebruiken

public static class IEnumerableExtensions
{
    public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
    {
        Random r = new Random();
        return target.OrderBy(x=>(r.Next()));
    }        
}

en je kunt het gebruiken door het volgende te doen

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

Antwoord 11

Ik wilde alleen een variant voorstellen met behulp van een IComparer<T>en List.Sort():

public class RandomIntComparer : IComparer<int>
{
    private readonly Random _random = new Random();
    public int Compare(int x, int y)
    {
        return _random.Next(-1, 2);
    }
}

Gebruik:

list.Sort(new RandomIntComparer());

Antwoord 12

Als u een vast aantal (75) heeft, kunt u een array maken met 75 elementen, vervolgens uw lijst opsommen en de elementen naar willekeurige posities in de array verplaatsen. U kunt de toewijzing van het lijstnummer aan de array-index genereren met behulp van de Fisher-Yates shuffle.


Antwoord 13

Ik gebruik meestal:

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

Antwoord 14

Ik heb online een interessante oplossing gevonden.

Met dank: https: //improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/

var shuffled = myList.OrderBy(x => Guid.NewGuid()).ToList();


Antwoord 15

Een eenvoudige wijziging van het geaccepteerde antwoorddie een nieuwe lijst retourneert in plaats van ter plaatse te werken, en de meer algemene accepteert IEnumerable<T>zoals veel andere Linq-methoden doen.

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

Antwoord 16

   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.

Antwoord 17

Hier is een efficiënte Shuffler die een bytearray van geschudde waarden retourneert. Er wordt nooit meer geschud dan nodig is. Het kan opnieuw worden gestart vanaf waar het eerder was gebleven. Mijn daadwerkelijke implementatie (niet getoond) is een MEF-component die een door de gebruiker gespecificeerde vervangende shuffler mogelijk maakt.

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

`


Antwoord 18

Hier is een veilige manier om dit te doen:

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

Antwoord 19

Hier is een implementatie van de Fisher-Yates-shuffle waarmee het aantal elementen kan worden gespecificeerd dat moet worden geretourneerd; daarom is het niet nodig om eerst de hele verzameling te sorteren voordat u het gewenste aantal elementen neemt.

De volgorde van het verwisselen van elementen is standaard omgekeerd; en gaat van het eerste element naar het laatste element, zodat het ophalen van een subset van de verzameling dezelfde (gedeeltelijke) volgorde oplevert als het shuffelen van de hele verzameling:

collection.TakeRandom(5).SequenceEqual(collection.Shuffle().Take(5)); // true

Dit algoritme is gebaseerd op de (moderne) versie van Durstenfeld van de Fisher -Yates schuifeltop Wikipedia.

public static IList<T> TakeRandom<T>(this IEnumerable<T> collection, int count, Random random) => shuffle(collection, count, random);
public static IList<T> Shuffle<T>(this IEnumerable<T> collection, Random random) => shuffle(collection, null, random);
private static IList<T> shuffle<T>(IEnumerable<T> collection, int? take, Random random)
{
    var a = collection.ToArray();
    var n = a.Length;
    if (take <= 0 || take > n) throw new ArgumentException("Invalid number of elements to return.");
    var end = take ?? n;
    for (int i = 0; i < end; i++)
    {
        var j = random.Next(i, n);
        (a[i], a[j]) = (a[j], a[i]);
    }
    if (take.HasValue) return new ArraySegment<T>(a, 0, take.Value);
    return a;
}

Antwoord 20

Uw vraag is hoe u een lijst randomiseert. Dit betekent:

  1. Alle unieke combinaties moeten mogelijk zijn
  2. Alle unieke combinaties moeten voorkomen met dezelfde verdeling (AKA is niet-bevooroordeeld).

Een groot aantal van de antwoorden op deze vraag voldoet NIET aan de twee bovenstaande vereisten om “willekeurig” te zijn.

Hier is een compacte, niet-vooringenomen pseudo-willekeurige functie volgens de Fisher-Yates shuffle-methode.

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for (var i = list.Count-1; i > 0; i--)
    {
        var randomIndex = rnd.Next(i + 1); //maxValue (i + 1) is EXCLUSIVE
        list.Swap(i, randomIndex); 
    }
}
public static void Swap<T>(this IList<T> list, int indexA, int indexB)
{
   var temp = list[indexA];
   list[indexA] = list[indexB];
   list[indexB] = temp;
}

Antwoord 21

Je kunt de Shuffle-extensie gebruiken methond van het morelinq-pakket, het werkt op IEnumerables

install-package morelinq

using MoreLinq;
...    
var randomized = list.Shuffle();

Antwoord 22

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

Antwoord 23

private List<GameObject> ShuffleList(List<GameObject> ActualList) {
    List<GameObject> newList = ActualList;
    List<GameObject> outList = new List<GameObject>();
    int count = newList.Count;
    while (newList.Count > 0) {
        int rando = Random.Range(0, newList.Count);
        outList.Add(newList[rando]);
        newList.RemoveAt(rando);
    }
    return (outList);
}

gebruik:

List<GameObject> GetShuffle = ShuffleList(ActualList);

Antwoord 24

Oud bericht zeker, maar ik gebruik gewoon een GUID.

Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();

Een GUID is altijd uniek en omdat deze elke keer opnieuw wordt gegenereerd, verandert het resultaat elke keer.


Antwoord 25

Een heel eenvoudige benadering van dit soort problemen is om een aantal willekeurige elementen te verwisselen in de lijst.

In pseudo-code zou dit er als volgt uitzien:

do 
    r1 = randomPositionInList()
    r2 = randomPositionInList()
    swap elements at index r1 and index r2 
for a certain number of times

Other episodes