Який найкращий спосіб рандомізувати порядок загального списку в C #? У мене в списку є обмежений набір 75 номерів, якому я хотів би призначити випадкове замовлення, щоб намалювати їх для заявки на тип лотереї.
Який найкращий спосіб рандомізувати порядок загального списку в C #? У мене в списку є обмежений набір 75 номерів, якому я хотів би призначити випадкове замовлення, щоб намалювати їх для заявки на тип лотереї.
Відповіді:
Перемішайте будь-який (I)List
із методом розширення на основі перемішування 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;
}
}
Використання:
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;
}
}
}
}
Random rng = new Random();
static
Якщо нам потрібно лише переміщувати елементи в абсолютно випадковому порядку (просто для змішування елементів у списку), я віддаю перевагу цьому простому, але ефективного коду, який замовляє елементи за допомогою інструкцій ...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
гарантує лише те, що він дає вам унікальний GUID. Це не дає гарантій щодо випадковості. Якщо ви використовуєте GUID для цілей, відмінних від створення унікального значення, ви робите це неправильно.
Мене трохи здивують усі незграбні версії цього простого алгоритму. Фішер-Йейтс (або Кнут-шуфф) трохи складний, але дуже компактний. Чому це хитро? Тому що вам потрібно звернути увагу на те, чи r(a,b)
повертає ваш генератор випадкових чисел значення там, де b
він включений чи виключний. Я також редагував опис Вікіпедії, щоб люди не сліпо дотримувались псевдокоду там і створювали важкі для виявлення помилки. Для .Net, Random.Next(a,b)
повертає число, виключне b
так, без додаткового оболонки, ось як це можна реалізувати в 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
, тобто остання ітерація, rnd.Next(i, list.Count)
поверне вас я назад. Тому вам потрібна i < list.Count -1
умова циклу. Ну, ви не «потреба» це, але це економить 1 ітерація;)
Метод розширення для IE Численні:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
OrderBy
використовує варіант QuickSort для сортування елементів за їх (нібито випадковими) клавішами. Продуктивність QuickSort становить O (N log N) ; На відміну від цього, переміщення Фішера-Йейта є O (N) . Для колекції з 75 елементів це може не бути великою справою, але різниця стане яскраво вираженою для великих колекцій.
Random.Next()
може спричинити розумно псевдовипадковий розподіл значень, але це не гарантує, що значення будуть унікальними. Ймовірність повторюваних ключів зростає (нелінійно) з N, поки не досягне визначеності, коли N досягне 2 ^ 32 + 1. OrderBy
QuickSort є стабільним родом; таким чином, якщо трапляється присвоєння декількох елементів однакового псевдовипадкового значення індексу, їх порядок у вихідній послідовності буде таким самим, як у послідовності введення; таким чином, упередження вводиться в "перетасування".
Ідея - отримати анонімний об’єкт з предметом і випадковим порядком, а потім переупорядкувати елементи за цим порядком і повернути значення:
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()
щоб уникнути спливання всіх елементів із списку вхідних? Я не розумію, чому ви хочете вимкнути ці списки на порожні.
EDITRemoveAt
слабкість в моїй попередній версії. Це рішення долає це.
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]];
}
GetNext
чи Next
?
Ви можете досягти цього, використовуючи цей простий метод розширення
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);
}
Random
екземпляр класу поза функцією як static
змінної. В іншому випадку ви можете отримати одне насіння рандомізації з таймера, якщо викликати його швидкою послідовністю.
Це мій кращий метод переміщення, коли бажано не змінювати оригінал. Це варіант алгоритму Фішера-Йейта "всередину", який працює на будь-якій численній послідовності (довжина 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)];
Насолоджуйтесь!
Якщо ви не проти використовувати два 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);
Якщо у вас є фіксоване число (75), ви можете створити масив із 75 елементами, а потім перерахувати свій список, перемістивши елементи до рандомізованих позицій у масиві. Ви можете створити відображення номера списку для індексу масиву за допомогою перемикання Fisher-Yates .
Я зазвичай використовую:
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.
Ось ефективний Shuffler, який повертає байтовий масив перетасованих значень. Він ніколи не змішується більше, ніж потрібно. Її можна перезапустити з того місця, де вона раніше припинилась. Моя реальна реалізація (не показана) - це компонент MEF, який дозволяє користувачеві замінити перетасовувач.
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;
}
`
Ось безпечний для цього спосіб:
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);
}
Проста модифікація прийнятої відповіді, яка повертає новий список замість того, щоб працювати на місці, і приймає більш загальний, 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;
}
Я знайшов цікаве рішення в Інтернеті.
Люб’язно: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var shuffled = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
Старий пост напевно, але я просто використовую GUID.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
GUID завжди унікальний, і оскільки він регенерується щоразу, коли результат змінюється кожного разу.
Дуже простий підхід до подібної проблеми полягає у використанні в списку декількох випадкових елементів заміни.
У псевдо-коді це виглядатиме так:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times