¿Cuál es la mejor manera de aleatorizar el orden de una lista genérica en C #? Tengo un conjunto finito de 75 números en una lista a la que me gustaría asignar un orden aleatorio, para poder dibujarlos para una aplicación de tipo lotería.
c#
generic-list
mirezus
fuente
fuente
Respuestas:
Mezcle cualquiera
(I)List
con un método de extensión basado en la mezcla aleatoria de Fisher-Yates :Uso:
El código anterior utiliza el método System.Random muy criticado para seleccionar candidatos de intercambio. Es rápido pero no tan aleatorio como debería ser. Si necesita una mejor calidad de aleatoriedad en sus barajas, use el generador de números aleatorios en System.Security.Cryptography de esta manera:
Una comparación simple está disponible en este blog (WayBack Machine).
Editar: desde que escribí esta respuesta hace un par de años, muchas personas me han comentado o escrito para señalar el gran error tonto en mi comparación. Por supuesto que tienen razón. No hay nada de malo en System.Random si se usa de la forma prevista. En mi primer ejemplo anterior, ejemplifico la variable rng dentro del método Shuffle, que está pidiendo problemas si el método se llamará repetidamente. A continuación se muestra un ejemplo completo y fijo basado en un comentario realmente útil recibido hoy de @weston aquí en SO.
Program.cs:
fuente
Random rng = new Random();
unstatic
resolvería el problema en la publicación de comparación. Como cada llamada subsiguiente seguiría de las llamadas anteriores, el último resultado aleatorio.Si solo necesitamos mezclar elementos en un orden completamente aleatorio (solo para mezclar los elementos en una lista), prefiero este código simple pero efectivo que ordena elementos por guid ...
fuente
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.csNewGuid
solo garantiza que te da un GUID único. No garantiza la aleatoriedad. Si está utilizando un GUID para un propósito que no sea crear un valor único , lo está haciendo mal.Estoy un poco sorprendido por todas las versiones torpes de este algoritmo simple aquí. Fisher-Yates (o Knuth shuffle) es un poco complicado pero muy compacto. ¿Por qué es complicado? Porque debe prestar atención a si su generador de números aleatorios
r(a,b)
devuelve valor dondeb
es inclusivo o exclusivo. También he editado la descripción de Wikipedia para que la gente no siga ciegamente el pseudocódigo allí y cree errores difíciles de detectar. Para .Net,Random.Next(a,b)
devuelve un número exclusivo,b
sin más preámbulos, así es como se puede implementar en C # /. Net:Prueba este código .
fuente
i = list.Count - 1
, es decir, la última iteración,rnd.Next(i, list.Count)
te devolverá i. Por lo tanto, necesitai < list.Count -1
como condición de bucle. Bueno, no lo 'necesita', pero ahorra 1 iteración;)Método de extensión para IEnumerable:
fuente
OrderBy
utiliza una variante QuickSort para ordenar los elementos por sus teclas (aparentemente aleatorias). El rendimiento de QuickSort es O (N log N) ; en contraste, un shuffle de Fisher-Yates es O (N) . Para una colección de 75 elementos, esto puede no ser un gran problema, pero la diferencia será pronunciada para colecciones más grandes.Random.Next()
puede producir una distribución de valores razonablemente pseudoaleatoria, pero no garantiza que los valores sean únicos. La probabilidad de duplicar claves crece (no linealmente) con N hasta que alcanza la certeza cuando N alcanza 2 ^ 32 + 1. ElOrderBy
QuickSort es un tipo estable ; por lo tanto, si a varios elementos se les asigna el mismo valor de índice pseudoaleatorio, entonces su orden en la secuencia de salida será el mismo que en la secuencia de entrada; por lo tanto, se introduce un sesgo en el "shuffle".La idea es obtener un objeto anónimo con un artículo y un orden aleatorio y luego reordenar los artículos por este orden y valor de retorno:
fuente
fuente
var listCopy = list.ToList()
evitar que todos los elementos salgan de la lista entrante? Realmente no entiendo por qué querrías mutar esas listas para vaciarlas.EDITAR El
RemoveAt
es una debilidad en mi versión anterior. Esta solución supera eso.Tenga en cuenta lo opcional
Random generator
, si la implementación del marco base deRandom
no es segura para subprocesos o criptográficamente lo suficientemente fuerte para sus necesidades, puede inyectar su implementación en la operación.En
Random
esta respuesta se puede encontrar una implementación adecuada para una implementación criptográfica segura para subprocesos .He aquí una idea, extienda IList de una manera (con suerte) eficiente.fuente
GetNext
oNext
?Puede lograrlo utilizando este sencillo método de extensión
y puedes usarlo haciendo lo siguiente
fuente
Random
instancia de clase fuera de la función como unastatic
variable. De lo contrario, puede obtener la misma semilla de aleatorización del temporizador si se llama en una sucesión rápida.Este es mi método preferido de barajar cuando es deseable no modificar el original. Es una variante del algoritmo "de adentro hacia afuera" de Fisher-Yates que funciona en cualquier secuencia enumerable (
source
no es necesario conocer la longitud desde el principio).Este algoritmo también se puede implementar mediante la asignación de un rango de
0
alength - 1
y agotando al azar los índices mediante el canje el índice elegido al azar con el último índice hasta que todos los índices se han elegido exactamente una vez. Este código anterior logra exactamente lo mismo pero sin la asignación adicional. Lo cual es bastante bueno.Con respecto a la
Random
clase, es un generador de números de propósito general (y si estuviera ejecutando una lotería, consideraría usar algo diferente). También se basa en un valor inicial basado en el tiempo de forma predeterminada. Un pequeño alivio del problema es sembrar laRandom
clase con elRNGCryptoServiceProvider
o podría usarloRNGCryptoServiceProvider
en un método similar a este (vea a continuación) para generar valores aleatorios de punto flotante doble elegidos uniformemente, pero ejecutar una lotería requiere bastante comprensión de la aleatoriedad y la naturaleza de La fuente de aleatoriedad.El punto de generar un doble aleatorio (entre 0 y 1 exclusivamente) es usar para escalar a una solución entera. Si necesita elegir algo de una lista basada en un doble aleatorio
x
que siempre será0 <= x && x < 1
sencillo.¡Disfrutar!
fuente
Si no le importa usar dos
Lists
, entonces esta es probablemente la forma más fácil de hacerlo, pero probablemente no sea la más eficiente o impredecible:fuente
Si tiene un número fijo (75), puede crear una matriz con 75 elementos, luego enumerar su lista, moviendo los elementos a posiciones aleatorias en la matriz. Puede generar el mapeo del número de lista al índice de la matriz utilizando la combinación aleatoria de Fisher-Yates .
fuente
Usualmente uso:
fuente
fuente
Aquí hay un barajador eficiente que devuelve una matriz de bytes de valores barajados. Nunca baraja más de lo necesario. Se puede reiniciar desde donde lo dejó anteriormente. Mi implementación real (no se muestra) es un componente MEF que permite un barajador de reemplazo especificado por el usuario.
``
fuente
Aquí hay una forma segura de subprocesos para hacer esto:
fuente
fuente
Una modificación simple de la respuesta aceptada que devuelve una nueva lista en lugar de funcionar en el lugar, y acepta el más general
IEnumerable<T>
como lo hacen muchos otros métodos de Linq.fuente
He encontrado una solución interesante en línea.
Cortesía: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var barajado = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
fuente
Publicación antigua con seguridad, pero solo uso un GUID.
Un GUID siempre es único, y dado que se regenera cada vez que el resultado cambia cada vez.
fuente
Un enfoque muy simple para este tipo de problema es usar un número de intercambio de elementos aleatorios en la lista.
En pseudocódigo esto se vería así:
fuente