¿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.
¿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.
Respuestas:
Mezcle cualquiera (I)List
con un método de extensión basado en la mezcla aleatoria de Fisher-Yates :
private static Random rng = new Random();
public static void Shuffle<T>(this IList<T> 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;
}
}
Uso:
List<Product> products = GetProducts();
products.Shuffle();
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:
using System.Security.Cryptography;
...
public static void Shuffle<T>(this IList<T> 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;
}
}
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:
using System;
using System.Collections.Generic;
using System.Threading;
namespace SimpleLottery
{
class Program
{
private static void Main(string[] args)
{
var numbers = new List<int>(Enumerable.Range(1, 75));
numbers.Shuffle();
Console.WriteLine("The winning numbers are: {0}", string.Join(", ", numbers.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<T>(this IList<T> 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;
}
}
}
}
Random rng = new Random();
un static
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 ...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
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 donde b
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:
public static void Shuffle<T>(this IList<T> list, Random rnd)
{
for(var i=list.Count; i > 0; i--)
list.Swap(0, rnd.Next(0, i));
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}
i = list.Count - 1
, es decir, la última iteración, rnd.Next(i, list.Count)
te devolverá i. Por lo tanto, necesita i < list.Count -1
como condición de bucle. Bueno, no lo 'necesita', pero ahorra 1 iteración;)
Método de extensión para IEnumerable:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
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. El OrderBy
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:
var result = items.Select(x => new { value = x, order = rnd.Next() })
.OrderBy(x => x.order).Select(x => x.value).ToList()
public static List<T> Randomize<T>(List<T> list)
{
List<T> randomizedList = new List<T>();
Random rnd = new Random();
while (list.Count > 0)
{
int index = rnd.Next(0, list.Count); //pick a random item from the master list
randomizedList.Add(list[index]); //place it at the end of the randomized list
list.RemoveAt(index);
}
return randomizedList;
}
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.
public static IEnumerable<T> Shuffle<T>(
this IEnumerable<T> source,
Random generator = null)
{
if (generator == null)
{
generator = new Random();
}
var elements = source.ToArray();
for (var i = elements.Length - 1; i >= 0; i--)
{
var swapIndex = generator.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
Tenga en cuenta lo opcional Random generator
, si la implementación del marco base de Random
no es segura para subprocesos o criptográficamente lo suficientemente fuerte para sus necesidades, puede inyectar su implementación en la operación.
He aquí una idea, extienda IList de una manera (con suerte) eficiente.
public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
var choices = Enumerable.Range(0, list.Count).ToList();
var rng = new Random();
for(int n = choices.Count; n > 1; n--)
{
int k = rng.Next(n);
yield return list[choices[k]];
choices.RemoveAt(k);
}
yield return list[choices[0]];
}
GetNext
o Next
?
Puede lograrlo utilizando este sencillo método de extensión
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
y puedes usarlo haciendo lo siguiente
// use this on any collection that implements IEnumerable!
// List, Array, HashSet, Collection, etc
List<string> myList = new List<string> { "hello", "random", "world", "foo", "bar", "bat", "baz" };
foreach (string s in myList.Randomize())
{
Console.WriteLine(s);
}
Random
instancia de clase fuera de la función como una static
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).
public static IList<T> NextList<T>(this Random r, IEnumerable<T> source)
{
var list = new List<T>();
foreach (var item in source)
{
var i = r.Next(list.Count + 1);
if (i == list.Count)
{
list.Add(item);
}
else
{
var temp = list[i];
list[i] = item;
list.Add(temp);
}
}
return list;
}
Este algoritmo también se puede implementar mediante la asignación de un rango de 0
a length - 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 la Random
clase con el RNGCryptoServiceProvider
o podría usarlo RNGCryptoServiceProvider
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.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
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.
return list[(int)(x * list.Count)];
¡Disfrutar!
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:
List<int> xList = new List<int>() { 1, 2, 3, 4, 5 };
List<int> deck = new List<int>();
foreach (int xInt in xList)
deck.Insert(random.Next(0, deck.Count + 1), xInt);
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 .
Usualmente uso:
var list = new List<T> ();
fillList (list);
var randomizedList = new List<T> ();
var rnd = new Random ();
while (list.Count != 0)
{
var index = rnd.Next (0, list.Count);
randomizedList.Add (list [index]);
list.RemoveAt (index);
}
List<T> OriginalList = new List<T>();
List<T> TempList = new List<T>();
Random random = new Random();
int length = OriginalList.Count;
int TempIndex = 0;
while (length > 0) {
TempIndex = random.Next(0, length); // get random value between 0 and original length
TempList.Add(OriginalList[TempIndex]); // add to temp list
OriginalList.RemoveAt(TempIndex); // remove from original list
length = OriginalList.Count; // get new list <T> length.
}
OriginalList = new List<T>();
OriginalList = TempList; // copy all items from temp list to original list.
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.
public byte[] Shuffle(byte[] array, int start, int count)
{
int n = array.Length - start;
byte[] shuffled = new byte[count];
for(int i = 0; i < count; i++, start++)
{
int k = UniformRandomGenerator.Next(n--) + start;
shuffled[i] = array[k];
array[k] = array[start];
array[start] = shuffled[i];
}
return shuffled;
}
``
Aquí hay una forma segura de subprocesos para hacer esto:
public static class EnumerableExtension
{
private static Random globalRng = new Random();
[ThreadStatic]
private static Random _rng;
private static Random rng
{
get
{
if (_rng == null)
{
int seed;
lock (globalRng)
{
seed = globalRng.Next();
}
_rng = new Random(seed);
}
return _rng;
}
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
{
return items.OrderBy (i => rng.Next());
}
}
public Deck(IEnumerable<Card> initialCards)
{
cards = new List<Card>(initialCards);
public void Shuffle()
}
{
List<Card> NewCards = new List<Card>();
while (cards.Count > 0)
{
int CardToMove = random.Next(cards.Count);
NewCards.Add(cards[CardToMove]);
cards.RemoveAt(CardToMove);
}
cards = NewCards;
}
public IEnumerable<string> GetCardNames()
{
string[] CardNames = new string[cards.Count];
for (int i = 0; i < cards.Count; i++)
CardNames[i] = cards[i].Name;
return CardNames;
}
Deck deck1;
Deck deck2;
Random random = new Random();
public Form1()
{
InitializeComponent();
ResetDeck(1);
ResetDeck(2);
RedrawDeck(1);
RedrawDeck(2);
}
private void ResetDeck(int deckNumber)
{
if (deckNumber == 1)
{
int numberOfCards = random.Next(1, 11);
deck1 = new Deck(new Card[] { });
for (int i = 0; i < numberOfCards; i++)
deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14)));
deck1.Sort();
}
else
deck2 = new Deck();
}
private void reset1_Click(object sender, EventArgs e) {
ResetDeck(1);
RedrawDeck(1);
}
private void shuffle1_Click(object sender, EventArgs e)
{
deck1.Shuffle();
RedrawDeck(1);
}
private void moveToDeck1_Click(object sender, EventArgs e)
{
if (listBox2.SelectedIndex >= 0)
if (deck2.Count > 0) {
deck1.Add(deck2.Deal(listBox2.SelectedIndex));
}
RedrawDeck(1);
RedrawDeck(2);
}
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.
private static Random rng = new Random();
/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
var source = list.ToList();
int n = source.Count;
var shuffled = new List<T>(n);
shuffled.AddRange(source);
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = shuffled[k];
shuffled[k] = shuffled[n];
shuffled[n] = value;
}
return shuffled;
}
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 ();
Publicación antigua con seguridad, pero solo uso un GUID.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
Un GUID siempre es único, y dado que se regenera cada vez que el resultado cambia cada vez.
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í:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times