1115 Stimmen

List<T> zufällig ordnen

Wie kann man am besten die Reihenfolge einer generischen Liste in C# zufällig durcheinanderbringen? Ich habe eine endliche Menge von 75 Zahlen in einer Liste, denen ich eine zufällige Reihenfolge zuweisen möchte, um sie für eine Lotterie-Anwendung zu ziehen.

4 Stimmen

Es gibt ein offenes Problem, um diese Funktionalität in .NET zu integrieren: github.com/dotnet/corefx/issues/461

7 Stimmen

Du könntest an diesem NuGet-Paket interessiert sein, das Erweiterungsmethoden für das Mischen von IList und IEnumerable mithilfe des unten erwähnten Fisher-Yates-Algorithmus enthält

0 Stimmen

1405voto

grenade Punkte 30103

Mischen Sie jede (I)List mit einer Erweiterungsmethode basierend auf dem Fisher-Yates shuffle:

private static Random rng = new Random();  

public static void Shuffle(this IList 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;  
    }  
}

Verwendung:

List produkte = GetProducts();
produkte.Shuffle();

Der obige Code verwendet die stark kritisierte System.Random-Methode zur Auswahl von Austauschkandidaten. Es ist schnell, aber nicht so zufällig, wie es sein sollte. Wenn Sie eine bessere Qualität der Zufälligkeit in Ihren Mischungen benötigen, verwenden Sie den Zufallszahlengenerator in System.Security.Cryptography wie folgt:

using System.Security.Cryptography;
...
public static void Shuffle(this IList 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;
    }
}

Ein einfacher Vergleich ist verfügbar auf diesem Blog (WayBack Machine).

Bearbeitung: Seitdem ich diese Antwort vor ein paar Jahren geschrieben habe, haben viele Leute kommentiert oder mir geschrieben, um den großen dummen Fehler in meinem Vergleich herauszustellen. Sie haben natürlich recht. Es ist nichts falsch mit System.Random, wenn es auf die beabsichtigte Weise verwendet wird. In meinem ersten obigen Beispiel instantiiere ich die rng-Variable innerhalb der Shuffle-Methode, was Probleme verursacht, wenn die Methode wiederholt aufgerufen wird. Unten finden Sie ein korrigiertes Beispiel auf der Grundlage eines wirklich nützlichen Kommentars, den ich heute von @weston hier im SO erhalten habe.

Program.cs:

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

namespace SimpleLottery
{
  class Program
  {
    private static void Main(string[] args)
    {
      var zahlen = new List(Enumerable.Range(1, 75));
      zahlen.Shuffle();
      Console.WriteLine("Die Gewinnzahlen lauten: {0}", string.Join(",  ", zahlen.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(this IList 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;
      }
    }
  }
}

2 Stimmen

Beachten Sie, dass die Leistung bei LinkedLists im Vergleich zu ArrayLists drastisch niedriger ist. Stellen Sie immer sicher, dass es sich um ein O(1) leistungsstarkes indexierbares Array handelt.

0 Stimmen

@Jedi Wenn das ein signifikantes Problem wäre, wäre es möglicherweise vorteilhaft, eine sekundäre Speicherliste oder -array zu erstellen, dort das Mischen durchzuführen und die Inhalte der Liste damit zu ersetzen.

37 Stimmen

Was passiert, wenn list.Count > Byte.MaxValue ist? Wenn n = 1000, dann 255 / 1000 = 0, also wird die Do-Schleife eine Endlosschleife sein, da box[0] < 0 immer falsch ist.

525voto

user453230 Punkte 4829

Wenn wir nur Elemente in einer völlig zufälligen Reihenfolge mischen müssen (nur um die Elemente in einer Liste zu mischen), ziehe ich diesen einfachen, aber effektiven Code vor, der Elemente nach der GUID ordnet ...

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

Wie von einigen Personen in den Kommentaren erwähnt wurde, sind GUIDs nicht garantiert zufällig, daher sollten wir stattdessen einen echten Zufallszahlengenerator verwenden:

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

1 Stimmen

Dies nutzt eine Linq-Methode, um die Liste neu zu ordnen. NewGuid erstellt eine zufällige Guid (einen 16-Byte-Wert). OrderBy verwendet die bereitgestellte Funktion, um jedem Objekt eine vergleichbare Karte zuzuweisen, und gibt dann eine neue Sammlung zurück, die nach den Guids geordnet ist. Da Guids so lang sind, ist die Wahrscheinlichkeit einer Kollision (dasselbe Guid für zwei verschiedene Objekte) sehr gering.

51 Stimmen

GUIDs sind dazu gedacht, eindeutig, nicht zufällig zu sein. Ein Teil davon stammt von Maschinen und ein anderer Teil von der Zeit, und nur ein kleiner Teil ist zufällig. blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx

130 Stimmen

Das ist eine schöne elegante Lösung. Wenn Sie etwas anderes als eine GUID zur Generierung von Zufälligkeit wünschen, ordnen Sie einfach nach etwas anderem. z.B.: var shuffledcards = cards.OrderBy(a => rng.Next()); compilr.com/grenade/sandbox/Program.cs

127voto

Shital Shah Punkte 54846

Ich bin ein wenig überrascht von all den umständlichen Versionen dieses einfachen Algorithmus hier. Fisher-Yates (oder Knuth shuffle) ist etwas knifflig, aber sehr kompakt. Warum ist es knifflig? Weil du darauf achten musst, ob dein Zufallszahlengenerator r(a,b) einen Wert zurückgibt, bei dem b inklusiv oder exklusiv ist. Ich habe auch die Wikipedia-Beschreibung bearbeitet, damit die Leute nicht blind dem Pseudocode dort folgen und schwer zu erkennende Fehler verursachen. Für .Net gibt Random.Next(a,b) eine Zahl exklusiv von b zurück, also ohne weiteres, hier ist wie es in C#/.Net implementiert werden kann:

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

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

Versuche diesen Code.

7 Stimmen

Dieser Code funktioniert nicht wie erwartet. Die letzte Zahl ist immer 0 oder list.Count-1.

3 Stimmen

@ShitalShah Der aktuelle Code in Ihrer Antwort liefert keine korrekten Ergebnisse, da es sich nicht um eine korrekte Fisher-Yates-Mischung handelt. Es sollte behoben werden, ebenso wie der Code im Link.

6 Stimmen

Dieser Code ist kaputt. Wenn Sie eine Liste von Zeichenfolgen für 3 Buchstaben verwendet haben, "A", "B" und "C", würde CBA und BCA mit dieser Funktion buchstäblich niemals auftreten, aufgrund dieser Zeile: list.Swap(0, rnd.Next(0, i)); Durch Ändern auf das folgende wird es behoben und macht es zu einer funktionierenden, nicht voreingenommenen Pseudo-Zufallsfunktion: list.Swap(i-1, rnd.Next(0, i));

85voto

Denis Punkte 925

Erweiterungsmethode für IEnumerable:

public static IEnumerable Zufällig(this IEnumerable quelle)
{
    Random rnd = new Random();
    return quelle.OrderBy((item) => rnd.Next());
}

12 Stimmen

Es gibt zwei wesentliche Probleme mit diesem Algorithmus: -- OrderBy verwendet eine QuickSort-Variante, um die Elemente nach ihren (angeblich zufälligen) Schlüsseln zu sortieren. Die Leistung von QuickSort beträgt O(N log N); im Gegensatz dazu hat ein Fisher-Yates-Shuffle eine Leistung von O(N). Für eine Sammlung von 75 Elementen ist dies möglicherweise kein großes Problem, aber der Unterschied wird bei größeren Sammlungen deutlicher.

13 Stimmen

... -- Random.Next() kann eine ziemlich pseudozufällige Verteilung von Werten erzeugen, aber es garantiert nicht, dass die Werte einzigartig sind. Die Wahrscheinlichkeit von Duplikaten wächst (nicht linear) mit N bis sie sicher wird, wenn N 2^32+1 erreicht. Der Sortieralgorithmus OrderBy QuickSort ist ein stabiler Sortieralgorithmus; daher, wenn mehrere Elemente den gleichen pseudozufälligen Indexwert zugewiesen bekommen, wird ihre Reihenfolge in der Ausgabesequenz die gleiche sein wie in der Eingabesequenz; dadurch wird eine Voreingenommenheit in das "Mischen" eingeführt.

40 Stimmen

@JohnBeyer: Es gibt weit, weit größere Probleme als diese Quelle von Bias. Es gibt nur vier Milliarden mögliche Seeds für Random, was weit, weit weniger ist als die Anzahl der möglichen Mischungen eines moderat großen Sets. Nur eine winzige Fraktion der möglichen Mischungen kann generiert werden. Dieser Bias übertrifft den Bias durch zufällige Kollisionen bei weitem.

20voto

Andrey Kucher Punkte 347

Idee ist es, ein anonymes Objekt mit Element und zufälliger Reihenfolge zu erhalten und dann die Elemente nach dieser Reihenfolge neu zu ordnen und den Wert zurückzugeben:

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

1 Stimmen

Was ist der Unterschied, wenn direkt nach rnd.Next() bestellt wird?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X