Большинство предлагаемых решений до сих пор предлагают либо полный случайный список, либо последовательный случайный выбор, проверяя уникальность и, при необходимости, повторяя попытку.
Но мы можем воспользоваться преимуществами алгоритма Дюрстенфельда (самый популярныйВариант Yates в наши дни).
Решение Дюрстенфельда состоит в том, чтобы переместить «пораженные» числа в конец списка, поменяв их местами с последним нерушаемым числом на каждой итерации.
Из-за вышеизложенного, нам не нужно перетасовывать весь список , а запустить цикл столько раз, сколько элементов требуется вернуть.Алгоритм гарантирует, что последние N элементов в конце списка будут на 100% случайными, если мы использовали идеальную случайную функцию.
Среди многих реальных сценариев, где нам нужно выбрать заранее определенное (максимальное) количестводля случайных элементов из массивов / списков этот оптимизированный метод очень полезен для различных карточных игр, таких как Техасский покер, где вы априори знаете количество карт, которые будут использоваться в игре;из колоды обычно требуется ограниченное количество карт.
public static <E> List<E> pickNRandomElements(List<E> list, int n, Random r) {
int length = list.size();
if (length < n) return null;
//We don't need to shuffle the whole list
for (int i = length - 1; i >= length - n; --i)
{
Collections.swap(list, i , r.nextInt(i + 1));
}
return list.subList(length - n, length);
}
public static <E> List<E> pickNRandomElements(List<E> list, int n) {
return pickNRandomElements(list, n, ThreadLocalRandom.current());
}