Ini bukan cara pengocokan yang saya sukai, sebagian besar dengan alasan itu adalah O (n log n) tanpa alasan yang baik ketika mudah untuk menerapkan pengocokan O (n). Kode dalam pertanyaan "berfungsi" pada dasarnya memberikan nomor acak (semoga unik!) Untuk setiap elemen, lalu memesan elemen sesuai dengan nomor itu.
Saya lebih suka varian Durstenfield dari shuffle Fisher-Yates yang menukar elemen.
Menerapkan Shuffle
metode ekstensi sederhana pada dasarnya akan terdiri dari panggilan ToList
atau ToArray
input kemudian menggunakan implementasi Fisher-Yates yang sudah ada. (Lewati Random
sebagai parameter untuk membuat hidup secara umum lebih baik.) Ada banyak implementasi di sekitar ... Saya mungkin punya satu di jawaban di suatu tempat.
Hal yang menyenangkan tentang metode perluasan semacam itu adalah bahwa akan sangat jelas bagi pembaca apa yang sebenarnya Anda coba lakukan.
EDIT: Ini implementasi sederhana (tanpa pengecekan kesalahan!):
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
// Note i > 0 to avoid final pointless iteration
for (int i = elements.Length-1; i > 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
int swapIndex = rng.Next(i + 1);
T tmp = elements[i];
elements[i] = elements[swapIndex];
elements[swapIndex] = tmp;
}
// Lazily yield (avoiding aliasing issues etc)
foreach (T element in elements)
{
yield return element;
}
}
Sunting: Komentar tentang kinerja di bawah mengingatkan saya bahwa kami dapat benar-benar mengembalikan elemen saat mengocoknya:
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
for (int i = elements.Length - 1; i >= 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
// ... except we don't really need to swap it fully, as we can
// return it immediately, and afterwards it's irrelevant.
int swapIndex = rng.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
Ini sekarang hanya akan melakukan pekerjaan sebanyak yang dibutuhkan.
Perhatikan bahwa dalam kedua kasus, Anda harus berhati-hati tentang contoh yang Random
Anda gunakan sebagai:
- Membuat dua contoh secara
Random
kasar pada waktu yang sama akan menghasilkan urutan angka acak yang sama (bila digunakan dengan cara yang sama)
Random
tidak aman untuk thread.
Saya punya artikelRandom
yang membahas lebih detail tentang masalah ini dan memberikan solusi.
source.ToArray();
Anda harus memilikiusing System.Linq;
file yang sama. Jika tidak, Anda mendapatkan kesalahan ini:'System.Collections.Generic.IEnumerable<T>' does not contain a definition for 'ToArray' and no extension method 'ToArray' accepting a first argument of type 'System.Collections.Generic.IEnumerable<T>' could be found (are you missing a using directive or an assembly reference?)
Ini berdasarkan jawaban Jon Skeet .
Dalam jawaban itu, array dikocok, lalu dikembalikan menggunakan
yield
. Hasil akhirnya adalah array disimpan dalam memori selama durasi foreach, serta objek yang diperlukan untuk iterasi, namun biayanya semua pada awalnya - hasilnya pada dasarnya adalah loop kosong.Algoritma ini banyak digunakan dalam permainan, di mana tiga item pertama diambil, dan yang lainnya hanya akan dibutuhkan nanti jika sama sekali. Saran saya adalah
yield
nomor segera setelah mereka ditukar. Ini akan mengurangi biaya awal, sambil menjaga biaya iterasi pada O (1) (pada dasarnya 5 operasi per iterasi). Total biaya akan tetap sama, tetapi pengocokan itu sendiri akan lebih cepat. Dalam kasus-kasus di mana ini disebutcollection.Shuffle().ToArray()
secara teoritis tidak akan membuat perbedaan, tetapi dalam kasus-kasus penggunaan yang disebutkan di atas akan mempercepat start-up. Selain itu, ini akan membuat algoritme berguna untuk kasus di mana Anda hanya perlu beberapa item unik. Misalnya, jika Anda perlu mengeluarkan tiga kartu dari setumpuk 52, Anda dapat memanggildeck.Shuffle().Take(3)
dan hanya tiga swap yang akan terjadi (meskipun seluruh array harus disalin terlebih dahulu).sumber
Mulai dari kutipan Skeet ini:
Saya akan menjelaskan sedikit alasan untuk semoga unik!
Sekarang, dari Enumerable.OrderBy :
Ini sangat penting! Apa yang terjadi jika dua elemen "menerima" nomor acak yang sama? Itu terjadi bahwa mereka tetap dalam urutan yang sama mereka berada di array. Sekarang, bagaimana kemungkinan ini terjadi? Sulit untuk menghitung dengan tepat, tetapi ada Masalah Ulang Tahun yang justru merupakan masalah ini.
Sekarang, benarkah? Apakah itu benar
Seperti biasa, ketika ragu, tulis beberapa baris program: http://pastebin.com/5CDnUxPG
Blok kecil kode ini mengocok array 3 elemen beberapa kali menggunakan algoritma Fisher-Yates yang dilakukan mundur, algoritma Fisher-Yates dilakukan ke depan (pada halaman wiki ada dua algoritma pseudo-code ... Mereka menghasilkan setara hasil, tetapi satu dilakukan dari elemen pertama ke elemen terakhir, sedangkan yang lain dilakukan dari elemen terakhir ke elemen pertama), algoritma salah naif dari http://blog.codinghorror.com/the-danger-of-naivete/ dan menggunakan
.OrderBy(x => r.Next())
dan.OrderBy(x => r.Next(someValue))
.Sekarang, Random.Next adalah
jadi itu setara dengan
Untuk menguji apakah masalah ini ada, kita dapat memperbesar array (sesuatu yang sangat lambat) atau cukup mengurangi nilai maksimum dari generator angka acak (
int.MaxValue
bukan nomor "khusus" ... Ini hanyalah angka yang sangat besar). Pada akhirnya, jika algoritma tidak bias oleh stableness dariOrderBy
, maka rentang nilai apa pun harus memberikan hasil yang sama.Program kemudian menguji beberapa nilai, dalam kisaran 1 ... 4096. Melihat hasilnya, cukup jelas bahwa untuk nilai rendah (<128), algoritmenya sangat bias (4-8%). Dengan 3 nilai yang Anda butuhkan setidaknya
r.Next(1024)
. Jika Anda membuat array lebih besar (4 atau 5), maka itu punr.Next(1024)
tidak cukup. Saya bukan ahli dalam pengocokan dan matematika, tapi saya pikir untuk setiap bit ekstra panjang array, Anda memerlukan 2 bit ekstra dari nilai maksimum (karena paradoks ulang tahun terhubung ke sqrt (numvalues)), jadi bahwa jika nilai maksimum adalah 2 ^ 31, saya akan mengatakan bahwa Anda harus dapat mengurutkan array hingga 2 ^ 12/2 ^ 13 bit (4096-8192 elemen)sumber
Ini mungkin ok untuk sebagian besar tujuan, dan hampir selalu menghasilkan distribusi yang benar-benar acak (kecuali ketika Random.Next () menghasilkan dua bilangan bulat acak identik).
Ia bekerja dengan menetapkan setiap elemen dari seri bilangan bulat acak, lalu memesan urutannya dengan bilangan bulat ini.
Ini benar-benar dapat diterima untuk 99,9% dari aplikasi (kecuali jika Anda benar-benar harus menangani kasus tepi di atas). Selain itu, keberatan skeet terhadap runtime-nya valid, jadi jika Anda mengacak daftar panjang, Anda mungkin tidak ingin menggunakannya.
sumber
Ini telah muncul berkali-kali sebelumnya. Cari Fisher-Yates di StackOverflow.
Berikut adalah contoh kode C # yang saya tulis untuk algoritma ini. Anda dapat membuat parameter pada beberapa jenis lain, jika Anda mau.
sumber
Random
variabel statis seperti ini -Random
tidak aman untuk thread. Lihat csharpindepth.com/Articles/Chapter12/Random.aspxRandom
adalah rasa sakit untuk digunakan, seperti disebutkan dalam artikel saya.Sepertinya algoritma pengocokan yang baik, jika Anda tidak terlalu khawatir dengan kinerja. Satu-satunya masalah yang saya tunjukkan adalah bahwa perilakunya tidak dapat dikontrol, jadi Anda mungkin kesulitan mengujinya.
Salah satu opsi yang mungkin adalah memiliki seed untuk dikirimkan sebagai parameter ke generator angka acak (atau generator acak sebagai parameter), sehingga Anda dapat memiliki lebih banyak kontrol dan mengujinya dengan lebih mudah.
sumber
Saya menemukan jawaban Jon Skeet sepenuhnya memuaskan, tetapi robo-scanner klien saya akan melaporkan
Random
kesalahan keamanan. Jadi saya menukarnyaSystem.Security.Cryptography.RNGCryptoServiceProvider
. Sebagai bonus, itu memperbaiki masalah keamanan thread yang disebutkan. Di sisi lain,RNGCryptoServiceProvider
telah diukur 300x lebih lambat daripada menggunakanRandom
.Pemakaian:
Metode:
sumber
Mencari algoritma? Anda bisa menggunakan
ShuffleList
kelas saya :Lalu, gunakan seperti ini:
Bagaimana cara kerjanya?
Mari kita ambil daftar awal yang diurutkan dari 5 bilangan bulat pertama:
{ 0, 1, 2, 3, 4 }
.Metode ini dimulai dengan menghitung nubmer elemen dan menyebutnya
count
. Kemudian, dengancount
menurun pada setiap langkah, dibutuhkan angka acak antara0
dancount
dan memindahkannya ke akhir daftar.Dalam contoh langkah demi langkah berikut, item yang dapat dipindahkan adalah miring , item yang dipilih dicetak tebal :
0 1 2 3 4
0 1 2 3 4
0 1 2 4 3
0 1 2 4 3
1 2 4 3 0
1 2 4 3 0
1 2 3 0 4
1 2 3 0 4
2 3 0 4 1
2 3 0 4 1
3 0 4 1 2
sumber
Algoritma ini mengocok dengan menghasilkan nilai acak baru untuk setiap nilai dalam daftar, lalu memesan daftar dengan nilai acak tersebut. Anggap saja sebagai menambahkan kolom baru ke tabel di memori, lalu mengisinya dengan GUID, lalu mengurutkan berdasarkan kolom itu. Sepertinya cara yang efisien bagi saya (terutama dengan gula lambda!)
sumber
Sedikit tidak berhubungan, tetapi di sini adalah metode yang menarik (yang meskipun benar-benar berlebihan, telah BENAR-BENAR diterapkan) untuk pembuatan gulungan dadu yang benar-benar acak!
Dice-O-Matic
Alasan saya memposting ini di sini, adalah bahwa ia membuat beberapa poin menarik tentang bagaimana para penggunanya bereaksi terhadap gagasan menggunakan algoritma untuk mengocok, lebih dari dadu yang sebenarnya. Tentu saja, di dunia nyata, solusi semacam itu hanya untuk ujung spektrum yang sangat ekstrem di mana keacakan memiliki dampak yang sangat besar dan mungkin dampaknya memengaruhi uang;).
sumber
Saya akan mengatakan bahwa banyak jawaban di sini seperti "Algoritma ini mengocok dengan menghasilkan nilai acak baru untuk setiap nilai dalam daftar, kemudian memesan daftar dengan nilai-nilai acak" mungkin sangat salah!
Saya akan berpikir bahwa ini TIDAK memberikan nilai acak untuk setiap elemen koleksi sumber. Sebagai gantinya mungkin ada semacam algoritma yang berjalan seperti Quicksort yang akan memanggil fungsi-perbandingan kira-kira n log n kali. Beberapa algortihm benar-benar mengharapkan fungsi perbandingan ini stabil dan selalu mengembalikan hasil yang sama!
Mungkinkah IEnumerableSorter memanggil fungsi bandingkan untuk setiap langkah algoritma misalnya quicksort dan setiap kali memanggil fungsi
x => r.Next()
untuk kedua parameter tanpa menyimpannya!Dalam hal ini Anda mungkin benar-benar mengacaukan algoritma pengurutan dan membuatnya jauh lebih buruk daripada harapan yang dibangun oleh algoritma tersebut. Tentu saja, pada akhirnya akan menjadi stabil dan mengembalikan sesuatu.
Saya mungkin memeriksanya nanti dengan meletakkan output debugging di dalam fungsi "Next" baru jadi lihat apa yang terjadi. Di Reflector saya tidak bisa segera mengetahui cara kerjanya.
sumber
Waktu mulai berjalan pada kode dengan menghapus semua utas dan cache setiap pengujian baru,
Kode pertama yang gagal. Ini berjalan pada LINQPad. Jika Anda mengikuti untuk menguji kode ini.
list.OrderBy (x => r.Next ()) menggunakan 38,6528 ms
list.OrderBy (x => Guid.NewGuid ()) menggunakan 36,7634 ms (Disarankan dari MSDN.)
setelah kedua kalinya keduanya digunakan dalam waktu yang sama.
EDIT: TEST CODE pada Intel Core i7 [email protected], Ram 8 GB DDR3 @ 1600, HDD SATA 5200 rpm dengan [Data: www.dropbox.com/s/pbtmh5s9lw285kp/data]
Deskripsi Hasil: https://www.dropbox.com/s/9dw9wl259dfs04g/ResultDescription.PNG
Hasil Statistik: https://www.dropbox.com/s/ewq5ybtsvesme4d/ResultStat.PNG
Kesimpulan:
Asumsikan: LINQ OrderBy (r.Next ()) dan OrderBy (Guid.NewGuid ()) tidak lebih buruk daripada Metode Shuffle Buatan Pengguna dalam Solusi Pertama.
Jawaban: Mereka kontradiksi.
sumber