Выберите N случайных элементов из списка < T > в C #

Мне нужен быстрый алгоритм для выбора 5 случайных элементов из общего списка. Например, я хотел бы получить 5 случайных элементов из List<string>.

144
задан 21.07.2016, 15:27

7 ответов

Выполните итерации через, и для каждого элемента делают вероятность из выбора = (число необходимый) / (число оставленный)

Поэтому, если бы у Вас было 40 объектов, первое имело бы 5/40 шанс того, чтобы быть выбранным. Если это, следующее имеет 4/39 шанс, иначе это имеет 5/39 шанс. К тому времени, когда Вы добираетесь до конца, у Вас будут свои 5 объектов, и часто у Вас будут все они перед этим.

123
ответ дан 05.10.2019, 12:16
  • 1
    Я чувствую, что это тонко неправильно. Кажется, что бэкэнд списка будет выбираться чаще, чем фронтэнд, поскольку бэкэнд будет видеть намного большие вероятности. Например, если первые 35 чисел не становятся выбранными, последние 5 чисел должны быть выбраны. Первое число будет только когда-либо видеть 5/40 шанс, но что последнее число будет видеть 1/1 чаще, чем 5/40 времена. Необходимо будет рандомизировать список сначала перед реализацией этого алгоритма. – Ankur Goel 23.02.2010, 00:52
  • 2
    хорошо, я выполнил этот алгоритм 10 миллионов раз в списке 40 элементов, каждого с 5/40 (.125) выстрел в то, чтобы быть выбранным, и затем выполнял то моделирование несколько раз. Оказывается, что это не равномерно распределяется. Элементы 16 - 22 получают underselected (16 =.123, 17 =.124), в то время как элемент 34 сверхвыбран (34 =.129). Элементы 39 и 40 также получают underselected, но не так (39 =.1247, 40 =.1246) – Ankur Goel 23.02.2010, 01:21
  • 3
    @Ankur: Я don' t верят that' s статистически значительный. Я полагаю, что существует индуктивное доказательство, что это обеспечит ровное распределение. – recursive 23.02.2010, 22:03
  • 4
    I' ve повторил ту же пробную версию 100 миллионов раз, и в моей пробной версии наименее выбранный объект выбирался меньше чем на 0,106% менее часто, чем наиболее часто выбираемый объект. – recursive 23.02.2010, 23:04
  • 5
    @recursive: доказательство почти тривиально. Мы знаем, как выбрать объекты K из K для любого K и как выбрать 0 объектов из N для любого N. Предположите, что мы знаем, что метод однородно выбирает K или K-1 объекты из N-1 > = K; тогда мы можем выбрать объекты K из N путем выбора первого объекта с вероятностью K/N и затем использования известного метода для выбора все еще необходимого K или K-1 объектов из остающегося N-1. – Ilmari Karonen 07.03.2012, 21:03
  • 6
    @MartijnPieters: Это довольно работает отлично! Спасибо! Целый сценарий: paste.pound-python.org/show/xoUPpsfFhtKssXBzLxBd Удаление моих предыдущих отказов. – bukzor 17.12.2013, 03:53

Я недавно сделал это на своем проекте с помощью идеи, подобной точка Tyler 1 .
я загружал набор вопросов и выбирал пять наугад. Сортировка была достигнута с помощью IComparer.
aAll вопросы были загружены в список QuestionSorter, который был тогда отсортирован с помощью функция Вида Списка и первые k элементы, где выбрано.

    private class QuestionSorter : IComparable<QuestionSorter>
    {
        public double SortingKey
        {
            get;
            set;
        }

        public Question QuestionObject
        {
            get;
            set;
        }

        public QuestionSorter(Question q)
        {
            this.SortingKey = RandomNumberGenerator.RandomDouble;
            this.QuestionObject = q;
        }

        public int CompareTo(QuestionSorter other)
        {
            if (this.SortingKey < other.SortingKey)
            {
                return -1;
            }
            else if (this.SortingKey > other.SortingKey)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }

Использование:

    List<QuestionSorter> unsortedQuestions = new List<QuestionSorter>();

    // add the questions here

    unsortedQuestions.Sort(unsortedQuestions as IComparer<QuestionSorter>);

    // select the first k elements
0
ответ дан 05.10.2019, 12:16

почему не что-то вроде этого:

 Dim ar As New ArrayList
    Dim numToGet As Integer = 5
    'hard code just to test
    ar.Add("12")
    ar.Add("11")
    ar.Add("10")
    ar.Add("15")
    ar.Add("16")
    ar.Add("17")

    Dim randomListOfProductIds As New ArrayList

    Dim toAdd As String = ""
    For i = 0 To numToGet - 1
        toAdd = ar(CInt((ar.Count - 1) * Rnd()))

        randomListOfProductIds.Add(toAdd)
        'remove from id list
        ar.Remove(toAdd)

    Next
'sorry i'm lazy and have to write vb at work :( and didn't feel like converting to c#
1
ответ дан 05.10.2019, 12:16

Это является лучшим, я мог придумать на первом сокращении:

public List<String> getRandomItemsFromList(int returnCount, List<String> list)
{
    List<String> returnList = new List<String>();
    Dictionary<int, int> randoms = new Dictionary<int, int>();

    while (randoms.Count != returnCount)
    {
        //generate new random between one and total list count
        int randomInt = new Random().Next(list.Count);

        // store this in dictionary to ensure uniqueness
        try
        {
            randoms.Add(randomInt, randomInt);
        }
        catch (ArgumentException aex)
        {
            Console.Write(aex.Message);
        } //we can assume this element exists in the dictonary already 

        //check for randoms length and then iterate through the original list 
        //adding items we select via random to the return list
        if (randoms.Count == returnCount)
        {
            foreach (int key in randoms.Keys)
                returnList.Add(list[randoms[key]]);

            break; //break out of _while_ loop
        }
    }

    return returnList;
}

Используя список randoms в диапазоне 1 - общее количество списка и затем просто получение по запросу тех объектов в списке, казалось, было лучшим способом, но использование Словаря для обеспечения уникальности является чем-то, что я все еще обдумываю.

Также примечание я использовал список строк, замена по мере необходимости.

1
ответ дан 05.10.2019, 12:16

От Драконы в Алгоритме , интерпретация в C#:

int k = 10; // items to select
var items = new List<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 });
var selected = new List<int>();
double needed = k;
double available = items.Count;
var rand = new Random();
while (selected.Count < k) {
   if( rand.NextDouble() < needed / available ) {
      selected.Add(items[(int)available-1])
      needed--;
   }
   available--;
}

Этот алгоритм выберет уникальный indicies списка объектов.

8
ответ дан 05.10.2019, 12:16
  • 1
    Только получите достаточно объекта в списке, но не доберитесь случайным образом. – culithay 01.03.2012, 09:47
  • 2
    Это работало. Спасибо! Из любопытства, что лучшее место должно найти информацию о некоторых из них менее зарегистрированными функциями ggplot? – sharoz 22.02.2020, 18:46

Это - на самом деле более трудная проблема, чем она походит, главным образом потому что много математически-правильных-решений на самом деле не позволят Вам поражать все возможности (больше на этом ниже).

Первый, вот некоторые легкие к реализации, correct-if-you-have-a-truly-random-number генератор:

(0) ответ Kyle, который является O (n).

(1) Генерируют список n пар [(0, рэнд), (1, рэнд), (2, рэнд)...], сортируют их по второй координате и используют первый k (для Вас, k=5) индексы для получения случайного подмножества. Я думаю, что это легко реализовать, хотя это - O (n, регистрируют n), время.

(2) Init пустой список s = [], который вырастет, чтобы быть индексами k случайных элементов. Выберите номер r в {0, 1, 2..., n-1} наугад, r = % рэнда n, и добавьте это к s. Затем возьмите r = % рэнда (n-1) и всуньте s; добавьте к r # элементы меньше, чем он в s для предотвращения коллизий. Затем возьмите r = % рэнда (n-2) и сделайте то же самое, и т.д. пока у Вас не будет k отличных элементов в s. Это имеет время выполнения худшего случая O (k^2). Таким образом для k < < n, это может быть быстрее. Если Вы сохраняете s отсортированным и дорожка, какие непрерывные интервалы он имеет, можно реализовать его в O (k, регистрируют k), но это - больше работы.

@Kyle - Вы правы, вообще-то, если задуматься я соглашаюсь с Вашим ответом. Я торопливо считал его сначала, и по ошибке думал, что Вы указывали для последовательного выбора каждого элемента с фиксированной вероятностью k/n, который будет неправильным - но адаптивный подход кажется корректным мне. Извините за это.

хорошо, и теперь для строки над заголовком: асимптотически (для фиксированного k, n растущий), существуют n^k/k! выбор k подмножества элемента из n элементов [это - приближение (n, выбирают k)]. Если n является большим, и k не является очень маленьким, то эти числа огромны. Лучшая длина цикла, на которую можно надеяться в любом стандартном генераторе случайных чисел на 32 бита, 2^32 = 256^4. Таким образом, если у нас есть список 1 000 элементов, и мы хотим выбрать 5 наугад, нет никакого способа, которым стандартный генератор случайных чисел поразит все возможности. Однако, пока Вы соглашаетесь с выбором, который хорошо работает для меньших наборов, и всегда "выглядит" случайным, тогда эти алгоритмы должны быть в порядке.

Приложение : После записи этого я понял, что это хитро для реализовывания идеи (2) правильно, таким образом, я хотел разъяснить этот ответ. Для получения O (k регистрируют k) время Вам нужна подобная массиву структура, которая поддерживает O (зарегистрируйте m), ищет и вставляет - сбалансированное двоичное дерево может сделать это. Используя такую структуру для создания массива, названного s вот некоторый псевдо-Python:

# Returns a container s with k distinct random numbers from {0, 1, ..., n-1}
def ChooseRandomSubset(n, k):
  for i in range(k):
    r = UniformRandom(0, n-i)                 # May be 0, must be < n-i
    q = s.FirstIndexSuchThat( s[q] - q > r )  # This is the search.
    s.InsertInOrder(q ? r + q : r + len(s))   # Inserts right before q.
  return s

я предлагаю пробежать несколько демонстрационных случаев, чтобы видеть, как это эффективно реализует вышеупомянутое английское объяснение.

26
ответ дан 05.10.2019, 12:16
  • 1
    для (1) можно переставить список быстрее, чем сортировка, для (2) Вы будете смещать свое распределение при помощи % – jk. 27.01.2010, 15:39
  • 2
    Вы не получаете достаточно кредита на этот потрясающий ответ – Korijn 25.03.2013, 10:59
  • 3
    Учитывая возражение Вы повысили о длине цикла rng, есть ли какой-либо способ, которым мы можем создавать алгоритм, который выберет все наборы с равной вероятностью? – Jonah 11.07.2013, 20:19
  • 4
    Я скорее хотел найти его с одним вызовом вместо 2 – mirelon 17.02.2020, 13:44

Я думаю, что выбранный ответ корректен и довольно сладок. Я реализовал его по-другому, хотя, поскольку я также хотел результат в произвольном порядке.

    static IEnumerable<SomeType> PickSomeInRandomOrder<SomeType>(
        IEnumerable<SomeType> someTypes,
        int maxCount)
    {
        Random random = new Random(DateTime.Now.Millisecond);

        Dictionary<double, SomeType> randomSortTable = new Dictionary<double,SomeType>();

        foreach(SomeType someType in someTypes)
            randomSortTable[random.NextDouble()] = someType;

        return randomSortTable.OrderBy(KVP => KVP.Key).Take(maxCount).Select(KVP => KVP.Value);
    }
16
ответ дан 05.10.2019, 12:16
  • 1
    ПОТРЯСАЮЩИЙ! Действительно вырученный меня! – Armstrongest 26.03.2009, 01:45
  • 2
    У Вас есть какая-либо причина не использовать новый Случайный (), который основан на Среде. TickCount по сравнению с DateTime. Теперь. Миллисекунда? – Lasse Espeholt 20.07.2010, 12:28
  • 3
    Нет, просто wasn' t зная, что значение по умолчанию существовало. – Frank Schwieterman 20.07.2010, 18:37
  • 4
    Улучшение randomSortTable: randomSortTable = someTypes. ToDictionary (x = > случайный. NextDouble (), y = > y); Сохраняет цикл foreach. – Keltex 12.08.2010, 20:01
  • 5
    Хорошо год поздно, но... Doesn' t это удаются к @ersin' s скорее более короткий ответ и won' t это перестало работать, если Вы получаете повторное случайное число (Где Ersin' s будет иметь предвзятость к первому объекту повторной пары), – Andiih 08.09.2011, 12:53
  • 6
    Похож на you' ve получил более хороший путь:) – simonmorley 17.02.2020, 13:44

Теги

Похожие вопросы