Як отримати доступ до випадкових елементів у списку?


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 Знайдіть алгоритм перемішування Fisher-Yates: 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>();

у чому полягає складність цих? чи означає лінивий характер Ізліченного, що це не 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. OTOH працюватиме над IEnumerable <T>, а не лише списками.
розчинна риба

12
Не впевнений, наскільки це випадково. Посібники унікальні, а не випадкові.
помбер

1
Я думаю, що краща та розширена версія цієї відповіді та коментар @ solublefish чудово підсумована у цій відповіді (плюс мій коментар ) до подібного питання.
Нео

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 class
bafsar

1

Я запропоную інший підхід. Якщо порядок елементів у списку не важливий при вилученні (і кожен елемент слід вибирати лише один раз), то замість цього Listви можете використовувати 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; }
    }

Реалізація:

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.