Получить случайный элемент из последовательной коллекции

Я говорю с API, который дает мне java.util.Iterator по коллекции. Это означает, что я могу перебирать его, но я не могу получить прямой/случайный доступ к элементам.

теперь моя проблема: я хочу получить один случайный элемент из этой коллекции. Как мне это сделать? Думаю, я мог бы создать новую коллекцию, которая позволяет прямой доступ, но разве это не потребляет немного памяти? Я также мог бы перебирать всю коллекцию и для каждого элемента "бросать кости", чтобы увидеть, должен ли я взять этот элемент и выйти из итерации или продолжить. Но тогда мне нужен размер коллекции, и я не могу получить это от итератора.

спасибо заранее.

6 ответов


есть способ сделать это за один проход через коллекцию, которая не использует много дополнительной памяти (только размер одного элемента коллекции плюс float). В псевдокоде:

  • выполните итерацию по коллекции.
  • для каждого элемента создайте случайный поплавок.
  • Если float является самым низким (или самым высоким, это не имеет значения), который вы видели до сих пор, сохраните текущий элемент из коллекции во временной переменной. (Также храните новый самый низкий случайное значение.)
  • как только вы достигнете конца коллекции, у вас есть случайный элемент в переменной temp.

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

обновление: название этой проблемы, наконец, вернулось ко мне. Это называется отбор проб пластовых.


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

public static <T> T selectRandom(final Iterator<T> iter, final Random random) {
    if (!iter.hasNext()) {
        throw new IllegalArgumentException();
    }
    if (random == null) {
        throw new NullPointerException();
    }
    T selected = iter.next();
    int count = 1;
    while (iter.hasNext()) {
        final T current = iter.next();
        ++count;
        if (random.nextInt(count) == 0) {
            selected = current;
        }
    }
    return selected;
}

(отказ от переполнения стека: не скомпилирован и, конечно, не протестирован.)

см. также раздел о Collections.shuffle в Java Puzzlers.


единственное безопасное решение (в случае, если дополнительная информация не известна / гарантирована) - это то, как вы описали: Создать List с Iterator и выберите случайный элемент.

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

кстати: вы действительно используете коллекцию, которая реализует java.util.Iterator?


Это зависит от требований, если размер коллекции не огромен, то это сделает это, иначе вы должны повторить и использовать метод dice, который вы упомянули

List<Object> list = Arrays.asList(yourCollection.toArray(new Object[0]));
result = list.get(new Random().nextInt(list.size()));

используется для генерации взвешенных тестовых данных. это не эффективно, но легко

class ProbabilitySet<E> {

    Set<Option<E>> options =  new HashSet<Option<E>>(); 

    class Option<E> {
        E object;
        double min;
        double max;

        private Option(E object, double prob) {
            this.object = object;
            min = totalProb;
            max = totalProb + prob;
        }

        @Override
        public String toString() {
            return "Option [object=" + object + ", min=" + min + ", max=" + max + "]";
        }
    }

    double totalProb = 0;
    Random rnd = new Random();

    public void add(E object, double probability){
        Option<E> tuple = new Option<E>(object, probability);
        options.add(tuple);
        totalProb += probability;
    }

    public E getRandomElement(){

        double no = rnd.nextDouble() * totalProb;
        for (Option<E> tuple : options) {
            if (no >= tuple.min && no < tuple.max){
                return tuple.object;
            }
        }


        return null;  // if this happens sumfink is wrong.

    }

    @Override
    public String toString() {
        return "ProbabilitySet [options=" + options + ", totalProb=" + totalProb + "]";
    }

}

Примечание: параметры вероятности будут относительно общего числа не до 1,0

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

public static void main(String[] args) {
    ProbabilitySet<String> stati = new ProbabilitySet<String>();
    stati.add("TIMEOUT", 0.2);
    stati.add("FAILED", 0.2);
    stati.add("SUCCESSFUL", 1.0);

    for (int i = 0; i < 100; i++) {
        System.out.println(stati.getRandomElement());
    }

}

Если у вас действительно нет произвольного доступа, и у вас очень большой список, поэтому вы не можете его скопировать, вы можете сделать следующее:

int n = 2
iterator i = ...
Random rand = new Random();
Object candidate = i.next();
while (i.hasNext()) {
    if (rand.nextInt(n)) {
        candidate = i.next();
    } else {
        i.next();
    }
    n++;
}
return candidate;

это сохранит случайный элемент из списка, но требует, чтобы вы пересекли весь список. Если вы хотите действительно равномерно распределенное значение, у вас нет выбора, кроме как сделать это.

альтернативно, если количество элементов мало, или если вы хотите случайную перестановку списка неизвестного размера (другими словами, вы хотите получить доступ все элементы списка в случайном порядке), то я рекомендую скопировать все ссылки на новый список (это не будет значительным объемом памяти, если у вас нет миллионов элементов, так как вы только храните ссылки). Затем используйте get со случайным целым числом или используйте стандартную java.утиль.Коллекции shuffle метод для перестановки списка.