La mejor manera de aleatorizar una matriz con .NET
141
¿Cuál es la mejor manera de aleatorizar una matriz de cadenas con .NET? Mi matriz contiene aproximadamente 500 cadenas y me gustaría crear una nueva Arraycon las mismas cadenas pero en un orden aleatorio.
Usando el paquete MedallionRandom NuGet, esto es solo myArray.Shuffled().ToArray()(o myArray.Shuffle()si desea mutar la matriz actual)
ChaseMedallion
Respuestas:
171
Si está en .NET 3.5, puede usar la siguiente frescura IEnumerable (VB.NET, no C #, pero la idea debería ser clara ...):
Random rnd=newRandom();string[]MyRandomArray=MyArray.OrderBy(x => rnd.Next()).ToArray();
Editar: OK y aquí está el código VB.NET correspondiente:
Dim rnd AsNewSystem.RandomDimMyRandomArray=MyArray.OrderBy(Function() rnd.Next()).ToArray()
Segunda edición, en respuesta a los comentarios de que System.Random "no es seguro para subprocesos" y "solo es adecuado para aplicaciones de juguete" debido a que devuelve una secuencia basada en el tiempo: como se usa en mi ejemplo, Random () es perfectamente seguro para subprocesos, a menos que está permitiendo que se vuelva a ingresar la rutina en la que aleatoriza la matriz, en cuyo caso necesitará algo así lock (MyRandomArray)para no corromper sus datos, lo que protegerárnd .
Además, debe entenderse bien que System.Random como fuente de entropía no es muy fuerte. Como se señala en la documentación de MSDN , debe usar algo derivado System.Security.Cryptography.RandomNumberGeneratorsi está haciendo algo relacionado con la seguridad. Por ejemplo:
dos notas: 1) System.Random no es seguro para subprocesos (se le ha advertido) y 2) System.Random se basa en el tiempo, por lo que si usa este código en un sistema muy concurrente, es posible que dos solicitudes obtengan mismo valor (es decir, en webapps)
therealhoff
2
Solo para aclarar lo anterior, System.Random se iniciará utilizando la hora actual, por lo que dos instancias creadas simultáneamente generarán la misma secuencia "aleatoria" ... System.Random solo debe usarse en aplicaciones de juguete
tanto, el
8
Además, este algoritmo es O (n log n) y está sesgado por el algoritmo Qsort. Vea mi respuesta para una solución imparcial O (n).
Matt Howells
9
A menos que OrderByguarde en caché las claves de clasificación internamente, esto también tiene el problema de violar la propiedad transitiva de las comparaciones ordenadas. Si alguna vez hay una verificación de modo de depuración que OrderByproduce resultados correctos, en teoría podría arrojar una excepción.
La siguiente implementación utiliza el algoritmo de Fisher-Yates, también conocido como Knuth Shuffle. Se ejecuta en tiempo O (n) y se baraja en su lugar, por lo que tiene un mejor rendimiento que la técnica de "ordenar por azar", aunque tiene más líneas de código. Vea aquí algunas medidas comparativas de rendimiento. He usado System.Random, que está bien para fines no criptográficos. *
staticclassRandomExtensions{publicstaticvoidShuffle<T>(thisRandom rng, T[]array){int n =array.Length;while(n >1){int k = rng.Next(n--);
T temp =array[n];array[n]=array[k];array[k]= temp;}}}
Uso:
vararray=newint[]{1,2,3,4};var rng =newRandom();
rng.Shuffle(array);
rng.Shuffle(array);// different order from first call to Shuffle
* Para matrices más largas, para que el número (extremadamente grande) de permutaciones sea igualmente probable, sería necesario ejecutar un generador de números pseudoaleatorios (PRNG) a través de muchas iteraciones para cada intercambio para producir suficiente entropía. ¡Para una matriz de 500 elementos solo una fracción muy pequeña de los 500 posibles! se podrán obtener permutaciones utilizando un PRNG. Sin embargo, el algoritmo de Fisher-Yates es imparcial y, por lo tanto, la combinación será tan buena como el RNG que use.
¿No sería mejor cambiar los parámetros y hacer el uso como array.Shuffle(new Random());...?
Ken Kin
Puede simplificar el intercambio utilizando Tuplas a partir del marco 4.0 -> (matriz [n], matriz [k]) = (matriz [k], matriz [n]);
dynamichael
@ Ken Kin: No, esto sería malo. La razón es que new Random()se inicializa con un valor inicial basado en la hora actual del sistema, que solo se actualiza cada ~ 16 ms.
Matt Howells
En algunas pruebas rápidas de esto frente a la solución removeAt de la lista, hay una pequeña diferencia en 999 elementos. La diferencia se vuelve drástica en 99999 entradas aleatorias, con esta solución a 3 ms y la otra a 1810 ms.
galamdring
18
Estás buscando un algoritmo de barajado, ¿verdad?
De acuerdo, hay dos formas de hacer esto: los inteligentes, pero la gente, siempre parece que lo malinterpretan y se equivocan, así que tal vez no sea tan inteligente después de todo y la manera tonta como rocas pero a quién le importa porque funciona.
Manera tonta
Cree un duplicado de su primera matriz, pero etiquete cada cadena con un número aleatorio.
Ordene la matriz duplicada con respecto al número aleatorio.
Este algoritmo funciona bien, pero asegúrese de que es poco probable que su generador de números aleatorios etiquete dos cadenas con el mismo número. Debido a la llamada paradoja del cumpleaños , esto ocurre con más frecuencia de lo que cabría esperar. Su complejidad temporal es O ( n log n ).
Manera inteligente
Describiré esto como un algoritmo recursivo:
Para barajar una matriz de tamaño n (índices en el rango [0 .. n -1]):
si n = 0
hacer nada
si n > 0
(paso recursivo) baraja el primer n -1 elementos de la matriz
elija un índice aleatorio, x , en el rango [0 .. n -1]
intercambie el elemento en el índice n -1 con el elemento en el índice x
El equivalente iterativo es caminar un iterador a través de la matriz, intercambiando elementos aleatorios a medida que avanza, pero tenga en cuenta que no puede intercambiar con un elemento después del que señala el iterador. Este es un error muy común y conduce a una mezcla aleatoria sesgada.
Este algoritmo es simple pero no eficiente, O (N 2 ). Todos los algoritmos de "ordenar por" suelen ser O (N log N). Probablemente no haga una diferencia por debajo de cientos de miles de elementos, pero lo haría para grandes listas.
var stringlist =...// add your values to stringlistvar r =newRandom();var res =newList<string>(stringlist.Count);while(stringlist.Count>0){var i = r.Next(stringlist.Count);
res.Add(stringlist[i]);
stringlist.RemoveAt(i);}
La razón por la cual es O (N 2 ) es sutil: List.RemoveAt () es una operación O (N) a menos que elimine en orden desde el final.
Esto tiene el mismo efecto que un knuth shuffle, pero no es tan eficiente, ya que implica despoblar una lista y repoblar otra. Intercambiar elementos en su lugar sería una mejor solución.
Nick Johnson
1
Encuentro esto elegante y fácilmente comprensible y con 500 cuerdas no hace la
menor
4
También puede hacer un método de extensión de Matt Howells. Ejemplo.
namespace System{publicstaticclassMSSystemExtenstions{privatestaticRandom rng =newRandom();publicstaticvoidShuffle<T>(this T[]array){
rng =newRandom();int n =array.Length;while(n >1){int k = rng.Next(n);
n--;
T temp =array[n];array[n]=array[k];array[k]= temp;}}}}
por qué estás recreando cada llamada al método ... Lo declaras a nivel de clase pero lo usas como local ...
Yaron
1
Aleatorizar la matriz es intensivo ya que tienes que desplazarte alrededor de un montón de cadenas. ¿Por qué no simplemente leer al azar de la matriz? En el peor de los casos, incluso podría crear una clase de contenedor con getNextString (). Si realmente necesita crear una matriz aleatoria, entonces podría hacer algo como
for i =0-> i=array.length *5
swap two strings in random places
¡Es probable que una lectura aleatoria de la matriz golpee algunos elementos varias veces y se pierda otros!
Ray Hayes
El algoritmo aleatorio está roto. Tendría que hacer que sus 5 arbitrarios sean muy altos antes de que su barajado sea imparcial.
Pitarou
Haga una matriz de los índices (enteros). Baraja los índices. Simplemente use los índices en ese orden aleatorio. No hay duplicados, no se barajan las referencias de cadenas en la memoria (que pueden desencadenar internados y qué no).
Christopher
1
Solo pensando en la parte superior de mi cabeza, podrías hacer esto:
publicstring[]Randomize(string[] input){List<string> inputList = input.ToList();string[] output =newstring[input.Length];Random randomizer =newRandom();int i =0;while(inputList.Count>0){int index = r.Next(inputList.Count);
output[i++]= inputList[index];
inputList.RemoveAt(index);}return(output);}
Genere una matriz de flotantes aleatorios o entradas de la misma longitud. Ordene esa matriz y realice los intercambios correspondientes en su matriz de destino.
Esto produce un tipo verdaderamente independiente.
Random r =newRandom();List<string>list=newList(originalArray);List<string> randomStrings =newList();while(list.Count>0){int i = r.Random(list.Count);
randomStrings.Add(list[i]);list.RemoveAt(i);}
Jacco, tu solución con un IComparer personalizado no es seguro. Las rutinas de clasificación requieren que el comparador cumpla con varios requisitos para funcionar correctamente. El primero de ellos es la consistencia. Si se llama al comparador en el mismo par de objetos, siempre debe devolver el mismo resultado. (la comparación también debe ser transitiva).
El incumplimiento de estos requisitos puede causar cualquier número de problemas en la rutina de clasificación, incluida la posibilidad de un bucle infinito.
Con respecto a las soluciones que asocian un valor numérico aleatorio con cada entrada y luego las clasifican por ese valor, esto genera un sesgo inherente en la salida porque cada vez que se asignan dos entradas al mismo valor numérico, la aleatoriedad de la salida se verá comprometida. (En una rutina de clasificación "estable", lo que sea primero en la entrada será primero en la salida. Array.Sort no es estable, pero aún existe un sesgo basado en la partición realizada por el algoritmo Quicksort).
Debe pensar un poco sobre el nivel de aleatoriedad que necesita. Si está ejecutando un sitio de póker donde necesita niveles criptográficos de aleatoriedad para protegerse contra un atacante determinado, tiene requisitos muy diferentes a los de alguien que solo quiere aleatorizar una lista de reproducción de canciones.
Para barajar la lista de canciones, no hay problema al usar un PRNG sembrado (como System.Random). Para un sitio de póker, ni siquiera es una opción y debes pensar en el problema mucho más difícil de lo que nadie te hará en stackoverflow. (el uso de un RNG criptográfico es solo el comienzo, debe asegurarse de que su algoritmo no introduzca un sesgo, que tenga suficientes fuentes de entropía y que no exponga ningún estado interno que pueda comprometer la aleatoriedad posterior).
Esta publicación ya ha sido bastante bien respondida: use una implementación de Durstenfeld de la mezcla de Fisher-Yates para obtener un resultado rápido e imparcial. Incluso se han publicado algunas implementaciones, aunque observo que algunas son realmente incorrectas.
Ok, esto es claramente un golpe de mi parte (se disculpa ...), pero a menudo uso un método bastante general y criptográficamente fuerte.
publicstaticclassEnumerableExtensions{staticreadonlyRNGCryptoServiceProviderRngCryptoServiceProvider=newRNGCryptoServiceProvider();publicstaticIEnumerable<T>Shuffle<T>(thisIEnumerable<T> enumerable){var randomIntegerBuffer =newbyte[4];Func<int> rand =()=>{RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);returnBitConverter.ToInt32(randomIntegerBuffer,0);};returnfrom item in enumerable
let rec =new{item, rnd = rand()}orderby rec.rnd
select rec.item;}}
Shuffle () es una extensión en cualquier IEnumerable, por lo que puede obtener, por ejemplo, números del 0 al 1000 en orden aleatorio en una lista con
Enumerable.Range(0,1000).Shuffle().ToList()
Este método tampoco dará sorpresas cuando se trata de ordenar, ya que el valor de clasificación se genera y recuerda exactamente una vez por elemento en la secuencia.
Para mí, parece que podría aumentar tanto la eficiencia como la legibilidad si en lugar de tratar de barajar una matriz declarando una segunda matriz, es mejor intentar convertirla en una lista, barajar y volver a una matriz:sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
myArray.Shuffled().ToArray()
(omyArray.Shuffle()
si desea mutar la matriz actual)Respuestas:
Si está en .NET 3.5, puede usar la siguiente frescura IEnumerable (VB.NET, no C #, pero la idea debería ser clara ...):
Editar: OK y aquí está el código VB.NET correspondiente:
Segunda edición, en respuesta a los comentarios de que System.Random "no es seguro para subprocesos" y "solo es adecuado para aplicaciones de juguete" debido a que devuelve una secuencia basada en el tiempo: como se usa en mi ejemplo, Random () es perfectamente seguro para subprocesos, a menos que está permitiendo que se vuelva a ingresar la rutina en la que aleatoriza la matriz, en cuyo caso necesitará algo así
lock (MyRandomArray)
para no corromper sus datos, lo que protegerárnd
.Además, debe entenderse bien que System.Random como fuente de entropía no es muy fuerte. Como se señala en la documentación de MSDN , debe usar algo derivado
System.Security.Cryptography.RandomNumberGenerator
si está haciendo algo relacionado con la seguridad. Por ejemplo:...
...
fuente
OrderBy
guarde en caché las claves de clasificación internamente, esto también tiene el problema de violar la propiedad transitiva de las comparaciones ordenadas. Si alguna vez hay una verificación de modo de depuración queOrderBy
produce resultados correctos, en teoría podría arrojar una excepción.La siguiente implementación utiliza el algoritmo de Fisher-Yates, también conocido como Knuth Shuffle. Se ejecuta en tiempo O (n) y se baraja en su lugar, por lo que tiene un mejor rendimiento que la técnica de "ordenar por azar", aunque tiene más líneas de código. Vea aquí algunas medidas comparativas de rendimiento. He usado System.Random, que está bien para fines no criptográficos. *
Uso:
* Para matrices más largas, para que el número (extremadamente grande) de permutaciones sea igualmente probable, sería necesario ejecutar un generador de números pseudoaleatorios (PRNG) a través de muchas iteraciones para cada intercambio para producir suficiente entropía. ¡Para una matriz de 500 elementos solo una fracción muy pequeña de los 500 posibles! se podrán obtener permutaciones utilizando un PRNG. Sin embargo, el algoritmo de Fisher-Yates es imparcial y, por lo tanto, la combinación será tan buena como el RNG que use.
fuente
array.Shuffle(new Random());
...?new Random()
se inicializa con un valor inicial basado en la hora actual del sistema, que solo se actualiza cada ~ 16 ms.Estás buscando un algoritmo de barajado, ¿verdad?
De acuerdo, hay dos formas de hacer esto: los inteligentes, pero la gente, siempre parece que lo malinterpretan y se equivocan, así que tal vez no sea tan inteligente después de todo y la manera tonta como rocas pero a quién le importa porque funciona.
Manera tonta
Este algoritmo funciona bien, pero asegúrese de que es poco probable que su generador de números aleatorios etiquete dos cadenas con el mismo número. Debido a la llamada paradoja del cumpleaños , esto ocurre con más frecuencia de lo que cabría esperar. Su complejidad temporal es O ( n log n ).
Manera inteligente
Describiré esto como un algoritmo recursivo:
El equivalente iterativo es caminar un iterador a través de la matriz, intercambiando elementos aleatorios a medida que avanza, pero tenga en cuenta que no puede intercambiar con un elemento después del que señala el iterador. Este es un error muy común y conduce a una mezcla aleatoria sesgada.
La complejidad del tiempo es O ( n ).
fuente
Este algoritmo es simple pero no eficiente, O (N 2 ). Todos los algoritmos de "ordenar por" suelen ser O (N log N). Probablemente no haga una diferencia por debajo de cientos de miles de elementos, pero lo haría para grandes listas.
La razón por la cual es O (N 2 ) es sutil: List.RemoveAt () es una operación O (N) a menos que elimine en orden desde el final.
fuente
También puede hacer un método de extensión de Matt Howells. Ejemplo.
Entonces puedes usarlo como:
fuente
Aleatorizar la matriz es intensivo ya que tienes que desplazarte alrededor de un montón de cadenas. ¿Por qué no simplemente leer al azar de la matriz? En el peor de los casos, incluso podría crear una clase de contenedor con getNextString (). Si realmente necesita crear una matriz aleatoria, entonces podría hacer algo como
El * 5 es arbitrario.
fuente
Solo pensando en la parte superior de mi cabeza, podrías hacer esto:
fuente
Genere una matriz de flotantes aleatorios o entradas de la misma longitud. Ordene esa matriz y realice los intercambios correspondientes en su matriz de destino.
Esto produce un tipo verdaderamente independiente.
fuente
fuente
Jacco, tu solución con un IComparer personalizado no es seguro. Las rutinas de clasificación requieren que el comparador cumpla con varios requisitos para funcionar correctamente. El primero de ellos es la consistencia. Si se llama al comparador en el mismo par de objetos, siempre debe devolver el mismo resultado. (la comparación también debe ser transitiva).
El incumplimiento de estos requisitos puede causar cualquier número de problemas en la rutina de clasificación, incluida la posibilidad de un bucle infinito.
Con respecto a las soluciones que asocian un valor numérico aleatorio con cada entrada y luego las clasifican por ese valor, esto genera un sesgo inherente en la salida porque cada vez que se asignan dos entradas al mismo valor numérico, la aleatoriedad de la salida se verá comprometida. (En una rutina de clasificación "estable", lo que sea primero en la entrada será primero en la salida. Array.Sort no es estable, pero aún existe un sesgo basado en la partición realizada por el algoritmo Quicksort).
Debe pensar un poco sobre el nivel de aleatoriedad que necesita. Si está ejecutando un sitio de póker donde necesita niveles criptográficos de aleatoriedad para protegerse contra un atacante determinado, tiene requisitos muy diferentes a los de alguien que solo quiere aleatorizar una lista de reproducción de canciones.
Para barajar la lista de canciones, no hay problema al usar un PRNG sembrado (como System.Random). Para un sitio de póker, ni siquiera es una opción y debes pensar en el problema mucho más difícil de lo que nadie te hará en stackoverflow. (el uso de un RNG criptográfico es solo el comienzo, debe asegurarse de que su algoritmo no introduzca un sesgo, que tenga suficientes fuentes de entropía y que no exponga ningún estado interno que pueda comprometer la aleatoriedad posterior).
fuente
Esta publicación ya ha sido bastante bien respondida: use una implementación de Durstenfeld de la mezcla de Fisher-Yates para obtener un resultado rápido e imparcial. Incluso se han publicado algunas implementaciones, aunque observo que algunas son realmente incorrectas.
Hace un tiempo escribí un par de publicaciones sobre la implementación de shuffles completos y parciales utilizando esta técnica , y (este segundo enlace es donde espero agregar valor) también una publicación de seguimiento sobre cómo verificar si su implementación es imparcial , que se puede usar para verificar cualquier algoritmo aleatorio. Puede ver al final de la segunda publicación el efecto de un simple error en la selección de números aleatorios.
fuente
Ok, esto es claramente un golpe de mi parte (se disculpa ...), pero a menudo uso un método bastante general y criptográficamente fuerte.
Shuffle () es una extensión en cualquier IEnumerable, por lo que puede obtener, por ejemplo, números del 0 al 1000 en orden aleatorio en una lista con
Este método tampoco dará sorpresas cuando se trata de ordenar, ya que el valor de clasificación se genera y recuerda exactamente una vez por elemento en la secuencia.
fuente
No necesitas algoritmos complicados.
Solo una línea simple:
Tenga en cuenta que tenemos que convertir la
Array
a unaList
primera, si no se utilizaList
en el primer lugar.Además, tenga en cuenta que esto no es eficiente para matrices muy grandes. De lo contrario, es limpio y simple.
fuente
Esta es una solución de consola de trabajo completa basada en el ejemplo proporcionado aquí :
fuente
fuente
Aquí hay una manera simple de usar OLINQ:
fuente
fuente
sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();