Каков наилучший способ рандомизировать порядок общего списка в С#? У меня есть конечный набор из 75 номеров в списке, который я хотел бы присвоить случайному порядку, чтобы нарисовать их для приложения типа лотереи.
Рандомизировать список <T>
Ответ 1
Перетасуйте любой (I)List
с помощью метода расширения, основанного на Fisher-Yates shuffle:
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;
}
}
Использование:
List<Product> products = GetProducts();
products.Shuffle();
В приведенном выше коде используется сильно критичный метод System.Random для выбора подкачки. Он быстрый, но не такой случайный, как должен. Если вам нужно лучшее качество случайности в ваших тасованиях, используйте генератор случайных чисел в System.Security.Cryptography следующим образом:
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;
}
}
Простое сравнение доступно в этом блоге (WayBack Machine).
Изменить: поскольку я пишу этот ответ пару лет назад, многие люди комментировали или писали мне, чтобы указать на большой глупый недостаток в моем сравнении. Они, конечно, правы. Там нет ничего плохого в System.Random, если он используется так, как он был предназначен. В моем первом примере выше я создаю экземпляр rng-переменной внутри метода Shuffle, который запрашивает проблемы, если метод будет вызываться повторно. Ниже приведен фиксированный полный пример, основанный на действительно полезном комментарии, полученном сегодня от @weston здесь, на 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;
}
}
}
}
Ответ 2
Если нам нужно только перетасовать элементы в полностью случайном порядке (просто для смешивания элементов в списке), я предпочитаю этот простой, но эффективный код, который заказывает элементы по guid...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
Ответ 3
Я немного удивлен всеми неуклюжими версиями этого простого алгоритма здесь. Фишер-Йейтс (или Кнут Шаффл) немного сложнее, но очень компактен. Если вы пойдете в Википедию, вы увидите версию этого алгоритма, которая имеет цикл for в обратном порядке, и многие люди на самом деле не понимают, почему это наоборот. Основная причина в том, что в этой версии алгоритма предполагается, что генератор Random(n)
чисел Random(n)
в вашем распоряжении имеет два следующих свойства:
- Он принимает n как один входной параметр.
- Возвращает число от 0 до n включительно.
Однако .Net генератор случайных чисел не удовлетворяет свойству # 2. Random.Next(n)
возвращает число от 0 до n-1 включительно. Если вы попытаетесь использовать цикл for в обратном порядке, вам потребуется вызвать Random.Next(n+1)
который добавляет одну дополнительную операцию.
Однако в генераторе случайных чисел .Net есть еще одна приятная функция Random.Next(a,b)
которая возвращает a в b-1 включительно. Это на самом деле прекрасно вписывается в реализацию этого алгоритма с нормальным циклом for. Так что без лишних слов, здесь правильная, эффективная и компактная реализация:
public static void Shuffle<T>(this IList<T> list, Random rnd)
{
for(var i=0; i < list.Count - 1; i++)
list.Swap(i, rnd.Next(i, list.Count));
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}
Ответ 4
Метод расширения для IEnumerable:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
Ответ 5
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;
}
Ответ 6
ИЗМЕНИТЬ
RemoveAt
- слабость в моей предыдущей версии. Это решение преодолевает это.
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];
}
}
Обратите внимание на необязательный Random generator
, если реализация базового фреймворка Random
не является потокобезопасной или криптографически достаточно сильной для ваших нужд, вы можете ввести свою реализацию в операцию.
Здесь идея, расширяющая IList в (надеюсь) эффективный способ.
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]];
}
Ответ 7
Вы можете достичь этого, используя этот простой метод расширения
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
и вы можете использовать его, выполнив следующие
// 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);
}
Ответ 8
Идея получает анонимный объект с элементом и случайным порядком, а затем переупорядочивает элементы по этому порядку и возвращаемому значению:
var result = items.Select(x => new { value = x, order = rnd.Next() })
.OrderBy(x => x.order).Select(x => x.value).ToList()
Ответ 9
Если у вас есть фиксированное число (75), вы можете создать массив из 75 элементов, а затем перечислить список, перемещая элементы в рандомизированные позиции в массиве. Вы можете сгенерировать отображение номера списка в индекс массива с помощью Fisher-Yates shuffle.
Ответ 10
Обычно я использую:
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);
}
Ответ 11
Это мой предпочтительный метод перетасовки, когда желательно не изменять оригинал. Это вариант алгоритма Fisher-Yates "наизнанку" , который работает с любой перечислимой последовательностью (длина source
не нужна быть известными с самого начала).
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;
}
Этот алгоритм также может быть реализован путем выделения диапазона от 0
до length - 1
и случайного исчерпания индексов путем замены случайно выбранного индекса на последний индекс до тех пор, пока все индексы не будут выбраны ровно один раз. Этот выше код выполняет то же самое, но без дополнительного распределения. Это довольно аккуратно.
Что касается класса Random
, это генератор чисел общего назначения (и если бы я запускал лотерею, я бы подумал об использовании чего-то другого). По умолчанию он также полагается на начальное значение по времени. Небольшое облегчение проблемы состоит в том, чтобы засеять класс Random
с помощью RNGCryptoServiceProvider
, или вы можете использовать RNGCryptoServiceProvider
в методе, подобном этому (см. Ниже), чтобы генерировать равномерно выбранные случайные значения двойной плавающей запятой, но запускать лотерею в значительной степени требуется понимание случайности и характер источника случайности.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
Точка генерации случайного двойника (между 0 и 1 исключительно) заключается в использовании масштабирования для целочисленного решения. Если вам нужно выбрать что-то из списка на основе случайного двойного x
, который всегда будет 0 <= x && x < 1
, является прямым.
return list[(int)(x * list.Count)];
Наслаждайтесь!
Ответ 12
Если вы не против использования двух Lists
, то это, вероятно, самый простой способ сделать это, но, вероятно, не самый эффективный или непредсказуемый:
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);
Ответ 13
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);
}
Ответ 14
Здесь эффективный Shuffler, который возвращает массив байтов перетасованных значений. Он никогда не перемешивает больше, чем нужно. Он может быть перезапущен с того места, где он ранее был остановлен. Моя фактическая реализация (не показана) - это MEF-компонент, который позволяет пользователю задавать замену shuffler.
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;
}
`
Ответ 15
В этом потокобезопасном способе сделать это:
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());
}
}
Ответ 16
Простая модификация принятого ответа , который возвращает новый список вместо работы на месте и принимает более общий IEnumerable<T>
, как и многие другие методы 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;
}
Ответ 17
Улучшенная (IMHO) версия алгоритма гранатового ответа, аннотированная для понимания изменений с первого взгляда:
public static class IListExtensions
{
//Random is perfectly fine if used correctly.
[NotNull]
private static readonly Random Random;
//Delay instantiation to when it actually needed, if it ever is.
static IListExtensions()
{
Random = new Random();
}
/// <summary>Shuffles the specified list.</summary>
/// <typeparam name="T">The type of the elements in the list.</typeparam>
/// <param name="list">The list.</param>
/// <exception cref="ArgumentNullException">list - Is null.</exception>
public static void Shuffle<T>([NotNull] this IList<T> list)
{
//An extension method is a static method with syntax sugar sprinkled on top...
//...A null check is still necessary, which most answers here forget to do.
if (list == null) throw new ArgumentNullException("list", "Is null.");
//Using a FOR-LOOP instead of a WHILE-LOOP avoids an unnecessary variable.
for (var i = list.Count; i > 1; i--)
{
var r = Random.Next(i--); //Using 'i--' here avoids an unnecessary line/statement.
//Swapping with a separate method has no advantage and just introduces overhead.
var t = list[r];
list[r] = list[i];
list[i] = t;
}
}
}
Ответ 18
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.
Ответ 19
Вы можете сделать это путем сортировки, и сравнение будет сделано с помощью случайного
var rand = new Random();
var list = new List<T>();
list.Sort((a,b)=>rand.Next(-1,2));
Ответ 20
Старый пост наверняка, но я просто использую GUID.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
GUID всегда уникален, и поскольку он регенерируется каждый раз, когда результат изменяется каждый раз.
Ответ 21
Очень простой подход к этой проблеме состоит в том, чтобы использовать в ней список случайных свопов.
В псевдокоде это будет выглядеть так:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times