Ce n'est pas une façon de mélanger que j'aime, principalement parce que c'est O (n log n) sans raison valable quand il est facile d'implémenter un mélange O (n). Le code de la question "fonctionne" en donnant essentiellement un nombre aléatoire (espérons-le unique!) À chaque élément, puis en ordonnant les éléments en fonction de ce nombre.
Je préfère la variante de Durstenfield du shuffle Fisher-Yates qui échange des éléments.
La mise en œuvre d'une Shuffle
méthode d'extension simple consisterait essentiellement à appeler ToList
ou ToArray
sur l'entrée puis à utiliser une implémentation existante de Fisher-Yates. (Passez le Random
comme paramètre pour rendre la vie généralement plus agréable.) Il y a beaucoup d'implémentations autour ... J'en ai probablement une dans une réponse quelque part.
La bonne chose à propos d'une telle méthode d'extension est qu'il serait alors très clair pour le lecteur ce que vous essayez réellement de faire.
EDIT: Voici une implémentation simple (pas de vérification d'erreur!):
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;
}
}
EDIT: Les commentaires sur les performances ci-dessous m'ont rappelé que nous pouvons réellement renvoyer les éléments lorsque nous les mélangeons:
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];
}
}
Cela ne fera plus que le travail nécessaire.
Notez que dans les deux cas, vous devez faire attention à l'instance que Random
vous utilisez comme:
- La création de deux instances de
Random
à peu près au même moment donnera la même séquence de nombres aléatoires (lorsqu'elle est utilisée de la même manière)
Random
n'est pas thread-safe.
J'ai un articleRandom
qui donne plus de détails sur ces problèmes et propose des solutions.
source.ToArray();
que vous devez avoirusing System.Linq;
dans le même fichier. Si vous ne le faites pas, vous obtenez cette erreur:'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?)
Ceci est basé sur la réponse de Jon Skeet .
Dans cette réponse, le tableau est mélangé, puis renvoyé en utilisant
yield
. Le résultat net est que le tableau est conservé en mémoire pendant la durée de foreach, ainsi que les objets nécessaires à l'itération, et pourtant le coût est tout au début - le rendement est fondamentalement une boucle vide.Cet algorithme est beaucoup utilisé dans les jeux, où les trois premiers éléments sont choisis, et les autres ne seront nécessaires que plus tard, voire pas du tout. Ma suggestion concerne
yield
les numéros dès qu'ils sont échangés. Cela réduira le coût de démarrage, tout en maintenant le coût d'itération à O (1) (essentiellement 5 opérations par itération). Le coût total resterait le même, mais le brassage lui-même serait plus rapide. Dans les cas où cela est appelé carcollection.Shuffle().ToArray()
cela ne fera théoriquement aucune différence, mais dans les cas d'utilisation mentionnés ci-dessus, cela accélérera le démarrage. En outre, cela rendrait l'algorithme utile dans les cas où vous n'avez besoin que de quelques éléments uniques. Par exemple, si vous devez retirer trois cartes d'un jeu de 52, vous pouvez appelerdeck.Shuffle().Take(3)
et seuls trois échanges auront lieu (bien que l'ensemble du tableau devrait d'abord être copié).la source
À partir de cette citation de Skeet:
Je vais continuer à expliquer un peu la raison de l' unique, espérons-le!
Maintenant, à partir de Enumerable.OrderBy :
C'est très important! Que se passe-t-il si deux éléments "reçoivent" le même nombre aléatoire? Il arrive qu'ils restent dans le même ordre qu'ils sont dans le tableau. Maintenant, quelle est la possibilité que cela se produise? Il est difficile de calculer exactement, mais il y a le problème d'anniversaire qui est exactement ce problème.
Maintenant, est-ce réel? Est-ce vrai?
Comme toujours, en cas de doute, écrivez quelques lignes de programme: http://pastebin.com/5CDnUxPG
Ce petit bloc de code mélange un tableau de 3 éléments un certain nombre de fois en utilisant l'algorithme de Fisher-Yates fait à l'envers, l'algorithme de Fisher-Yates fait en avant (dans la page wiki il y a deux algorithmes de pseudo-code ... Ils produisent l'équivalent résultats, mais l'un est fait du premier au dernier élément, tandis que l'autre est fait du dernier au premier élément), le mauvais algorithme naïf de http://blog.codinghorror.com/the-danger-of-naivete/ et en utilisant le
.OrderBy(x => r.Next())
et le.OrderBy(x => r.Next(someValue))
.Maintenant, Random.Next est
donc c'est équivalent à
Pour tester si ce problème existe, nous pourrions agrandir le tableau (quelque chose de très lent) ou simplement réduire la valeur maximale du générateur de nombres aléatoires (ce
int.MaxValue
n'est pas un nombre "spécial" ... C'est simplement un très grand nombre). En fin de compte, si l'algorithme n'est pas biaisé par la stabilité duOrderBy
, alors toute plage de valeurs devrait donner le même résultat.Le programme teste ensuite certaines valeurs, comprises entre 1 et 4096. En regardant le résultat, il est assez clair que pour des valeurs faibles (<128), l'algorithme est très biaisé (4-8%). Avec 3 valeurs dont vous avez besoin au moins
r.Next(1024)
. Si vous agrandissez le tableau (4 ou 5), celar.Next(1024)
ne suffit même pas. Je ne suis pas un expert en shuffling et en maths, mais je pense que pour chaque bit supplémentaire de longueur du tableau, vous avez besoin de 2 bits supplémentaires de valeur maximale (car le paradoxe d'anniversaire est connecté au sqrt (numvalues)), donc que si la valeur maximale est 2 ^ 31, je dirai que vous devriez pouvoir trier des tableaux jusqu'à 2 ^ 12/2 ^ 13 bits (4096-8192 éléments)la source
C'est probablement correct dans la plupart des cas, et presque toujours, cela génère une distribution vraiment aléatoire (sauf lorsque Random.Next () produit deux entiers aléatoires identiques).
Il fonctionne en attribuant à chaque élément de la série un entier aléatoire, puis en ordonnant la séquence par ces entiers.
C'est totalement acceptable pour 99,9% des applications (sauf si vous devez absolument gérer le cas de bord ci-dessus). De plus, l'objection de skeet à son exécution est valide, donc si vous mélangez une longue liste, vous ne voudrez peut-être pas l'utiliser.
la source
Cela s'est produit plusieurs fois auparavant. Recherchez Fisher-Yates sur StackOverflow.
Voici un exemple de code C # que j'ai écrit pour cet algorithme. Vous pouvez le paramétrer sur un autre type, si vous préférez.
la source
Random
comme une variable statique comme celle-ci -Random
n'est pas thread-safe. Voir csharpindepth.com/Articles/Chapter12/Random.aspxRandom
c'est une douleur à utiliser, comme indiqué dans mon article.Cela semble être un bon algorithme de mélange, si vous n'êtes pas trop préoccupé par les performances. Le seul problème que je signalerais est que son comportement n'est pas contrôlable, vous pourriez donc avoir du mal à le tester.
Une option possible est d'avoir une graine à transmettre en tant que paramètre au générateur de nombres aléatoires (ou au générateur aléatoire en tant que paramètre), afin que vous puissiez avoir plus de contrôle et le tester plus facilement.
la source
J'ai trouvé la réponse de Jon Skeet entièrement satisfaisante, mais le robot-scanner de mon client signalera toute instance de
Random
comme un défaut de sécurité. Alors je l'ai échangé contreSystem.Security.Cryptography.RNGCryptoServiceProvider
. En prime, il corrige ce problème de sécurité des threads qui a été mentionné. D'autre part,RNGCryptoServiceProvider
a été mesuré comme 300 fois plus lent que l'utilisationRandom
.Usage:
Méthode:
la source
Vous recherchez un algorithme? Vous pouvez utiliser ma
ShuffleList
classe:Ensuite, utilisez-le comme ceci:
Comment ça marche?
Prenons une première liste triée des 5 premiers entiers:
{ 0, 1, 2, 3, 4 }
.La méthode commence par compter le nombre d'éléments et l'appelle
count
. Ensuite, encount
diminuant à chaque étape, il prend un nombre aléatoire entre0
etcount
et le déplace à la fin de la liste.Dans l'exemple pas à pas suivant, les éléments pouvant être déplacés sont en italique , l'élément sélectionné est en gras :
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
la source
Cet algorithme mélange en générant une nouvelle valeur aléatoire pour chaque valeur d'une liste, puis en ordonnant la liste en fonction de ces valeurs aléatoires. Considérez cela comme l'ajout d'une nouvelle colonne à une table en mémoire, puis en le remplissant avec des GUID, puis en le triant par cette colonne. Cela me semble être un moyen efficace (surtout avec le sucre lambda!)
la source
Un peu sans rapport, mais voici une méthode intéressante (qui même si elle est vraiment excessive, a VRAIMENT été implémentée) pour une génération vraiment aléatoire de lancers de dés!
Dice-O-Matic
La raison pour laquelle je publie ceci ici, c'est qu'il fait des remarques intéressantes sur la façon dont ses utilisateurs ont réagi à l'idée d'utiliser des algorithmes pour mélanger, sur de vrais dés. Bien sûr, dans le monde réel, une telle solution ne concerne que les extrémités vraiment extrêmes du spectre où le hasard a un si grand impact et peut-être que l'impact affecte l'argent;).
la source
Je dirais que de nombreuses réponses ici comme "Cet algorithme mélange en générant une nouvelle valeur aléatoire pour chaque valeur d'une liste, puis en ordonnant la liste par ces valeurs aléatoires" pourraient être très erronées!
Je pense que cela n'attribue pas une valeur aléatoire à chaque élément de la collection source. Au lieu de cela, il pourrait y avoir un algorithme de tri fonctionnant comme Quicksort qui appellerait une fonction de comparaison environ n fois n log. Certains types d'algortihm s'attendent vraiment à ce que cette fonction de comparaison soit stable et renvoie toujours le même résultat!
Ne pourrait-il pas être que le IEnumerableSorter appelle une fonction de comparaison pour chaque étape de l'algorithme, par exemple quicksort et à chaque fois appelle la fonction
x => r.Next()
pour les deux paramètres sans les mettre en cache!Dans ce cas, vous pourriez vraiment gâcher l'algorithme de tri et le rendre bien pire que les attentes sur lesquelles l'algorithme est construit. Bien sûr, il finira par devenir stable et retourner quelque chose.
Je pourrais le vérifier plus tard en mettant la sortie de débogage dans une nouvelle fonction "Next" pour voir ce qui se passe. Dans Reflector, je n'ai pas pu découvrir immédiatement comment cela fonctionne.
la source
Heure de démarrage pour exécuter sur le code avec effacer tous les threads et mettre en cache chaque nouveau test,
Premier code infructueux. Il fonctionne sur LINQPad. Si vous suivez pour tester ce code.
list.OrderBy (x => r.Next ()) utilise 38,6528 ms
list.OrderBy (x => Guid.NewGuid ()) utilise 36,7634 ms (il est recommandé par MSDN.)
après la deuxième fois, les deux utilisent en même temps.
EDIT: TEST CODE sur Intel Core i7 [email protected], Ram 8 GB DDR3 @ 1600, HDD SATA 5200 rpm avec [Données: www.dropbox.com/s/pbtmh5s9lw285kp/data]
Description du résultat: https://www.dropbox.com/s/9dw9wl259dfs04g/ResultDescription.PNG
Statistiques du résultat: https://www.dropbox.com/s/ewq5ybtsvesme4d/ResultStat.PNG
Conclusion:
Supposons: LINQ OrderBy (r.Next ()) et OrderBy (Guid.NewGuid ()) ne sont pas pires que la méthode de lecture aléatoire définie par l'utilisateur dans la première solution.
Réponse: Ce sont des contradictions.
la source