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

233

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

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

jay_t55
источник

Ответы:

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>Для этой цели используйте индексированные коллекции, такие как или массивы.

Dave_cz
источник
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); 
bafsar
источник
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);
Р.М. Шахидул Ислам Шахед
источник
-3

Почему бы не [2]:

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