Як я можу взяти n випадкових елементів з an ArrayList<E>
? В ідеалі я хотів би мати можливість здійснювати послідовні виклики take()
методу, щоб отримати інші елементи x без заміни.
Як я можу взяти n випадкових елементів з an ArrayList<E>
? В ідеалі я хотів би мати можливість здійснювати послідовні виклики take()
методу, щоб отримати інші елементи x без заміни.
Відповіді:
Два основних способи.
Використання Random#nextInt(int)
:
List<Foo> list = createItSomehow();
Random random = new Random();
Foo foo = list.get(random.nextInt(list.size()));
Однак не гарантується, що послідовні n
виклики повертають унікальні елементи.
Використання Collections#shuffle()
:
List<Foo> list = createItSomehow();
Collections.shuffle(list);
Foo foo = list.get(0);
Це дозволяє отримувати n
унікальні елементи за збільшеним індексом (припускаючи, що сам список містить унікальні елементи).
Якщо вам цікаво, чи існує підхід Java 8 Stream; ні, вбудованого немає. Немає такої речі, як Comparator#randomOrder()
у стандартному API (ще?). Ви можете спробувати щось на кшталт нижче, все ще виконуючи суворий Comparator
контракт (хоча розподіл досить жахливий):
List<Foo> list = createItSomehow();
int random = new Random().nextInt();
Foo foo = list.stream().sorted(Comparator.comparingInt(o -> System.identityHashCode(o) ^ random)).findFirst().get();
Краще використовувати Collections#shuffle()
замість цього.
Більшість із запропонованих рішень дотепер пропонують або перетасування повного списку, або послідовний випадковий вибір, перевіряючи унікальність та повторюючи, якщо потрібно.
Але ми можемо скористатися алгоритмом Дюрстенфельда (найпопулярніший у наш час варіант Фішер-Йейтса).
Рішення Дурстенфельда полягає в тому, щоб перемістити "зачеплені" числа в кінець списку, помінявши їх на останній незнімений номер на кожній ітерації.
Через вищесказане, нам не потрібно перемішувати весь список , а запускати цикл на стільки етапів, скільки потрібно елементів для повернення. Алгоритм гарантує, що останні N елементів у кінці списку є 100% випадковими, якщо ми використовували ідеальну випадкову функцію.
Серед багатьох реальних сценаріїв, коли нам потрібно вибрати заздалегідь визначену (макс.) Кількість випадкових елементів із масивів / списків, цей оптимізований метод дуже корисний для різних карткових ігор, таких як Texas Poker, де ви апріорі знаєте кількість карт, що використовуватимуться в грі; із колоди зазвичай потрібно лише обмежена кількість карт.
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());
}
Якщо ви хочете послідовно вибирати n елементів зі списку і мати можливість це робити без заміни знову і знову і знову, вам, мабуть, найкраще випадково переставляти елементи, а потім знімати фрагменти в блоках n. Якщо ви випадково переставляєте список, ви гарантуєте статистичну випадковість для кожного вибраного вами блоку. Мабуть, найпростішим способом зробити це було б використання Collections.shuffle
.
Просто і зрозуміло
// define ArrayList to hold Integer objects
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < maxRange; i++) {
arrayList.add(i + 1);
}
// shuffle list
Collections.shuffle(arrayList);
// adding defined amount of numbers to target list
ArrayList<Integer> targetList = new ArrayList<>();
for (int j = 0; j < amount; j++) {
targetList.add(arrayList.get(j));
}
return targetList;
arrayList
і targetList
.
Справедливим способом зробити це є перегляд списку на n-ій ітерації, обчислюючи ймовірність вибору n-го елементу чи ні, що є, по суті, часткою кількості елементів, які вам все-таки потрібно, щоб вибрати кількість елементів доступні в іншій частині списку. Наприклад:
public static <T> T[] pickSample(T[] population, int nSamplesNeeded, Random r) {
T[] ret = (T[]) Array.newInstance(population.getClass().getComponentType(),
nSamplesNeeded);
int nPicked = 0, i = 0, nLeft = population.length;
while (nSamplesNeeded > 0) {
int rand = r.nextInt(nLeft);
if (rand < nSamplesNeeded) {
ret[nPicked++] = population[i];
nSamplesNeeded--;
}
nLeft--;
i++;
}
return ret;
}
(Цей код скопійовано зі сторінки, яку я написав деякий час тому, вибравши випадкову вибірку зі списку .)
Використовуйте наступний клас:
import java.util.Enumeration;
import java.util.Random;
public class RandomPermuteIterator implements Enumeration<Long> {
int c = 1013904223, a = 1664525;
long seed, N, m, next;
boolean hasNext = true;
public RandomPermuteIterator(long N) throws Exception {
if (N <= 0 || N > Math.pow(2, 62)) throw new Exception("Unsupported size: " + N);
this.N = N;
m = (long) Math.pow(2, Math.ceil(Math.log(N) / Math.log(2)));
next = seed = new Random().nextInt((int) Math.min(N, Integer.MAX_VALUE));
}
public static void main(String[] args) throws Exception {
RandomPermuteIterator r = new RandomPermuteIterator(100);
while (r.hasMoreElements()) System.out.print(r.nextElement() + " ");
}
@Override
public boolean hasMoreElements() {
return hasNext;
}
@Override
public Long nextElement() {
next = (a * next + c) % m;
while (next >= N) next = (a * next + c) % m;
if (next == seed) hasNext = false;
return next;
}
}
Продовжуйте вибирати випадковий елемент і переконайтеся, що не вибираєте той самий елемент знову:
public static <E> List<E> selectRandomElements(List<E> list, int amount)
{
// Avoid a deadlock
if (amount >= list.size())
{
return list;
}
List<E> selected = new ArrayList<>();
Random random = new Random();
int listSize = list.size();
// Get a random item until we got the requested amount
while (selected.size() < amount)
{
int randomIndex = random.nextInt(listSize);
E element = list.get(randomIndex);
if (!selected.contains(element))
{
selected.add(element);
}
}
return selected;
}
Теоретично це може тривати нескінченно, але на практиці це нормально. Чим ближче ви наближаєтесь до всього оригінального списку, тим повільніше виконується це, очевидно, але це не сенс вибору випадкового підсписку, чи не так?
Як зазначалося в інших відповідях, Collections.shuffle
не дуже ефективно, коли список джерел великий, через копіювання. Ось однолінійний Java 8, який:
Код:
private static <E> List<E> pickRandom(List<E> list, int n) {
return new Random().ints(n, 0, list.size()).mapToObj(list::get).collect(Collectors.toList());
}
Однак для списку без швидкого довільного доступу (як LinkedList) складність буде такою n*O(list_size)
.
Наступний клас отримує N елементів зі списку будь-якого типу. Якщо ви надаєте насіння, то при кожному запуску воно повертатиме той самий список, інакше елементи нового списку змінюватимуться при кожному запуску. Ви можете перевірити його поведінку під час запуску основних методів.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class NRandomItem<T> {
private final List<T> initialList;
public NRandomItem(List<T> list) {
this.initialList = list;
}
/**
* Do not provide seed, if you want different items on each run.
*
* @param numberOfItem
* @return
*/
public List<T> retrieve(int numberOfItem) {
int seed = new Random().nextInt();
return retrieve(seed, numberOfItem);
}
/**
* The same seed will always return the same random list.
*
* @param seed,
* the seed of random item generator.
* @param numberOfItem,
* the number of items to be retrieved from the list
* @return the list of random items
*/
public List<T> retrieve(int seed, int numberOfItem) {
Random rand = new Random(seed);
Collections.shuffle(initialList, rand);
// Create new list with the number of item size
List<T> newList = new ArrayList<>();
for (int i = 0; i < numberOfItem; i++) {
newList.add(initialList.get(i));
}
return newList;
}
public static void main(String[] args) {
List<String> l1 = Arrays.asList("Foo", "Bar", "Baz", "Qux");
int seedValue = 10;
NRandomItem<String> r1 = new NRandomItem<>(l1);
System.out.println(String.format("%s", r1.retrieve(seedValue, 2)));
}
}
Це рішення не змінює оригінальний список або іншим чином масштабує складність розміру списку.
Щоб отримати вибірку 4 зі списку 7, ми просто вибираємо випадковий елемент із усіх 7, потім вибираємо випадковий елемент із решти 6 тощо. Якщо ми вже вибрали індекси 4, 0, 3, далі генеруємо випадкове число з 0, 1, 2, 3, відповідно представляючи індекс 1, 2, 5, 6.
static Random rand = new Random();
static <T> List<T> randomSample(List<T> list, int size) {
List<T> sample = new ArrayList<>();
for (int sortedSampleIndices[] = new int[size], i = 0; i < size; i++) {
int index = rand.nextInt(list.size() - i);
int j = 0;
for (; j < i && index >= sortedSampleIndices[j]; j++)
index++;
sample.add(list.get(index));
for (; j <= i; j++) {
int temp = sortedSampleIndices[j];
sortedSampleIndices[j] = index;
index = temp;
}
}
return sample;
}
Усі ці відповіді вимагають модифікованого списку або виходу в дію
Ось швидкий фрагмент, який вимагав додаткового місця O (k) і гарантовано запускається за час O (k) і не потребує модифікованого масиву. (Виконує перемішування на карті)
func getRandomElementsFrom(array: [Int], count: Int = 8) -> [Int] {
if array.count <= count {
return array
}
var mapper = [Int: Int]()
var results = [Int]()
for i in 0..<count {
let randomIndex = Int.random(in: 0..<array.count - i)
if let existing = mapper[randomIndex] {
results.append(array[existing])
} else {
let element = array[randomIndex]
results.append(element)
}
let targetIndex = array.count - 1 - i
mapper[randomIndex] = mapper[targetIndex] ?? targetIndex
}
return results
}
Наступний метод повертає новий Список мінімальних (n, list.size ()) випадкових елементів, взятих зі списку списку параметрів. Майте на увазі, що список Списків змінюється після кожного дзвінка. Тому кожен виклик буде «споживати» вихідний список, повертаючи з нього n випадкових елементів:
public static <T> List<T> nextRandomN(List<T> list, int n) {
return Stream
.generate(() -> list.remove((int) (list.size() * Math.random())))
.limit(Math.min(list.size(), n))
.collect(Collectors.toList());
}
Зразок використання:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
System.out.println(nextRandomN(list, 3).toString());
System.out.println(nextRandomN(list, 3).toString());
System.out.println(nextRandomN(list, 3).toString());
System.out.println(nextRandomN(list, 3).toString());
Вихідні дані:
[8, 2, 3]
[4, 10, 7]
[1, 5, 9]
[6]