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


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.