Выберите N случайных элементов из списка <T>в C # - PullRequest
141 голосов
/ 07 сентября 2008

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

Ответы [ 27 ]

0 голосов
/ 20 августа 2010

Вот мой подход (полный текст здесь http://krkadev.blogspot.com/2010/08/random-numbers-without-repetition.html).

Он должен работать в O (K) вместо O (N), где K - количество искомых элементов, а N - размер списка на выбор:

public <T> List<T> take(List<T> source, int k) {
 int n = source.size();
 if (k > n) {
   throw new IllegalStateException(
     "Can not take " + k +
     " elements from a list with " + n +
     " elements");
 }
 List<T> result = new ArrayList<T>(k);
 Map<Integer,Integer> used = new HashMap<Integer,Integer>();
 int metric = 0;
 for (int i = 0; i < k; i++) {
   int off = random.nextInt(n - i);
   while (true) {
     metric++;
     Integer redirect = used.put(off, n - i - 1);
     if (redirect == null) {
       break;
     }
     off = redirect;
   }
   result.add(source.get(off));
 }
 assert metric <= 2*k;
 return result;
}
0 голосов
/ 21 мая 2013

Это не так элегантно или эффективно, как принятое решение, но его быстро написать. Сначала перестановка массива случайным образом, затем выберите первые K элементов. В питоне

import numpy

N = 20
K = 5

idx = np.arange(N)
numpy.random.shuffle(idx)

print idx[:K]
0 голосов
/ 07 сентября 2008

Я недавно сделал это в своем проекте, используя идею, аналогичную Точка Тайлера 1 .
Я загружал кучу вопросов и выбирал пять наугад. Сортировка была достигнута с использованием IComparer .
Все вопросы были загружены в список 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 голосов
/ 28 января 2016

Когда N очень большое, обычный метод, который случайным образом перемешивает N чисел и выбирает, скажем, первые k чисел, может быть запретительным из-за сложности пространства. Для следующего алгоритма требуется только O (k) как для временной, так и для пространственной сложности.

http://arxiv.org/abs/1512.00501

def random_selection_indices(num_samples, N):
    modified_entries = {}
    seq = []
    for n in xrange(num_samples):
        i = N - n - 1
        j = random.randrange(i)

        # swap a[j] and a[i] 
        a_j = modified_entries[j] if j in modified_entries else j 
        a_i = modified_entries[i] if i in modified_entries else i

        if a_i != j:
            modified_entries[j] = a_i   
        elif j in modified_entries:   # no need to store the modified value if it is the same as index
            modified_entries.pop(j)

        if a_j != i:
            modified_entries[i] = a_j 
        elif i in modified_entries:   # no need to store the modified value if it is the same as index
            modified_entries.pop(i)
        seq.append(a_j)
    return seq
0 голосов
/ 09 января 2016
public static IEnumerable<T> GetRandom<T>(this IList<T> list, int count, Random random)
    {
        // Probably you should throw exception if count > list.Count
        count = Math.Min(list.Count, count);

        var selectedIndices = new SortedSet<int>();

        // Random upper bound
        int randomMax = list.Count - 1;

        while (selectedIndices.Count < count)
        {
            int randomIndex = random.Next(0, randomMax);

            // skip over already selected indeces
            foreach (var selectedIndex in selectedIndices)
                if (selectedIndex <= randomIndex)
                    ++randomIndex;
                else
                    break;

            yield return list[randomIndex];

            selectedIndices.Add(randomIndex);
            --randomMax;
        }
    }

Память: ~ count
Сложность: O (количество 2 )

0 голосов
/ 10 декабря 2015

Использование LINQ с большими списками (когда стоит прикасаться к каждому элементу) И если вы можете жить с возможностью дублирования:

new int[5].Select(o => (int)(rnd.NextDouble() * maxIndex)).Select(i => YourIEnum.ElementAt(i))

Для моего использования у меня был список из 100.000 элементов, и из-за того, что они были извлечены из БД, я примерно вдвое (или лучше) потратил время по сравнению с rnd во всем списке.

Наличие большого списка значительно сократит шансы на дубликаты.

0 голосов
/ 18 августа 2015

Я бы использовал метод расширения.

    public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> elements, int countToTake)
    {
        var random = new Random();

        var internalList = elements.ToList();

        var selected = new List<T>();
        for (var i = 0; i < countToTake; ++i)
        {
            var next = random.Next(0, internalList.Count - selected.Count);
            selected.Add(internalList[next]);
            internalList[next] = internalList[internalList.Count - selected.Count];
        }
        return selected;
    }
...