2011-01-15 2 views
47

Как я могу взять n случайных элементов из ArrayList<E>? В идеале я хотел бы сделать последовательные вызовы метода take() для получения других элементов x без замены.Возьмите n случайных элементов из списка <E>?

+0

что взять() в ArrayList? – Nishant

+0

что у вас есть? Если вы получите еще один элемент x, вы можете снова выбрать элементы из предыдущего набора или все время должны быть разными, пока не будут выбраны ВСЕ элементы? (Затем, что дальше?) –

+0

Без замены. Когда у вас больше нет, вы ничего не получите. –

ответ

82

Два основных способа.

  1. Использование Random#nextInt(int):

    List<Foo> list = createItSomehow(); 
    Random random = new Random(); 
    Foo foo = list.get(random.nextInt(list.size())); 
    

    Это, однако, не гарантирует, что последовательные вызовы n возвращает уникальные элементы.

  2. Использование Collections#shuffle():

    List<Foo> list = createItSomehow(); 
    Collections.shuffle(list); 
    Foo foo = list.get(0); 
    

    Это позволяет получить n уникальные элементы от увеличивающегося индекса (при условии, что сам список содержит уникальные элементы).


В случае, если вам интересно, если есть поток подход Java 8; нет, нет встроенного. Нет такой вещи, как 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() вместо этого.

+0

У меня есть список из 4000 слов, и я должен получить 5 слов из этого списка каждый раз, когда я нажимаю кнопку обновления, использую второй вариант вашего ответа. Насколько это гарантирует, что я буду получать уникальные значения все время, то есть какая вероятность? – Prateek

+0

@Prateek: Если у вас есть вопрос, нажмите кнопку «Спросить вопрос». Не нажимайте кнопку «Добавить комментарий» или «Опубликовать ответ». – BalusC

+0

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

10

Если вы хотите последовательно выбрать n элементов из списка и быть в состоянии сделать это без замены снова и снова, вы, вероятно, лучше всего произвольно переставляете элементы, а затем снимаете куски в блоках n. Если вы произвольно переставляете список, вы гарантируете статистическую случайность для каждого выбранного вами блока. Возможно, самый простой способ сделать это - использовать Collections.shuffle.

+3

И самый простой способ сделать это - вызвать java.util.Collections.shuffle() – biziclop

4

Справедливый способ сделать это - пройти через список, на n-й итерации, рассчитывая вероятность того, следует ли выбрать n-й элемент, который по существу является долей числа элементов, которые вам еще нужно выбрать количество элементов, доступных в остальной части списка. Например: (. Этот код скопирован со страницы я написал некоторое время назад на picking a random sample from a list)

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; 
} 

4

Простой и понятный

// 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; 
+0

Я не видел корреляции между 'arrayList' и' targetList'. – David

+0

Он должен был быть targetList.add (arrayList.get (j)) – nomad

1

Используйте следующий класс:

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; 
    } 
} 
15

Большинство предлагаемых решений до сих пор предлагают либо полный перетасовать список, либо последовательно выбирая дом, проверяя уникальность и, если потребуется, повторите попытку.

Но мы можем воспользоваться алгоритмом Дурстенфельда (самым популярным вариантом Fisher-Yates в наши дни).

решения Durstenfeld является переместить «поразивший» номер до конца списка путем замены их с последним незатронутым номером на каждой итерации .

В связи с вышеизложенным, нам не нужно перетасовать весь список, но запустить цикл на столько шагов, как количество элементов, необходимых для возврата. Алгоритм гарантирует, что последние 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()); 
} 
+1

Спасибо, что указали это. У меня есть ситуация, когда мне нужно удалить небольшое количество элементов из большого списка, и я был уверен, что перетасовка всего списка - не лучший способ сделать это, но я все время зависал о том, как удалить несколько элементов из список одним махом. Сворачивание их до конца списка, а затем его усечение - изящное решение. – Matt

1

Keep выбрать случайный элемент и убедитесь, что вы не выбрали один и тот же элемент снова:

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; 
} 

В теории это может работать бесконечно, но на практике это прекрасно. Чем ближе вы получаете весь исходный список, тем медленнее время выполнения этого становится очевидным, но это не вопрос выбора случайного подсписка, не так ли?

Смежные вопросы