Cách tốt nhất để ngẫu nhiên hóa thứ tự của một danh sách chung trong C # là gì? Tôi đã có một bộ 75 số hữu hạn trong một danh sách mà tôi muốn chỉ định một thứ tự ngẫu nhiên, để vẽ chúng cho một ứng dụng loại xổ số.
Cách tốt nhất để ngẫu nhiên hóa thứ tự của một danh sách chung trong C # là gì? Tôi đã có một bộ 75 số hữu hạn trong một danh sách mà tôi muốn chỉ định một thứ tự ngẫu nhiên, để vẽ chúng cho một ứng dụng loại xổ số.
Câu trả lời:
Xáo trộn bất kỳ (I)List
với một phương thức mở rộng dựa trên xáo trộn 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;
}
}
Sử dụng:
List<Product> products = GetProducts();
products.Shuffle();
Đoạn mã trên sử dụng phương thức System.Random bị chỉ trích nhiều để chọn ứng viên hoán đổi. Nó nhanh nhưng không ngẫu nhiên như nó phải vậy. Nếu bạn cần chất lượng ngẫu nhiên tốt hơn trong các xáo trộn của mình, hãy sử dụng trình tạo số ngẫu nhiên trong System.Security.Cryptography như vậy:
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;
}
}
Một so sánh đơn giản có sẵn tại blog này (WayBack Machine).
Chỉnh sửa: Kể từ khi viết câu trả lời này vài năm trước, nhiều người đã bình luận hoặc viết thư cho tôi, để chỉ ra lỗ hổng ngớ ngẩn lớn trong so sánh của tôi. Họ tất nhiên là đúng. Không có gì sai với System.Random nếu nó được sử dụng theo cách nó được dự định. Trong ví dụ đầu tiên của tôi ở trên, tôi khởi tạo biến rng bên trong phương thức Shuffle, yêu cầu sự cố nếu phương thức này sẽ được gọi nhiều lần. Dưới đây là một ví dụ đầy đủ, cố định dựa trên một nhận xét thực sự hữu ích nhận được ngày hôm nay từ @weston ở đây trên SO.
Chương trình.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();
một static
sẽ giải quyết vấn đề trong bài so sánh. Vì mỗi cuộc gọi tiếp theo sẽ tiếp tục từ các cuộc gọi trước kết quả ngẫu nhiên cuối cùng.
Nếu chúng ta chỉ cần xáo trộn các mục theo thứ tự hoàn toàn ngẫu nhiên (chỉ để trộn các mục trong danh sách), tôi thích mã đơn giản nhưng hiệu quả này để sắp xếp các mục theo hướng dẫn ...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
chỉ đảm bảo rằng nó mang lại cho bạn một GUID duy nhất. Nó không đảm bảo về tính ngẫu nhiên. Nếu bạn đang sử dụng GUID cho mục đích khác ngoài việc tạo một giá trị duy nhất , bạn đã làm sai.
Tôi hơi ngạc nhiên bởi tất cả các phiên bản mạnh mẽ của thuật toán đơn giản này ở đây. Fisher-Yates (hay Knuth shuffle) hơi phức tạp nhưng rất nhỏ gọn. Tại sao nó khó khăn? Bởi vì bạn cần chú ý xem liệu trình tạo số ngẫu nhiên của bạn r(a,b)
trả về giá trị ở đâu b
là bao gồm hay độc quyền. Tôi cũng đã chỉnh sửa mô tả Wikipedia để mọi người không mù quáng theo mã giả ở đó và khó phát hiện lỗi. Đối với .Net, Random.Next(a,b)
trả về số độc quyền b
như vậy mà không cần phải quảng cáo thêm, đây là cách có thể triển khai trong 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
, tức là lần lặp cuối cùng, rnd.Next(i, list.Count)
sẽ trả lại cho bạn. Do đó, bạn cần i < list.Count -1
như điều kiện vòng lặp. Chà, bạn không 'cần' nó, nhưng nó tiết kiệm được 1 lần lặp;)
Phương pháp mở rộng cho IEnumerable:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
OrderBy
sử dụng biến thể QuickSort để sắp xếp các mục theo các khóa (có vẻ ngẫu nhiên) của chúng. Hiệu suất QuickSort là O (N log N) ; ngược lại, một shuffle Fisher-Yates là O (N) . Đối với một bộ sưu tập gồm 75 yếu tố, điều này có thể không phải là vấn đề lớn, nhưng sự khác biệt sẽ trở nên rõ rệt đối với các bộ sưu tập lớn hơn.
Random.Next()
có thể tạo ra phân phối giá trị giả ngẫu nhiên hợp lý, nhưng không đảm bảo rằng các giá trị sẽ là duy nhất. Xác suất của các khóa trùng lặp tăng lên (phi tuyến tính) với N cho đến khi đạt được sự chắc chắn khi N đạt 2 ^ 32 + 1. Các OrderBy
Sắp xếp nhanh là ổn định loại; do đó, nếu nhiều phần tử xảy ra để được gán cùng một giá trị chỉ mục giả ngẫu nhiên, thì thứ tự của chúng trong chuỗi đầu ra sẽ giống như trong chuỗi đầu vào; do đó, một thiên vị được đưa vào "xáo trộn".
Ý tưởng là lấy đối tượng ẩn danh với mục và thứ tự ngẫu nhiên, sau đó sắp xếp lại các mục theo thứ tự này và trả về giá trị:
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()
để tránh bật tất cả các mục ra khỏi danh sách đến? Tôi thực sự không thấy lý do tại sao bạn muốn biến đổi những danh sách đó thành trống.
EDIT
Điểm RemoveAt
yếu trong phiên bản trước của tôi. Giải pháp này khắc phục điều đó.
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];
}
}
Lưu ý tùy chọn Random generator
, nếu việc triển khai khung cơ sở Random
không an toàn theo luồng hoặc mã hóa đủ mạnh cho nhu cầu của bạn, bạn có thể đưa triển khai của mình vào hoạt động.
Đây là một ý tưởng, mở rộng IList theo cách hiệu quả (hy vọng).
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
hay Next
?
Bạn có thể đạt được điều đó bằng cách sử dụng phương pháp mở rộng đơn giản này
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
và bạn có thể sử dụng nó bằng cách làm như sau
// 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
cá thể lớp bên ngoài hàm như một static
biến. Nếu không, bạn có thể nhận được hạt giống ngẫu nhiên tương tự từ bộ đếm thời gian nếu được gọi liên tiếp.
Đây là phương pháp xáo trộn ưa thích của tôi khi muốn không sửa đổi bản gốc. Đây là một biến thể của thuật toán "từ trong ra ngoài" của FisherTHER Yates hoạt động trên bất kỳ chuỗi nào có thể đếm được (độ dài source
không cần phải biết từ đầu).
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;
}
Thuật toán này cũng có thể được thực hiện bằng cách phân bổ một phạm vi từ 0
đếnlength - 1
và làm cạn kiệt ngẫu nhiên các chỉ số bằng cách hoán đổi chỉ mục được chọn ngẫu nhiên với chỉ mục cuối cùng cho đến khi tất cả các chỉ số được chọn chính xác một lần. Mã ở trên hoàn thành điều tương tự chính xác nhưng không có phân bổ bổ sung. Mà là khá gọn gàng.
Liên quan đến Random
lớp học, đây là trình tạo số mục đích chung (và nếu tôi đang chạy xổ số, tôi sẽ cân nhắc sử dụng một cái gì đó khác biệt). Nó cũng dựa trên giá trị hạt giống dựa trên thời gian theo mặc định. Một vấn đề nhỏ của vấn đề là tạo hạt giống cho Random
lớp RNGCryptoServiceProvider
hoặc bạn có thể sử dụng RNGCryptoServiceProvider
phương thức tương tự như thế này (xem bên dưới) để tạo các giá trị dấu phẩy động ngẫu nhiên được chọn thống nhất nhưng chạy xổ số khá nhiều đòi hỏi phải hiểu ngẫu nhiên và bản chất của nguồn ngẫu nhiên.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
Điểm tạo ra một gấp đôi ngẫu nhiên (chỉ từ 0 đến 1) là sử dụng để chia tỷ lệ thành một giải pháp số nguyên. Nếu bạn cần chọn một cái gì đó từ một danh sách dựa trên một đôi ngẫu nhiên x
, điều luôn luôn sẽ 0 <= x && x < 1
là thẳng về phía trước.
return list[(int)(x * list.Count)];
Thưởng thức!
Nếu bạn không phiền khi sử dụng hai Lists
, thì đây có lẽ là cách dễ nhất để làm điều đó, nhưng có lẽ không phải là cách hiệu quả nhất hoặc không thể đoán trước:
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);
Nếu bạn có một số cố định (75), bạn có thể tạo một mảng với 75 phần tử, sau đó liệt kê danh sách của bạn, di chuyển các phần tử đến các vị trí ngẫu nhiên trong mảng. Bạn có thể tạo ánh xạ số danh sách thành chỉ mục mảng bằng cách sử dụng xáo trộn Fisher-Yates .
Tôi thường sử dụng:
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.
Đây là một Shuffler hiệu quả trả về một mảng byte của các giá trị được xáo trộn. Nó không bao giờ xáo trộn nhiều hơn mức cần thiết. Nó có thể được khởi động lại từ nơi mà trước đó nó đã rời đi. Triển khai thực tế của tôi (không hiển thị) là một thành phần MEF cho phép người dùng thay thế chỉ định thay đổi.
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;
}
`
Đây là một cách an toàn chủ đề để làm điều này:
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);
}
Một sửa đổi đơn giản của câu trả lời được chấp nhận trả về một danh sách mới thay vì làm việc tại chỗ và chấp nhận tổng quát hơn IEnumerable<T>
như nhiều phương pháp Linq khác làm.
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;
}
Tôi đã tìm thấy một giải pháp thú vị trực tuyến.
Lịch sự: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var shuffled = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
Chắc chắn bài cũ, nhưng tôi chỉ sử dụng GUID.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
GUID luôn là duy nhất và vì nó được tạo lại mỗi lần kết quả thay đổi mỗi lần.
Một cách tiếp cận rất đơn giản cho loại vấn đề này là sử dụng một số trao đổi phần tử ngẫu nhiên trong danh sách.
Trong mã giả, nó sẽ trông như thế này:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times