2011-01-04 5 views
9

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

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

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

+3

Коллекция обычно не должна быть класса, реализующих 'Iterator'. – thejh

+0

Является ли ваша коллекция 'java.util.Collection'? – thejh

+0

Потребление памяти не должно быть таким большим. Новая коллекция просто содержит указатели на фактические данные, поэтому размер нового объекта коллекции! = Размер коллекции. –

ответ

10

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

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

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

Обновление: Название этого типа проблем, наконец, вернулось ко мне. Это называется Reservoir sampling.

+3

То же самое, что и мое решение (кроме того, что я не использую float (btw, ints лучше). –

+0

@Tom: Это похоже на почти ту же основную идею. Почему «int» лучше? –

+0

@Bill the Lizard Инт даст вам больший разброс значений для заданного количества бит. Не нужно иметь дело со всем этим IEEE guff. –

7

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

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

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

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

+1

Я не так, как это случайное: с каждой итерацией вероятность того, что 'random.nextInt (count) == 0' будет ниже и ниже. –

+0

Когда я перехожу в список с одним элементом, есть одна итерация. 'count' получает значение' 2'. В половине случаев «null» будет возвращен для списка с одним элементом, не так ли? Так что это неправильно. – thejh

+2

@ tulskly Да, если вы, скажем, десятый элемент, то он имеет вероятность быть выбранным как 1/10. –

2

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

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

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

1

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

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

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

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.util.Collections shuffle для перестановки списка.

+1

Так же, как и мое решение. –

+0

Да. Вы добавили его, когда я печатал :-). –

1

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

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

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