Как получить доступ к случайному элементу в списке?


233

У меня есть ArrayList, и мне нужно иметь возможность нажать кнопку, а затем случайным образом выбрать строку из этого списка и отобразить ее в окне сообщения.

Как бы я поступил так?

Ответы:


404
  1. Создайте экземпляр Randomкласса где-нибудь. Обратите внимание, что очень важно не создавать новый экземпляр каждый раз, когда вам нужно случайное число. Вы должны повторно использовать старый экземпляр для достижения однородности сгенерированных чисел. У вас может быть staticполе где-то (будьте осторожны в вопросах безопасности потоков):

    static Random rnd = new Random();
  2. Попросите Randomэкземпляр дать вам случайное число с максимальным количеством элементов в ArrayList:

    int r = rnd.Next(list.Count);
  3. Показать строку:

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

Есть ли хороший способ изменить это так, чтобы число не повторялось? Допустим, я хотел перетасовать колоду карт, выбирая по одному случайным образом, но, очевидно, не могу выбрать одну и ту же карту дважды.
— AdamMc331

7
@ McAdam331 Посмотрите на алгоритм Фишера-Йейтса Shuffle: en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
— Мехрдад Афшари

2
Должно ли это быть "rnd.Next (list.Count-1)" вместо "rnd.Next (list.Count)", чтобы избежать доступа к элементу max, который был бы один за пределами предположительно основанного на 0 индекса?
— Б. Клэй Шеннон

22
@ B.ClayShannon Нет. Верхняя граница в Next(max)вызове является эксклюзивной.
— Мехрдад Афшари

1
Как насчет того, когда список пуст?
— tsu1980

137

Я обычно использую эту небольшую коллекцию методов расширения:

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

Для строго типизированного списка это позволит вам написать:

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

Если у вас есть только ArrayList, вы можете разыграть его:

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

какова сложность тех? ленивая природа IEnumerable означает, что это не O (N)?
— Дэйв Хиллиер,

17
Этот ответ перетасовывает список каждый раз, когда вы выбираете случайное число. Было бы намного эффективнее возвращать случайное значение индекса, особенно для больших списков. Используйте это в PickRandom - return list[rnd.Next(list.Count)];
— swax

Это не перетасовывает оригинальный список, это фактически делает другой список, который все еще не может быть хорошим для эффективности, если список достаточно большой ..
— nawfal

.OrderBy (.) Не создает другой список - он создает объект типа IEnumerable <T>, который перебирает исходный список упорядоченным образом.
— Йохан Тиден

5
Алгоритм генерации GUID непредсказуем, но не является случайным. Попробуйте Randomвместо этого удерживать экземпляр в статическом состоянии.
— Дай

90

Ты можешь сделать:

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

Прекрасный. В ASP.NET MVC 4.5, используя список, мне пришлось изменить это на: list.OrderBy (x => Guid.NewGuid ()). FirstOrDefault ();
— Энди Браун

3
В большинстве случаев это не имеет значения, но это, вероятно, намного медленнее, чем использование rnd.Next. ОТО, он будет работать с IEnumerable <T>, а не только со списками.
— растворимая рыба

12
Не уверен, насколько это случайно. Гиды уникальны, а не случайны.
— pomber

1
Я думаю, что улучшенная и расширенная версия этого ответа и комментарий @olvedfish приятно обобщены в этом ответе (плюс мой комментарий ) на аналогичный вопрос.
— Neo

23

Создайте Randomэкземпляр:

Random rnd = new Random();

Получить случайную строку:

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

Помните, однако, что если вы делаете это часто, вы должны повторно использовать Randomобъект. Поместите его как статическое поле в классе, чтобы оно инициализировалось только один раз, а затем получите к нему доступ.


20

Или простой класс расширения, например:

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

Тогда просто позвоните:

myList.RandomElement();

Работает и для массивов.

Я бы не стал звонить, так OrderBy()как это может быть дорого для больших коллекций. List<T>Для этой цели используйте индексированные коллекции, такие как или массивы.


3
Массивы в .NET уже реализованы, IListпоэтому вторая перегрузка не нужна.
— Дай

3

Почему нет:

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
Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код.
— gunr2171

3
Я бы сказал, что maxValueпараметр метода Nextдолжен быть просто числом элементов в списке, а не минус один, потому что согласно документации « maxValue является исключительной верхней границей случайного числа ».
— Дэвид Ференци Рогожан

1

Я использую этот ExtensionMethod некоторое время:

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

Вы забыли добавить экземпляр класса Random
— bafsar

1

Я предложу другой подход. Если порядок элементов в списке не важен при извлечении (и каждый элемент должен быть выбран только один раз), то вместо «a» Listвы можете использовать a, ConcurrentBagкоторый является потокобезопасным, неупорядоченным набором объекты:

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

TryTakeБудет пытаться извлечь «случайный» объект из неупорядоченной коллекции.


0

Мне нужно было больше предметов вместо одного. Итак, я написал это:

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

С этим вы можете получить элементы, сколько вы хотите, так случайным образом, как это:

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

var randomItemList = _allItems.GetSelectedRandom(10); 

0

Печать случайного названия страны из файла JSON.
Модель:

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.