У меня есть List<Double>
который содержит вероятности (веса) для выборки элемента. Например, List
содержит 5 значений следующим образом.
0,1, 0,4, 0,2, 0,1, 0,2
Каждое i-е значение Double
- это вероятность выборки i-го элемента другого List<Object>
.
Как я могу построить алгоритм для выполнения выборки в соответствии с этими вероятностями?
Я попробовал что-то вроде этого, где я впервые составил список вероятностей в кумулятивную форму.
0,1, 0,5, 0,7, 0,8, 1,0
Тогда мой подход выглядит следующим образом. Я генерирую случайный двойник и перебираю по списку, чтобы найти первый элемент, который больше, чем случайный двойной, а затем возвращает его индекс.
Random r = new Random();
double p = r.nextDouble();
int total = list.size();
for(int i=0; i < total; i++) {
double d = list.get(i);
if(d > p) {
return i;
}
}
return total-1;
Этот подход медленный, поскольку я просматриваю список последовательно. На самом деле мой список состоит из 800 000 элементов, связанных с весами (вероятностями), которые мне нужно отбирать. Поэтому, разумеется, этот последовательный подход медленный.
Я не знаю, как может помочь двоичный поиск. Пусть говорят, что я порожден р = 0,01. Затем двоичный поиск может использовать рекурсию следующим образом со списком.
compare 0.01 to 0.7, repeat with L = 0.1, 0.5 compare 0.01 to 0.1, stop compare 0.01 to 0.5, stop
0,01 меньше 0,7, 0,5 и 0,1, но я, очевидно, хочу только 0,1. Поэтому критерии остановки для меня все еще не ясны при использовании двоичного поиска.
Если мне нужна библиотека, чтобы помочь в этом, я тоже был бы заинтересован.
Вот как вы могли это сделать, используя бинарный поиск, начиная с кумулятивных вероятностей:
public static void main (String[] args) {
double[] cdf = {0.1, 0.5, 0.7, 0.8, 1.0};
double random = 0.75; // generate randomly between zero and one
int el = Arrays.binarySearch(cdf, random);
if (el < 0) {
el = -(el + 1);
}
System.out.println(el);
}
PS Когда список вероятностей короток, простое линейное сканирование может оказаться таким же эффективным, как бинарный поиск.
Это не самый эффективный с точки зрения памяти подход, но используйте NavigableMap, где ваши значения кумулятивного списка являются ключами. Тогда вы можете просто использовать floorEntry(randon.nextDouble())
. Как двоичный поиск, это log (n) пробел и n памяти.
Так...
NavigableMap<Double, Object> pdf = new TreeMap<>();
pdf.put(0.0, "foo");
pdf.put(0.1, "bar");
pdf.put(0.5, "baz");
pdf.put(0.7, "quz");
pdf.put(0.8, "quuz");
Random random = new Random();
pdf.floorEntry(random.nextDouble()).getValue();