Es ist keine Art zu mischen, die ich mag, hauptsächlich mit der Begründung, dass es O (n log n) ist, ohne guten Grund, wenn es einfach ist, ein O (n) Shuffle zu implementieren. Der Code in der Frage "funktioniert", indem er jedem Element eine zufällige (hoffentlich eindeutige!) Nummer gibt und die Elemente dann nach dieser Nummer ordnet.
Ich bevorzuge Durstenfields Variante des Fisher-Yates-Shuffle, bei dem Elemente ausgetauscht werden.
Die Implementierung einer einfachen Shuffle
Erweiterungsmethode würde im Wesentlichen darin bestehen, die Eingabe aufzurufen ToList
oder ToArray
zu verwenden und dann eine vorhandene Implementierung von Fisher-Yates zu verwenden. (Geben Sie den Random
als Parameter ein, um das Leben im Allgemeinen schöner zu machen.) Es gibt viele Implementierungen ... Ich habe wahrscheinlich irgendwo eine Antwort.
Das Schöne an einer solchen Erweiterungsmethode ist, dass dem Leser dann sehr klar ist, was Sie tatsächlich versuchen.
EDIT: Hier ist eine einfache Implementierung (keine Fehlerprüfung!):
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
// Note i > 0 to avoid final pointless iteration
for (int i = elements.Length-1; i > 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
int swapIndex = rng.Next(i + 1);
T tmp = elements[i];
elements[i] = elements[swapIndex];
elements[swapIndex] = tmp;
}
// Lazily yield (avoiding aliasing issues etc)
foreach (T element in elements)
{
yield return element;
}
}
BEARBEITEN: Die folgenden Kommentare zur Leistung haben mich daran erinnert, dass wir die Elemente tatsächlich zurückgeben können, wenn wir sie mischen:
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
for (int i = elements.Length - 1; i >= 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
// ... except we don't really need to swap it fully, as we can
// return it immediately, and afterwards it's irrelevant.
int swapIndex = rng.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
Dies erledigt jetzt nur noch so viel Arbeit wie nötig.
Beachten Sie, dass Sie in beiden Fällen vorsichtig mit der von Random
Ihnen verwendeten Instanz sein müssen :
- Wenn Sie zwei Instanzen von
Random
ungefähr zur gleichen Zeit erstellen, erhalten Sie dieselbe Folge von Zufallszahlen (wenn Sie auf dieselbe Weise verwendet werden).
Random
ist nicht threadsicher.
Ich habe einen Artikel,Random
der ausführlicher auf diese Probleme eingeht und Lösungen bietet.
source.ToArray();
Sie, dass Sieusing System.Linq;
in der gleichen Datei haben müssen. Wenn Sie dies nicht tun, erhalten Sie folgende Fehlermeldung:'System.Collections.Generic.IEnumerable<T>' does not contain a definition for 'ToArray' and no extension method 'ToArray' accepting a first argument of type 'System.Collections.Generic.IEnumerable<T>' could be found (are you missing a using directive or an assembly reference?)
Dies basiert auf Jon Skeets Antwort .
In dieser Antwort wird das Array gemischt und dann mit zurückgegeben
yield
. Das Nettoergebnis ist, dass das Array für die Dauer von foreach sowie für die Iteration erforderliche Objekte im Speicher gehalten wird und die Kosten dennoch am Anfang stehen - die Ausbeute ist im Grunde eine leere Schleife.Dieser Algorithmus wird häufig in Spielen verwendet, in denen die ersten drei Elemente ausgewählt werden und die anderen erst später, wenn überhaupt, benötigt werden. Mein Vorschlag ist,
yield
die Nummern zu tauschen, sobald sie getauscht werden. Dies reduziert die Startkosten, während die Iterationskosten bei O (1) bleiben (im Grunde 5 Operationen pro Iteration). Die Gesamtkosten würden gleich bleiben, aber das Mischen selbst würde schneller sein. In Fällen, in denen dies aufgerufencollection.Shuffle().ToArray()
wird, macht es theoretisch keinen Unterschied, aber in den oben genannten Anwendungsfällen beschleunigt es den Start. Dies würde den Algorithmus auch für Fälle nützlich machen, in denen Sie nur wenige eindeutige Elemente benötigen. Wenn Sie beispielsweise drei Karten aus einem 52er-Deck herausziehen müssen, können Sie anrufendeck.Shuffle().Take(3)
und es finden nur drei Swaps statt (obwohl das gesamte Array zuerst kopiert werden müsste).quelle
Ausgehend von diesem Zitat von Skeet:
Ich werde ein wenig den Grund für das hoffentlich Einzigartige erklären !
Nun aus dem Enumerable.OrderBy :
Dies ist sehr wichtig! Was passiert, wenn zwei Elemente dieselbe Zufallszahl "erhalten"? Es kommt vor, dass sie in derselben Reihenfolge bleiben, in der sie sich im Array befinden. Was ist nun die Möglichkeit dafür? Es ist schwierig, genau zu berechnen, aber es gibt das Geburtstagsproblem , das genau dieses Problem ist.
Ist es jetzt real? Ist es wahr?
Wie immer schreiben Sie im Zweifelsfall einige Programmzeilen: http://pastebin.com/5CDnUxPG
Dieser kleine Codeblock mischt ein Array von 3 Elementen eine bestimmte Anzahl von Malen, wobei der Fisher-Yates-Algorithmus rückwärts und der Fisher-Yates-Algorithmus vorwärts ausgeführt wird (auf der Wiki- Seite gibt es zwei Pseudocode-Algorithmen ... Sie erzeugen Äquivalente Ergebnisse, aber eines wird vom ersten bis zum letzten Element ausgeführt, während das andere vom letzten bis zum ersten Element ausgeführt wird), der naive falsche Algorithmus von http://blog.codinghorror.com/the-danger-of-naivete/ und unter Verwendung des
.OrderBy(x => r.Next())
und die.OrderBy(x => r.Next(someValue))
.Nun Random.Next ist
also ist es gleichbedeutend mit
Um zu testen, ob dieses Problem besteht, können wir das Array vergrößern (etwas sehr Langsames) oder einfach den Maximalwert des Zufallszahlengenerators verringern (
int.MaxValue
keine "spezielle" Zahl ... Es ist einfach eine sehr große Zahl). Wenn der Algorithmus nicht durch die Stabilität von verzerrt ist, sollte letztendlichOrderBy
jeder Wertebereich das gleiche Ergebnis liefern.Das Programm testet dann einige Werte im Bereich von 1 bis 4096. Wenn man das Ergebnis betrachtet, ist es ziemlich klar, dass der Algorithmus für niedrige Werte (<128) sehr voreingenommen ist (4-8%). Mit 3 Werten benötigen Sie mindestens
r.Next(1024)
. Wenn Sie das Array größer machen (4 oder 5),r.Next(1024)
reicht es nicht einmal aus. Ich bin kein Experte für Mischen und Mathematik, aber ich denke, dass Sie für jedes zusätzliche Bit der Länge des Arrays 2 zusätzliche Bits des Maximalwerts benötigen (da das Geburtstagsparadoxon mit dem sqrt (numvalues) verbunden ist) Wenn der Maximalwert 2 ^ 31 ist, sollten Sie in der Lage sein, Arrays mit bis zu 2 ^ 12/2 ^ 13 Bits (4096-8192 Elemente) zu sortieren.quelle
Es ist wahrscheinlich für die meisten Zwecke in Ordnung und erzeugt fast immer eine wirklich zufällige Verteilung (außer wenn Random.Next () zwei identische zufällige ganze Zahlen erzeugt).
Es funktioniert, indem jedem Element der Reihe eine zufällige Ganzzahl zugewiesen wird und die Reihenfolge dann nach diesen Ganzzahlen geordnet wird.
Dies ist für 99,9% der Anwendungen völlig akzeptabel (es sei denn, Sie müssen den obigen Randfall unbedingt bearbeiten). Außerdem ist der Einwand von skeet gegen seine Laufzeit gültig. Wenn Sie also eine lange Liste mischen, möchten Sie sie möglicherweise nicht verwenden.
quelle
Dies ist schon oft vorgekommen. Suche nach Fisher-Yates auf StackOverflow.
Hier ist ein C # -Codebeispiel, das ich für diesen Algorithmus geschrieben habe. Sie können es bei Bedarf auch für einen anderen Typ parametrisieren.
quelle
Random
diese statische Variable nicht verwenden - sieRandom
ist nicht threadsicher. Siehe csharpindepth.com/Articles/Chapter12/Random.aspxRandom
ist ein Schmerz zu verwenden, wie in meinem Artikel erwähnt.Scheint ein guter Mischalgorithmus zu sein, wenn Sie sich keine Sorgen um die Leistung machen. Das einzige Problem, auf das ich hinweisen möchte, ist, dass das Verhalten nicht steuerbar ist, sodass Sie möglicherweise Schwierigkeiten haben, es zu testen.
Eine mögliche Option besteht darin, einen Startwert als Parameter an den Zufallszahlengenerator (oder den Zufallsgenerator als Parameter) zu übergeben, damit Sie mehr Kontrolle haben und ihn einfacher testen können.
quelle
Ich fand Jon Skeets Antwort völlig zufriedenstellend, aber der Robo-Scanner meines Kunden meldet jeden Fall
Random
als Sicherheitslücke. Also habe ich es ausgetauschtSystem.Security.Cryptography.RNGCryptoServiceProvider
. Als Bonus wird das erwähnte Thread-Sicherheitsproblem behoben. Auf der anderen Seite wurdeRNGCryptoServiceProvider
als 300x langsamer als mit gemessen gemessenRandom
.Verwendung:
Methode:
quelle
Suchen Sie einen Algorithmus? Du kannst meine
ShuffleList
Klasse benutzen :Verwenden Sie es dann folgendermaßen:
Wie funktioniert es?
Nehmen wir eine erste sortierte Liste der 5 ersten ganzen Zahlen :
{ 0, 1, 2, 3, 4 }
.Die Methode beginnt mit dem Zählen der Anzahl der Elemente und ruft sie auf
count
. Dann, mitcount
auf jedem Schritt abnimmt, nimmt es eine Zufallszahl zwischen0
undcount
und bewegt ihn an das Ende der Liste.Im folgenden schrittweisen Beispiel sind die Elemente, die verschoben werden könnten, kursiv und das ausgewählte Element fett gedruckt :
0 1 2 3 4
0 1 2 3 4
0 1 2 4 3
0 1 2 4 3
1 2 4 3 0
1 2 4 3 0
1 2 3 0 4
1 2 3 0 4
2 3 0 4 1
2 3 0 4 1
3 0 4 1 2
quelle
Dieser Algorithmus mischt, indem er für jeden Wert in einer Liste einen neuen Zufallswert generiert und die Liste dann nach diesen Zufallswerten sortiert. Stellen Sie sich vor, Sie fügen einer In-Memory-Tabelle eine neue Spalte hinzu, füllen sie dann mit GUIDs und sortieren nach dieser Spalte. Sieht für mich nach einem effizienten Weg aus (besonders mit dem Lambda-Zucker!)
quelle
Etwas unabhängig, aber hier ist eine interessante Methode (die, obwohl sie wirklich übertrieben ist, WIRKLICH implementiert wurde) für die wirklich zufällige Erzeugung von Würfeln!
Dice-O-Matic
Der Grund, warum ich dies hier poste, ist, dass er einige interessante Punkte darüber macht, wie seine Benutzer auf die Idee reagiert haben, Algorithmen zum Mischen über tatsächliche Würfel zu verwenden. In der realen Welt ist eine solche Lösung natürlich nur für die wirklich extremen Bereiche des Spektrums geeignet, in denen Zufälligkeit einen so großen Einfluss hat und möglicherweise den Einfluss auf das Geld hat;).
quelle
Ich würde sagen, dass viele Antworten hier wie "Dieser Algorithmus mischt, indem er für jeden Wert in einer Liste einen neuen Zufallswert generiert und dann die Liste nach diesen Zufallswerten ordnet" möglicherweise sehr falsch sind!
Ich würde denken, dass dies NICHT jedem Element der Quellensammlung einen zufälligen Wert zuweist. Stattdessen könnte es einen Sortieralgorithmus geben, der wie Quicksort läuft und eine Vergleichsfunktion ungefähr n log n-mal aufruft. Einige Sorten erwarten wirklich, dass diese Vergleichsfunktion stabil ist und immer das gleiche Ergebnis liefert!
Könnte es nicht sein, dass der IEnumerableSorter eine Vergleichsfunktion für jeden Algorithmusschritt von z. B. Quicksort aufruft und jedes Mal die Funktion
x => r.Next()
für beide Parameter aufruft, ohne diese zwischenzuspeichern!In diesem Fall könnten Sie den Sortieralgorithmus wirklich durcheinander bringen und ihn viel schlechter machen als die Erwartungen, auf denen der Algorithmus aufgebaut ist. Natürlich wird es irgendwann stabil und gibt etwas zurück.
Ich könnte es später überprüfen, indem ich die Debugging-Ausgabe in eine neue "Weiter" -Funktion setze, um zu sehen, was passiert. In Reflector konnte ich nicht sofort herausfinden, wie es funktioniert.
quelle
Startzeit für die Ausführung von Code mit allen Threads löschen und jeden neuen Test zwischenspeichern,
Erster erfolgloser Code. Es läuft auf LINQPad. Wenn Sie folgen, um diesen Code zu testen.
list.OrderBy (x => r.Next ()) verwendet 38,6528 ms
list.OrderBy (x => Guid.NewGuid ()) verwendet 36,7634 ms (wird von MSDN empfohlen.)
das nach dem zweiten Mal verwenden beide gleichzeitig.
BEARBEITEN: TESTCODE auf Intel Core i7 [email protected], Ram 8 GB DDR3 @ 1600, HDD SATA 5200 U / min mit [Daten: www.dropbox.com/s/pbtmh5s9lw285kp/data]
Ergebnisbeschreibung: https://www.dropbox.com/s/9dw9wl259dfs04g/ResultDescription.PNG
Ergebnisstatistik: https://www.dropbox.com/s/ewq5ybtsvesme4d/ResultStat.PNG
Schlussfolgerung:
Angenommen, LINQ OrderBy (r.Next ()) und OrderBy (Guid.NewGuid ()) sind in der ersten Lösung nicht schlechter als die benutzerdefinierte Zufallsmethode.
Antwort: Sie sind Widerspruch.
quelle