Làm thế nào để truy cập mục ngẫu nhiên trong danh sách?


233

Tôi có một ArrayList và tôi cần có thể nhấp vào một nút và sau đó chọn ngẫu nhiên một chuỗi từ danh sách đó và hiển thị nó trong một hộp thông báo.

Tôi sẽ đi đâu để tới đó?

Câu trả lời:


403
  1. Tạo một thể hiện của Randomlớp ở đâu đó. Lưu ý rằng điều quan trọng là không tạo một phiên bản mới mỗi khi bạn cần một số ngẫu nhiên. Bạn nên sử dụng lại thể hiện cũ để đạt được sự đồng nhất trong các số được tạo. Bạn có thể có một statictrường ở đâu đó (cẩn thận về các vấn đề an toàn của luồng):

    static Random rnd = new Random();
  2. Yêu cầu Randomcá thể cung cấp cho bạn một số ngẫu nhiên với số lượng mục tối đa trong ArrayList:

    int r = rnd.Next(list.Count);
  3. Hiển thị chuỗi:

    MessageBox.Show((string)list[r]);

Có cách nào tốt để sửa đổi điều này để một số không lặp lại không? Giả sử tôi muốn xáo trộn một cỗ bài bằng cách chọn ngẫu nhiên một lần, nhưng rõ ràng không thể chọn cùng một thẻ hai lần.
AdamMc331

7
@ McAdam331 Tra cứu thuật toán Shuffle của Fisher-Yates: en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
Mehrdad Afshari

2
Đây có nên là "rnd.Next (list.Count-1)" thay vì "rnd.Next (list.Count)" để tránh truy cập phần tử max, có thể vượt quá chỉ số dựa trên 0 không?
B. Clay Shannon

22
@ B.ClayShannon Không. Phần trên trong Next(max)cuộc gọi là độc quyền.
Mehrdad Afshari

1
Thế còn khi danh sách trống?
tsu1980

137

Tôi thường sử dụng bộ sưu tập nhỏ các phương thức mở rộng này:

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());
    }
}

Đối với một danh sách được gõ mạnh, điều này sẽ cho phép bạn viết:

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

Nếu tất cả những gì bạn có là một ArrayList, bạn có thể truyền nó:

var strings = myArrayList.Cast<string>();

sự phức tạp của những gì là gì? bản chất lười biếng của IEnumerable có nghĩa là nó không phải là O (N)?
Dave Hillier

17
Câu trả lời này xáo trộn lại danh sách mỗi khi bạn chọn một số ngẫu nhiên. Sẽ hiệu quả hơn nhiều khi trả về giá trị chỉ mục ngẫu nhiên, đặc biệt là đối với các danh sách lớn. Sử dụng cái này trong PickRandom - return list[rnd.Next(list.Count)];
swax

Điều này không xáo trộn danh sách ban đầu, nó thực sự nằm trong một danh sách khác trong thực tế mà vẫn có thể không tốt cho hiệu quả nếu danh sách đủ lớn ..
nawfal

.OrderBy (.) Không tạo danh sách khác - Nó tạo một đối tượng thuộc loại IEnumerable <T> đang lặp qua danh sách gốc theo cách có thứ tự.
Johan Tidén

5
Thuật toán tạo GUID không thể đoán trước nhưng không ngẫu nhiên. RandomThay vào đó, hãy xem xét việc giữ một thể hiện ở trạng thái tĩnh.
Đại

90

Bạn có thể làm:

list.OrderBy(x => Guid.NewGuid()).FirstOrDefault()

Xinh đẹp. Trong ASP.NET MVC 4.5, với một danh sách, tôi đã phải thay đổi nó thành: list.OrderBy (x => Guid.NewGuid ()). FirstOrDefault ();
Andy Brown

3
Nó không thành vấn đề trong hầu hết các trường hợp nhưng điều này có lẽ chậm hơn nhiều so với sử dụng rnd. Tiếp theo. OTOH nó sẽ hoạt động trên IEnumerable <T>, không chỉ danh sách.
cá hòa tan 22/03/2015

12
Không chắc là ngẫu nhiên như thế nào. Hướng dẫn là duy nhất, không ngẫu nhiên.
pomber

1
Tôi nghĩ rằng phiên bản tốt hơn và mở rộng của câu trả lời này và nhận xét của @ solfish được tóm tắt độc đáo trong câu trả lời này (cộng với nhận xét của tôi ) cho một câu hỏi tương tự.
Neo

23

Tạo một Randomví dụ:

Random rnd = new Random();

Lấy một chuỗi ngẫu nhiên:

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

Tuy nhiên, hãy nhớ rằng nếu bạn làm điều này thường xuyên, bạn nên sử dụng lại Randomđối tượng. Đặt nó dưới dạng một trường tĩnh trong lớp để nó được khởi tạo chỉ một lần và sau đó truy cập nó.


20

Hoặc lớp mở rộng đơn giản như thế này:

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)];
    }
}

Sau đó chỉ cần gọi:

myList.RandomElement();

Làm việc cho mảng là tốt.

Tôi sẽ tránh gọi OrderBy()vì nó có thể tốn kém cho các bộ sưu tập lớn hơn. Sử dụng các bộ sưu tập được lập chỉ mục như List<T>hoặc mảng cho mục đích này.


3
Mảng trong .NET đã thực hiện IListnên quá tải thứ hai là không cần thiết.
Đại

3

Tại sao không:

public static T GetRandom<T>(this IEnumerable<T> list)
{
   return list.ElementAt(new Random(DateTime.Now.Millisecond).Next(list.Count()));
}

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());

3
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
gunr2171

3
Tôi có thể nói rằng, maxValuetham số của phương thức Nextchỉ nên là một số phần tử trong danh sách, không trừ đi một phần, bởi vì theo tài liệu " maxValue là giới hạn trên độc quyền của số ngẫu nhiên ".
David Ferenczy Rogožan

1

Tôi đã sử dụng ExtensionMethod này một thời gian:

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;
}

Bạn đã quên thêm cá thể lớp Ngẫu nhiên
bafsar

1

Tôi sẽ đề xuất cách tiếp cận khác nhau, Nếu thứ tự của các mục trong danh sách không quan trọng khi trích xuất (và mỗi mục chỉ nên được chọn một lần), thì thay vì Listbạn có thể sử dụng một mục ConcurrentBaglà bộ sưu tập không có thứ tự, an toàn theo chủ đề các đối tượng:

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

Người tổ chức sự kiện:

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

Ý TryTakechí sẽ cố gắng trích xuất một đối tượng "ngẫu nhiên" từ bộ sưu tập không có thứ tự.


0

Tôi cần nhiều mục hơn thay vì chỉ một. Vì vậy, tôi đã viết điều này:

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;
}

Với điều này, bạn có thể nhận được các yếu tố có bao nhiêu bạn muốn ngẫu nhiên như thế này:

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

var randomItemList = _allItems.GetSelectedRandom(10); 

0

In ngẫu nhiên tên quốc gia từ tệp JSON.
Mô hình:

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

Triển khai:

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);

-3

Tại sao không [2]:

public static T GetRandom<T>(this List<T> list)
{
     return list[(int)(DateTime.Now.Ticks%list.Count)];
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.