Apa cara terbaik untuk mengacak urutan daftar generik dalam C #? Saya punya satu set terbatas dari 75 angka dalam daftar yang ingin saya berikan urutan acak, untuk menggambar mereka untuk aplikasi jenis lotre.
Apa cara terbaik untuk mengacak urutan daftar generik dalam C #? Saya punya satu set terbatas dari 75 angka dalam daftar yang ingin saya berikan urutan acak, untuk menggambar mereka untuk aplikasi jenis lotre.
Jawaban:
Acak apa saja (I)List
dengan metode ekstensi berdasarkan shuffle 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;
}
}
Pemakaian:
List<Product> products = GetProducts();
products.Shuffle();
Kode di atas menggunakan metode System.Random yang banyak dikritik untuk memilih calon swap. Ini cepat tetapi tidak acak sebagaimana mestinya. Jika Anda membutuhkan kualitas keacakan yang lebih baik dalam shuffles Anda, gunakan penghasil angka acak dalam System.Security.Cryptography seperti:
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;
}
}
Perbandingan sederhana tersedia di blog ini (WayBack Machine).
Sunting: Sejak menulis jawaban ini beberapa tahun yang lalu, banyak orang berkomentar atau menulis kepada saya, untuk menunjukkan kelemahan besar yang konyol dalam perbandingan saya. Mereka tentu saja benar. Tidak ada yang salah dengan System.Random jika digunakan seperti yang dimaksudkan. Dalam contoh pertama saya di atas, saya instantiate variabel rng di dalam metode Shuffle, yang meminta masalah jika metode ini akan dipanggil berulang kali. Di bawah ini adalah contoh lengkap dan tetap berdasarkan komentar yang sangat berguna yang diterima hari ini dari @weston di sini di 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();
sebuah static
akan memecahkan masalah di pos perbandingan. Karena setiap panggilan berikutnya akan mengikuti dari panggilan sebelumnya hasil acak terakhir.
Jika kita hanya perlu mengacak item dalam urutan yang benar-benar acak (hanya untuk mencampur item dalam daftar), saya lebih suka kode sederhana namun efektif ini yang memesan item dengan panduan ...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
hanya menjamin bahwa itu memberi Anda GUID unik. Itu tidak membuat jaminan tentang keacakan. Jika Anda menggunakan GUID untuk tujuan selain membuat nilai unik , Anda salah melakukannya.
Saya sedikit terkejut dengan semua versi kikuk dari algoritma sederhana ini di sini. Fisher-Yates (atau Knuth shuffle) agak rumit tetapi sangat kompak. Mengapa ini rumit? Karena Anda perlu memperhatikan apakah generator angka acak Anda r(a,b)
mengembalikan nilai di mana b
inklusif atau eksklusif. Saya juga mengedit deskripsi Wikipedia sehingga orang tidak secara buta mengikuti kodesemu di sana dan membuat sulit untuk mendeteksi bug. Untuk .Net, Random.Next(a,b)
mengembalikan nomor eksklusif b
tanpa basa-basi lagi, inilah cara penerapannya di 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
, yaitu iterasi terakhir, rnd.Next(i, list.Count)
akan memberikan Anda kembali. Karena itu Anda perlu i < list.Count -1
sebagai kondisi loop. Nah, Anda tidak 'membutuhkannya', tetapi menghemat 1 iterasi;)
Metode ekstensi untuk IEnumerable:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
OrderBy
menggunakan varian QuickSort untuk mengurutkan item dengan kunci (seolah-olah acak). Kinerja QuickSort adalah O (N log N) ; sebaliknya, shuffle Fisher-Yates adalah O (N) . Untuk koleksi 75 elemen, ini mungkin bukan masalah besar, tetapi perbedaannya akan menjadi nyata untuk koleksi yang lebih besar.
Random.Next()
dapat menghasilkan distribusi nilai pseudo-acak yang cukup, tetapi tidak menjamin bahwa nilai tersebut akan unik. Peluang kunci duplikat tumbuh (non-linear) dengan N hingga mencapai kepastian ketika N mencapai 2 ^ 32 + 1. The OrderBy
QuickSort adalah stabil semacam; dengan demikian, jika beberapa elemen terjadi untuk mendapatkan nilai indeks pseudo-acak yang sama, maka urutannya dalam urutan output akan sama seperti pada urutan input; dengan demikian, bias dimasukkan ke dalam "shuffle".
Ide mendapatkan objek anonim dengan item dan urutan acak lalu menyusun ulang item berdasarkan urutan dan nilai pengembalian ini:
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()
untuk menghindari mengeluarkan semua item dari daftar yang masuk? Saya tidak benar-benar mengerti mengapa Anda ingin bermutasi dari daftar tersebut menjadi kosong.
EDIT
The RemoveAt
adalah kelemahan dalam versi sebelumnya. Solusi ini mengatasi itu.
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];
}
}
Perhatikan opsional Random generator
, jika implementasi kerangka dasarRandom
tidak aman atau kriptografis cukup kuat untuk kebutuhan Anda, Anda dapat menyuntikkan implementasi Anda ke dalam operasi.
Inilah sebuah ide, perluas IList dengan cara (semoga) efisien.
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
atau Next
?
Anda dapat mencapainya dengan menggunakan metode ekstensi sederhana ini
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
dan Anda dapat menggunakannya dengan melakukan hal berikut
// 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
instance kelas di luar fungsi sebagai static
variabel. Kalau tidak, Anda mungkin mendapatkan seed pengacakan yang sama dari timer jika dipanggil secara berurutan.
Ini adalah metode shuffle pilihan saya ketika diinginkan untuk tidak memodifikasi yang asli. Ini adalah varian dari algoritma Fisher-Yates "inside-out" yang bekerja pada urutan enumerable apa pun (panjangnya source
tidak perlu diketahui dari awal).
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;
}
Algoritma ini juga dapat diimplementasikan dengan mengalokasikan rentang dari 0
hinggalength - 1
dan melelahkan indeks secara acak dengan menukar indeks yang dipilih secara acak dengan indeks terakhir sampai semua indeks telah dipilih tepat sekali. Kode di atas menyelesaikan hal yang sama persis tetapi tanpa alokasi tambahan. Yang cukup rapi.
Berkenaan dengan Random
kelas itu adalah generator nomor tujuan umum (dan Jika saya menjalankan lotre saya akan mempertimbangkan menggunakan sesuatu yang berbeda). Itu juga bergantung pada nilai seed berdasarkan waktu secara default. Pengurangan kecil dari masalahnya adalah dengan menaburkan Random
kelas dengan RNGCryptoServiceProvider
atau Anda dapat menggunakan RNGCryptoServiceProvider
dalam metode yang mirip dengan ini (lihat di bawah) untuk menghasilkan nilai titik mengambang ganda acak yang dipilih secara seragam, tetapi menjalankan lotre cukup banyak membutuhkan pemahaman keacakan dan sifat dari sumber keacakan.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
Titik menghasilkan dobel acak (antara 0 dan 1 secara eksklusif) adalah menggunakan skala untuk solusi integer. Jika Anda perlu mengambil sesuatu dari daftar berdasarkan pada double acak x
yang selalu akan 0 <= x && x < 1
lurus ke depan.
return list[(int)(x * list.Count)];
Nikmati!
Jika Anda tidak keberatan menggunakan dua Lists
, maka ini mungkin cara termudah untuk melakukannya, tetapi mungkin bukan yang paling efisien atau tidak dapat diprediksi:
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);
Jika Anda memiliki nomor tetap (75), Anda bisa membuat array dengan 75 elemen, lalu menghitung daftar Anda, memindahkan elemen ke posisi acak dalam array. Anda dapat menghasilkan pemetaan nomor daftar untuk indeks array menggunakan shuffle Fisher-Yates .
Saya biasanya menggunakan:
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.
Berikut adalah Shuffler yang efisien yang mengembalikan array byte dari nilai-nilai yang diacak. Itu tidak pernah mengocok lebih dari yang dibutuhkan. Itu dapat dimulai kembali dari tempat sebelumnya. Implementasi aktual saya (tidak diperlihatkan) adalah komponen MEF yang memungkinkan pengocok pengganti yang ditentukan pengguna.
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;
}
`
Berikut cara aman untuk melakukannya:
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);
}
Modifikasi sederhana dari jawaban yang diterima yang mengembalikan daftar baru alih-alih bekerja di tempat, dan menerima yang lebih umum IEnumerable<T>
seperti metode Linq lainnya.
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;
}
Saya telah menemukan solusi online yang menarik.
Courtesy: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var mengocok = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
Posting lama pasti, tapi saya hanya menggunakan GUID.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
GUID selalu unik, dan karena itu dibuat ulang setiap kali hasilnya berubah setiap kali.
Pendekatan yang sangat sederhana untuk masalah semacam ini adalah dengan menggunakan sejumlah swap elemen acak dalam daftar.
Dalam pseudo-code ini akan terlihat seperti ini:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times