Apa cara terbaik untuk mengacak array string dengan .NET? Array saya berisi sekitar 500 string dan saya ingin membuat yang baru Arraydengan string yang sama tetapi dalam urutan acak.
Menggunakan paket MedallionRandom NuGet, ini hanya myArray.Shuffled().ToArray()(atau myArray.Shuffle()jika Anda ingin mengubah array saat ini)
ChaseMedallion
Jawaban:
171
Jika Anda menggunakan .NET 3.5, Anda dapat menggunakan kesejukan IEnumerable berikut (VB.NET, bukan C #, tetapi idenya harus jelas ...):
Random rnd=newRandom();string[]MyRandomArray=MyArray.OrderBy(x => rnd.Next()).ToArray();
Edit: OK dan ini kode VB.NET yang sesuai:
Dim rnd AsNewSystem.RandomDimMyRandomArray=MyArray.OrderBy(Function() rnd.Next()).ToArray()
Suntingan kedua, sebagai tanggapan terhadap komentar bahwa System.Random "tidak threadsafe" dan "hanya cocok untuk aplikasi mainan" karena mengembalikan urutan berbasis waktu: seperti yang digunakan dalam contoh saya, Random () sangat aman untuk thread, kecuali Anda mengizinkan rutin di mana Anda mengacak array untuk dimasukkan kembali, dalam hal ini Anda akan memerlukan sesuatu seperti lock (MyRandomArray)itu agar tidak merusak data Anda, yang akan melindungi rndjuga.
Juga, harus dipahami bahwa System.Random sebagai sumber entropi tidak terlalu kuat. Seperti dicatat dalam dokumentasi MSDN , Anda harus menggunakan sesuatu yang berasal dari System.Security.Cryptography.RandomNumberGeneratorjika Anda melakukan sesuatu yang berhubungan dengan keamanan. Sebagai contoh:
dua catatan: 1) System.Random bukan thread-safe (Anda sudah diperingatkan) dan 2) System.Random berdasarkan waktu, jadi jika Anda menggunakan kode ini dalam sistem yang sangat konkuren, dimungkinkan untuk dua permintaan untuk mendapatkan nilai yang sama (yaitu di webapps)
therealhoff
2
Hanya untuk memperjelas hal di atas, System.Random akan mengunggah dirinya sendiri menggunakan waktu saat ini, sehingga dua contoh yang dibuat secara bersamaan akan menghasilkan urutan "acak" yang
sama..Sistem.Random
8
Algoritma ini juga adalah O (n log n) dan bias oleh algoritma Qsort. Lihat jawaban saya untuk solusi O (n) yang tidak bias.
Matt Howells
9
Kecuali jika OrderBycache kunci sortir secara internal, ini juga memiliki masalah melanggar properti transitif dari perbandingan yang dipesan. Jika pernah ada verifikasi mode debug yang OrderBymenghasilkan hasil yang benar, maka secara teori itu bisa melempar pengecualian.
Implementasi berikut menggunakan algoritma Fisher-Yates AKA the Knuth Shuffle. Ini berjalan dalam waktu O (n) dan mengocok di tempat, jadi lebih baik melakukan daripada teknik 'urutkan secara acak', meskipun lebih banyak baris kode. Lihat di sini untuk beberapa pengukuran kinerja komparatif. Saya telah menggunakan System.Random, yang bagus untuk keperluan non-kriptografi. *
staticclassRandomExtensions{publicstaticvoidShuffle<T>(thisRandom rng, T[]array){int n =array.Length;while(n >1){int k = rng.Next(n--);
T temp =array[n];array[n]=array[k];array[k]= temp;}}}
Pemakaian:
vararray=newint[]{1,2,3,4};var rng =newRandom();
rng.Shuffle(array);
rng.Shuffle(array);// different order from first call to Shuffle
* Untuk array yang lebih panjang, agar jumlah permutasi (sangat besar) sama-sama memungkinkan, perlu untuk menjalankan generator nomor acak semu (PRNG) melalui banyak iterasi untuk setiap swap untuk menghasilkan cukup entropi. Untuk array 500 elemen, hanya sebagian kecil dari kemungkinan 500! permutasi akan dimungkinkan untuk diperoleh menggunakan PRNG. Namun demikian, algoritma Fisher-Yates tidak bias dan oleh karena itu shuffle akan sebagus RNG yang Anda gunakan.
Bukankah lebih baik mengubah parameter dan membuat penggunaan seperti array.Shuffle(new Random());..?
Ken Kin
Anda dapat menyederhanakan swap menggunakan Tuples pada framework 4.0 -> (array [n], array [k]) = (array [k], array [n]);
dynamichael
@ Ken Kin: Tidak, ini akan buruk. Alasannya adalah yang new Random()diinisialisasi dengan nilai seed berdasarkan waktu sistem saat ini, yang hanya memperbarui setiap ~ 16ms.
Matt Howells
Dalam beberapa tes cepat ini vs daftar removeAt solution, ada perbedaan kecil pada 999 elemen. Perbedaannya menjadi drastis pada 99999 int acak, dengan solusi ini pada 3ms dan yang lain pada 1810ms.
galamdring
18
Anda sedang mencari algoritma pengocokan, bukan?
Oke, ada dua cara untuk melakukan ini: orang yang pandai-tetapi-orang-selalu-tampaknya-salah paham-itu-dan-salah-jadi-mungkin-tidak-sepintar-pandai cara, dan cara bodoh-seperti-batu-tapi-siapa-peduli-karena-itu-bekerja.
Cara bodoh
Buat duplikat dari array pertama Anda, tetapi tandai setiap string dengan angka acak.
Urutkan array duplikat sehubungan dengan nomor acak.
Algoritma ini berfungsi dengan baik, tetapi pastikan generator angka acak Anda tidak akan menandai dua string dengan nomor yang sama. Karena yang disebut Paradox Ulang Tahun , ini terjadi lebih sering daripada yang Anda harapkan. Kompleksitas waktunya adalah O ( n log n ).
Cara cerdik
Saya akan menggambarkan ini sebagai algoritma rekursif:
Untuk mengocok array ukuran n (indeks dalam kisaran [0 .. n -1]):
jika n = 0
tidak melakukan apapun
jika n > 0
(langkah rekursif) mengocok elemen n -1 pertama dari array
pilih indeks acak, x , dalam kisaran [0 .. n -1]
menukar elemen pada indeks n -1 dengan elemen pada indeks x
Setara iteratif adalah berjalan iterator melalui array, bertukar dengan elemen acak saat Anda pergi bersama, tetapi perhatikan bahwa Anda tidak dapat bertukar dengan elemen setelah yang iterator tunjuk. Ini adalah kesalahan yang sangat umum, dan mengarah pada shuffle yang bias.
Algoritma ini sederhana tetapi tidak efisien, O (N 2 ). Semua "urutan oleh" algoritma biasanya O (N log N). Ini mungkin tidak membuat perbedaan di bawah ratusan ribu elemen tetapi akan untuk daftar besar.
var stringlist =...// add your values to stringlistvar r =newRandom();var res =newList<string>(stringlist.Count);while(stringlist.Count>0){var i = r.Next(stringlist.Count);
res.Add(stringlist[i]);
stringlist.RemoveAt(i);}
Alasan mengapa O (N 2 ) halus: List.RemoveAt () adalah operasi O (N) kecuali Anda menghapus secara berurutan dari akhir.
Ini memiliki efek yang sama dengan shuffle knuth, tetapi tidak seefisien itu, karena melibatkan pengurangan satu daftar dan mengisi ulang yang lain. Menukar item di tempat akan menjadi solusi yang lebih baik.
Nick Johnson
1
Saya menemukan ini elegan dan mudah dimengerti dan pada 500 string itu tidak membuat perbedaan ...
Sklivvz
4
Anda juga dapat membuat metode ekstensi dari Matt Howells. Contoh.
namespace System{publicstaticclassMSSystemExtenstions{privatestaticRandom rng =newRandom();publicstaticvoidShuffle<T>(this T[]array){
rng =newRandom();int n =array.Length;while(n >1){int k = rng.Next(n);
n--;
T temp =array[n];array[n]=array[k];array[k]= temp;}}}}
mengapa Anda membuat ulang rng setiap panggilan ke metode ... Anda mendeklarasikannya di tingkat kelas tetapi menggunakannya sebagai lokal ...
Yaron
1
Mengacak array adalah intensif karena Anda harus menggeser banyak string. Mengapa tidak hanya membaca secara acak dari array? Dalam kasus terburuk Anda bahkan bisa membuat kelas pembungkus dengan getNextString (). Jika Anda benar-benar perlu membuat array acak maka Anda bisa melakukan sesuatu seperti
for i =0-> i=array.length *5
swap two strings in random places
Pembacaan acak dari array cenderung mengenai beberapa item beberapa kali dan melewatkan yang lain!
Ray Hayes
Algoritma shuffle rusak. Anda harus membuat arbitrary 5 Anda menjadi sangat tinggi sebelum shuffle Anda tidak bias.
Pitarou
Buat Array dari indeks (integer). Kocok indeks. Cukup gunakan indeks dalam urutan acak itu. Tidak ada duplikat, tidak ada shuffling sekitar referensi string dalam memori (yang masing-masing dapat memicu magang dan apa yang tidak).
Christopher
1
Hanya dengan memikirkan dari atas kepala saya, Anda dapat melakukan ini:
publicstring[]Randomize(string[] input){List<string> inputList = input.ToList();string[] output =newstring[input.Length];Random randomizer =newRandom();int i =0;while(inputList.Count>0){int index = r.Next(inputList.Count);
output[i++]= inputList[index];
inputList.RemoveAt(index);}return(output);}
Random r =newRandom();List<string>list=newList(originalArray);List<string> randomStrings =newList();while(list.Count>0){int i = r.Random(list.Count);
randomStrings.Add(list[i]);list.RemoveAt(i);}
Jacco, solusi Anda adalah IComparer khusus tidak aman. Sortir rutin memerlukan pembanding agar sesuai dengan beberapa persyaratan agar berfungsi dengan baik. Pertama di antara mereka adalah konsistensi. Jika pembanding dipanggil pada pasangan objek yang sama, ia harus selalu mengembalikan hasil yang sama. (perbandingannya juga harus transitif).
Kegagalan untuk memenuhi persyaratan ini dapat menyebabkan sejumlah masalah dalam penyortiran rutin termasuk kemungkinan loop tak terbatas.
Mengenai solusi yang mengaitkan nilai numerik acak dengan setiap entri dan kemudian mengurutkan berdasarkan nilai itu, ini mengarah ke bias yang melekat dalam output karena setiap kali dua entri diberi nilai numerik yang sama, keacakan output akan dikompromikan. (Dalam semacam rutin "stabil", mana yang pertama kali dalam input akan menjadi yang pertama di output. Array.Sort tidak kebetulan stabil, tetapi masih ada bias berdasarkan partisi yang dilakukan oleh algoritma Quicksort).
Anda perlu memikirkan tentang tingkat keacakan yang Anda butuhkan. Jika Anda menjalankan situs poker tempat Anda membutuhkan level acak kriptografi untuk melindungi dari penyerang yang teguh, Anda memiliki persyaratan yang sangat berbeda dari seseorang yang hanya ingin mengacak daftar lagu.
Untuk pengacakan daftar lagu, tidak ada masalah menggunakan PRNG yang diunggulkan (seperti System.Random). Untuk situs poker, itu bahkan bukan pilihan dan Anda perlu memikirkan masalah jauh lebih sulit daripada yang akan dilakukan siapa pun di stackoverflow. (menggunakan RNG kriptografi hanya permulaan, Anda perlu memastikan bahwa algoritme Anda tidak menimbulkan bias, bahwa Anda memiliki sumber entropi yang memadai, dan bahwa Anda tidak memaparkan keadaan internal yang akan membahayakan keacakan berikutnya).
Posting ini sudah dijawab dengan cukup baik - gunakan implementasi Durstenfeld dari shuffle Fisher-Yates untuk hasil yang cepat dan tidak bias. Bahkan ada beberapa implementasi yang diposting, meskipun saya perhatikan beberapa sebenarnya salah.
Ok, ini jelas merupakan kesalahan besar dari pihak saya (meminta maaf ...), tetapi saya sering menggunakan metode yang cukup umum dan kuat secara kriptografis.
publicstaticclassEnumerableExtensions{staticreadonlyRNGCryptoServiceProviderRngCryptoServiceProvider=newRNGCryptoServiceProvider();publicstaticIEnumerable<T>Shuffle<T>(thisIEnumerable<T> enumerable){var randomIntegerBuffer =newbyte[4];Func<int> rand =()=>{RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);returnBitConverter.ToInt32(randomIntegerBuffer,0);};returnfrom item in enumerable
let rec =new{item, rnd = rand()}orderby rec.rnd
select rec.item;}}
Shuffle () adalah ekstensi pada setiap IEnumerable sehingga mendapatkan, katakanlah, angka dari 0 hingga 1000 secara acak dalam daftar dapat dilakukan dengan
Enumerable.Range(0,1000).Shuffle().ToList()
Metode ini juga tidak akan memberikan kejutan ketika datang ke pengurutan, karena nilai pengurutan dihasilkan dan diingat tepat sekali per elemen dalam urutan.
Bagi saya rasanya Anda bisa meningkatkan efisiensi dan kemudahan dengan alih-alih mencoba mengocok Array dengan mendeklarasikan Array kedua, Anda lebih baik mencoba mengonversi ke Daftar, Mengocok dan kembali ke Array:sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
myArray.Shuffled().ToArray()
(ataumyArray.Shuffle()
jika Anda ingin mengubah array saat ini)Jawaban:
Jika Anda menggunakan .NET 3.5, Anda dapat menggunakan kesejukan IEnumerable berikut (VB.NET, bukan C #, tetapi idenya harus jelas ...):
Edit: OK dan ini kode VB.NET yang sesuai:
Suntingan kedua, sebagai tanggapan terhadap komentar bahwa System.Random "tidak threadsafe" dan "hanya cocok untuk aplikasi mainan" karena mengembalikan urutan berbasis waktu: seperti yang digunakan dalam contoh saya, Random () sangat aman untuk thread, kecuali Anda mengizinkan rutin di mana Anda mengacak array untuk dimasukkan kembali, dalam hal ini Anda akan memerlukan sesuatu seperti
lock (MyRandomArray)
itu agar tidak merusak data Anda, yang akan melindungirnd
juga.Juga, harus dipahami bahwa System.Random sebagai sumber entropi tidak terlalu kuat. Seperti dicatat dalam dokumentasi MSDN , Anda harus menggunakan sesuatu yang berasal dari
System.Security.Cryptography.RandomNumberGenerator
jika Anda melakukan sesuatu yang berhubungan dengan keamanan. Sebagai contoh:...
...
sumber
OrderBy
cache kunci sortir secara internal, ini juga memiliki masalah melanggar properti transitif dari perbandingan yang dipesan. Jika pernah ada verifikasi mode debug yangOrderBy
menghasilkan hasil yang benar, maka secara teori itu bisa melempar pengecualian.Implementasi berikut menggunakan algoritma Fisher-Yates AKA the Knuth Shuffle. Ini berjalan dalam waktu O (n) dan mengocok di tempat, jadi lebih baik melakukan daripada teknik 'urutkan secara acak', meskipun lebih banyak baris kode. Lihat di sini untuk beberapa pengukuran kinerja komparatif. Saya telah menggunakan System.Random, yang bagus untuk keperluan non-kriptografi. *
Pemakaian:
* Untuk array yang lebih panjang, agar jumlah permutasi (sangat besar) sama-sama memungkinkan, perlu untuk menjalankan generator nomor acak semu (PRNG) melalui banyak iterasi untuk setiap swap untuk menghasilkan cukup entropi. Untuk array 500 elemen, hanya sebagian kecil dari kemungkinan 500! permutasi akan dimungkinkan untuk diperoleh menggunakan PRNG. Namun demikian, algoritma Fisher-Yates tidak bias dan oleh karena itu shuffle akan sebagus RNG yang Anda gunakan.
sumber
array.Shuffle(new Random());
..?new Random()
diinisialisasi dengan nilai seed berdasarkan waktu sistem saat ini, yang hanya memperbarui setiap ~ 16ms.Anda sedang mencari algoritma pengocokan, bukan?
Oke, ada dua cara untuk melakukan ini: orang yang pandai-tetapi-orang-selalu-tampaknya-salah paham-itu-dan-salah-jadi-mungkin-tidak-sepintar-pandai cara, dan cara bodoh-seperti-batu-tapi-siapa-peduli-karena-itu-bekerja.
Cara bodoh
Algoritma ini berfungsi dengan baik, tetapi pastikan generator angka acak Anda tidak akan menandai dua string dengan nomor yang sama. Karena yang disebut Paradox Ulang Tahun , ini terjadi lebih sering daripada yang Anda harapkan. Kompleksitas waktunya adalah O ( n log n ).
Cara cerdik
Saya akan menggambarkan ini sebagai algoritma rekursif:
Setara iteratif adalah berjalan iterator melalui array, bertukar dengan elemen acak saat Anda pergi bersama, tetapi perhatikan bahwa Anda tidak dapat bertukar dengan elemen setelah yang iterator tunjuk. Ini adalah kesalahan yang sangat umum, dan mengarah pada shuffle yang bias.
Kompleksitas waktu adalah O ( n ).
sumber
Algoritma ini sederhana tetapi tidak efisien, O (N 2 ). Semua "urutan oleh" algoritma biasanya O (N log N). Ini mungkin tidak membuat perbedaan di bawah ratusan ribu elemen tetapi akan untuk daftar besar.
Alasan mengapa O (N 2 ) halus: List.RemoveAt () adalah operasi O (N) kecuali Anda menghapus secara berurutan dari akhir.
sumber
Anda juga dapat membuat metode ekstensi dari Matt Howells. Contoh.
Maka Anda bisa menggunakannya seperti:
sumber
Mengacak array adalah intensif karena Anda harus menggeser banyak string. Mengapa tidak hanya membaca secara acak dari array? Dalam kasus terburuk Anda bahkan bisa membuat kelas pembungkus dengan getNextString (). Jika Anda benar-benar perlu membuat array acak maka Anda bisa melakukan sesuatu seperti
* 5 adalah arbitrer.
sumber
Hanya dengan memikirkan dari atas kepala saya, Anda dapat melakukan ini:
sumber
Buat array float acak atau int dengan panjang yang sama. Urutkan array itu, dan lakukan swap yang sesuai pada array target Anda.
Ini menghasilkan jenis yang benar-benar independen.
sumber
sumber
Jacco, solusi Anda adalah IComparer khusus tidak aman. Sortir rutin memerlukan pembanding agar sesuai dengan beberapa persyaratan agar berfungsi dengan baik. Pertama di antara mereka adalah konsistensi. Jika pembanding dipanggil pada pasangan objek yang sama, ia harus selalu mengembalikan hasil yang sama. (perbandingannya juga harus transitif).
Kegagalan untuk memenuhi persyaratan ini dapat menyebabkan sejumlah masalah dalam penyortiran rutin termasuk kemungkinan loop tak terbatas.
Mengenai solusi yang mengaitkan nilai numerik acak dengan setiap entri dan kemudian mengurutkan berdasarkan nilai itu, ini mengarah ke bias yang melekat dalam output karena setiap kali dua entri diberi nilai numerik yang sama, keacakan output akan dikompromikan. (Dalam semacam rutin "stabil", mana yang pertama kali dalam input akan menjadi yang pertama di output. Array.Sort tidak kebetulan stabil, tetapi masih ada bias berdasarkan partisi yang dilakukan oleh algoritma Quicksort).
Anda perlu memikirkan tentang tingkat keacakan yang Anda butuhkan. Jika Anda menjalankan situs poker tempat Anda membutuhkan level acak kriptografi untuk melindungi dari penyerang yang teguh, Anda memiliki persyaratan yang sangat berbeda dari seseorang yang hanya ingin mengacak daftar lagu.
Untuk pengacakan daftar lagu, tidak ada masalah menggunakan PRNG yang diunggulkan (seperti System.Random). Untuk situs poker, itu bahkan bukan pilihan dan Anda perlu memikirkan masalah jauh lebih sulit daripada yang akan dilakukan siapa pun di stackoverflow. (menggunakan RNG kriptografi hanya permulaan, Anda perlu memastikan bahwa algoritme Anda tidak menimbulkan bias, bahwa Anda memiliki sumber entropi yang memadai, dan bahwa Anda tidak memaparkan keadaan internal yang akan membahayakan keacakan berikutnya).
sumber
Posting ini sudah dijawab dengan cukup baik - gunakan implementasi Durstenfeld dari shuffle Fisher-Yates untuk hasil yang cepat dan tidak bias. Bahkan ada beberapa implementasi yang diposting, meskipun saya perhatikan beberapa sebenarnya salah.
Saya menulis beberapa posting beberapa waktu lalu tentang menerapkan shuffles penuh dan parsial menggunakan teknik ini , dan (tautan kedua ini adalah tempat saya berharap untuk menambah nilai) juga posting lanjutan tentang bagaimana memeriksa apakah implementasi Anda tidak bias , yang dapat digunakan untuk memeriksa algoritma pengocokan. Anda dapat melihat di akhir posting kedua efek dari kesalahan sederhana dalam pemilihan angka acak dapat membuat.
sumber
Ok, ini jelas merupakan kesalahan besar dari pihak saya (meminta maaf ...), tetapi saya sering menggunakan metode yang cukup umum dan kuat secara kriptografis.
Shuffle () adalah ekstensi pada setiap IEnumerable sehingga mendapatkan, katakanlah, angka dari 0 hingga 1000 secara acak dalam daftar dapat dilakukan dengan
Metode ini juga tidak akan memberikan kejutan ketika datang ke pengurutan, karena nilai pengurutan dihasilkan dan diingat tepat sekali per elemen dalam urutan.
sumber
Anda tidak perlu algoritma yang rumit.
Hanya satu baris sederhana:
Perhatikan bahwa kita perlu mengonversi
Array
ke yangList
pertama, jika Anda tidak menggunakannyaList
di tempat pertama.Juga, ingatlah bahwa ini tidak efisien untuk array yang sangat besar! Kalau tidak bersih & sederhana.
sumber
Ini adalah solusi Konsol yang berfungsi lengkap berdasarkan contoh yang disediakan di sini :
sumber
sumber
Berikut cara sederhana menggunakan OLINQ:
sumber
sumber
sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();