Bagaimana cara mengakses item acak dalam daftar?

233

Saya memiliki ArrayList, dan saya harus dapat mengklik tombol dan kemudian secara acak memilih string dari daftar itu dan menampilkannya di kotak pesan.

Bagaimana saya bisa melakukan ini?

jay_t55
sumber

Jawaban:

404
  1. Buat instance Randomkelas di suatu tempat. Perhatikan bahwa sangat penting untuk tidak membuat instance baru setiap kali Anda membutuhkan nomor acak. Anda harus menggunakan kembali instance lama untuk mencapai keseragaman dalam angka yang dihasilkan. Anda dapat memiliki staticbidang di suatu tempat (hati-hati dengan masalah keamanan utas):

    static Random rnd = new Random();
  2. Minta Randominstance untuk memberi Anda nomor acak dengan maksimum jumlah item dalam ArrayList:

    int r = rnd.Next(list.Count);
  3. Tampilkan string:

    MessageBox.Show((string)list[r]);
Mehrdad Afshari
sumber
Apakah ada cara yang baik untuk memodifikasi ini sehingga angka tidak terulang? Katakanlah saya ingin mengocok satu set kartu dengan secara acak memilih satu per satu, tetapi jelas tidak dapat memilih kartu yang sama dua kali.
AdamMc331
7
@ McAdam331 Mencari Algoritma Fisher-Yates Shuffle: en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
Mehrdad Afshari
2
Haruskah ini "rnd.Next (list.Count-1)" alih-alih "rnd.Next (list.Count)" untuk menghindari mengakses elemen max, yang akan menjadi salah satu di luar indeks berbasis 0 mungkin?
B. Clay Shannon
22
@ B.ClayShannon No. Upperbound dalam Next(max)panggilan ini eksklusif.
Mehrdad Afshari
1
Bagaimana dengan kapan daftar kosong?
tsu1980
137

Saya biasanya menggunakan kumpulan kecil metode ekstensi ini:

public static class EnumerableExtension
{
    public static T PickRandom<T>(this IEnumerable<T> source)
    {
        return source.PickRandom(1).Single();
    }

    public static IEnumerable<T> PickRandom<T>(this IEnumerable<T> source, int count)
    {
        return source.Shuffle().Take(count);
    }

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
    {
        return source.OrderBy(x => Guid.NewGuid());
    }
}

Untuk daftar yang sangat diketik, ini memungkinkan Anda untuk menulis:

var strings = new List<string>();
var randomString = strings.PickRandom();

Jika yang Anda miliki hanyalah ArrayList, Anda dapat mengirimkannya:

var strings = myArrayList.Cast<string>();
Mark Seemann
sumber
apa kompleksitasnya? apakah sifat malas dari IEnumerable berarti bahwa itu bukan O (N)?
Dave Hillier
17
Jawaban ini mengocok ulang daftar setiap kali Anda memilih nomor acak. Akan jauh lebih efisien untuk mengembalikan nilai indeks acak, terutama untuk daftar besar. Gunakan ini di PickRandom - return list[rnd.Next(list.Count)];
swax
Ini tidak mengacak daftar asli, tetapi pada daftar lain pada kenyataannya yang masih mungkin tidak baik untuk efisiensi jika daftar cukup besar ..
nawfal
.OrderBy (.) Tidak membuat daftar lain - Ia membuat objek bertipe IEnumerable <T> yang beriterasi melalui daftar asli dengan cara yang dipesan.
Johan Tidén
5
Algoritma pembuatan GUID tidak dapat diprediksi tetapi tidak acak. Pertimbangkan memegang instance Randomdalam keadaan statis sebagai gantinya.
Dai
90

Anda dapat melakukan:

list.OrderBy(x => Guid.NewGuid()).FirstOrDefault()
Felipe Fujiy Pessoto
sumber
Cantik. DI ASP.NET MVC 4.5, di daftar, saya harus mengubahnya ke: list.OrderBy (x => Guid.NewGuid ()). FirstOrDefault ();
Andy Brown
3
Tidak masalah dalam banyak kasus, tetapi ini mungkin jauh lebih lambat daripada menggunakan rnd.Next. OTOH itu akan berfungsi pada IEnumerable <T>, bukan hanya daftar.
solublefish
12
Tidak yakin seberapa acak itu. Panduan unik, bukan acak.
pomber
1
Saya pikir versi yang lebih baik dan lebih panjang dari jawaban ini dan komentar @ solublefish diringkas dengan baik dalam jawaban ini (ditambah komentar saya ) untuk pertanyaan serupa.
Neo
23

Buat sebuah Randominstance:

Random rnd = new Random();

Ambil string acak:

string s = arraylist[rnd.Next(arraylist.Count)];

Ingat juga, bahwa jika Anda sering melakukan ini, Anda harus menggunakan kembali Randomobjek. Letakkan sebagai bidang statis di kelas sehingga hanya diinisialisasi satu kali dan kemudian mengaksesnya.

Joey
sumber
20

Atau kelas ekstensi sederhana seperti ini:

public static class CollectionExtension
{
    private static Random rng = new Random();

    public static T RandomElement<T>(this IList<T> list)
    {
        return list[rng.Next(list.Count)];
    }

    public static T RandomElement<T>(this T[] array)
    {
        return array[rng.Next(array.Length)];
    }
}

Lalu panggil saja:

myList.RandomElement();

Berfungsi untuk array juga.

Saya akan menghindari menelepon OrderBy()karena bisa mahal untuk koleksi yang lebih besar. Gunakan koleksi yang diindeks seperti List<T>atau array untuk tujuan ini.

Dave_cz
sumber
3
Array dalam. NET sudah menerapkan IListsehingga kelebihan kedua tidak perlu.
Dai
3

Kenapa tidak:

public static T GetRandom<T>(this IEnumerable<T> list)
{
   return list.ElementAt(new Random(DateTime.Now.Millisecond).Next(list.Count()));
}
Lucas
sumber
2
ArrayList ar = new ArrayList();
        ar.Add(1);
        ar.Add(5);
        ar.Add(25);
        ar.Add(37);
        ar.Add(6);
        ar.Add(11);
        ar.Add(35);
        Random r = new Random();
        int index = r.Next(0,ar.Count-1);
        MessageBox.Show(ar[index].ToString());
Rajesh Varma
sumber
3
Sementara potongan kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan sangat membantu untuk meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, dan orang-orang itu mungkin tidak tahu alasan untuk saran kode Anda.
gunr2171
3
Saya akan mengatakan, bahwa maxValueparameter metode Nextharus hanya sejumlah elemen dalam daftar, bukan minus satu, karena menurut dokumentasi " maxValue adalah batas atas eksklusif dari nomor acak ".
David Ferenczy Rogožan
1

Saya telah menggunakan ExtensionMethod ini untuk sementara waktu:

public static IEnumerable<T> GetRandom<T>(this IEnumerable<T> list, int count)
{
    if (count <= 0)
      yield break;
    var r = new Random();
    int limit = (count * 10);
    foreach (var item in list.OrderBy(x => r.Next(0, limit)).Take(count))
      yield return item;
}
Carlos Toledo
sumber
Anda lupa menambahkan instance kelas acak
bafsar
1

Saya akan menyarankan pendekatan yang berbeda, Jika urutan item di dalam daftar tidak penting saat ekstraksi (dan setiap item harus dipilih hanya sekali), maka alih-alih ListAnda dapat menggunakan ConcurrentBagyang merupakan koleksi thread-safe, unordered dari benda:

var bag = new ConcurrentBag<string>();
bag.Add("Foo");
bag.Add("Boo");
bag.Add("Zoo");

EventHandler:

string result;
if (bag.TryTake(out result))
{
    MessageBox.Show(result);
}

The TryTakeakan berupaya mengambil sebuah "acak" objek dari koleksi unordered.

Shahar Shokrani
sumber
0

Saya perlu lebih banyak barang, bukan hanya satu. Jadi, saya menulis ini:

public static TList GetSelectedRandom<TList>(this TList list, int count)
       where TList : IList, new()
{
    var r = new Random();
    var rList = new TList();
    while (count > 0 && list.Count > 0)
    {
        var n = r.Next(0, list.Count);
        var e = list[n];
        rList.Add(e);
        list.RemoveAt(n);
        count--;
    }

    return rList;
}

Dengan ini, Anda bisa mendapatkan elemen berapa banyak yang Anda inginkan secara acak seperti ini:

var _allItems = new List<TModel>()
{
    // ...
    // ...
    // ...
}

var randomItemList = _allItems.GetSelectedRandom(10); 
bafsar
sumber
0

Mencetak nama negara secara acak dari file JSON.
Model:

public class Country
    {
        public string Name { get; set; }
        public string Code { get; set; }
    }

Implementaton:

string filePath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"..\..\..\")) + @"Data\Country.json";
            string _countryJson = File.ReadAllText(filePath);
            var _country = JsonConvert.DeserializeObject<List<Country>>(_countryJson);


            int index = random.Next(_country.Count);
            Console.WriteLine(_country[index].Name);
RM Shahidul Islam Shahed
sumber
-3

Kenapa tidak [2]:

public static T GetRandom<T>(this List<T> list)
{
     return list[(int)(DateTime.Now.Ticks%list.Count)];
}
Хидеки Матосува
sumber