У меня есть ящик для лута, который я хочу заполнить случайным предметом. Но я хочу, чтобы у каждого предмета был свой шанс быть выбранным. Например:
- 5% шанс 10 золота
- 20% шанс меча
- 45% шанс щита
- 20% шанс брони
- 10% шанс зелья
Как я могу сделать так, чтобы я выбрал точно один из пунктов выше, где эти проценты являются соответствующими шансами на получение добычи?
Ответы:
Мягкое решение вероятностей
Жестко запрограммированное вероятностное решение имеет тот недостаток, что вам необходимо установить вероятности в своем коде. Вы не можете определить их во время выполнения. Это также трудно поддерживать.
Вот динамическая версия того же алгоритма.
Вот пример реализации в Java в форме класса шаблона, который вы можете создать для любого объекта, который использует ваша игра. Затем вы можете добавить объекты с помощью метода
.addEntry(object, relativeWeight)
и выбрать одну из записей, которые вы добавили ранее с.get()
Использование:
Вот тот же класс, реализованный в C # для вашего проекта Unity, XNA или MonoGame:
И вот один в JavaScript :
Pro:
Contra:
O(n)
сложность во время выполнения). Поэтому, когда у вас очень большой набор предметов и вы часто рисуете, это может стать медленным. Простая оптимизация заключается в том, чтобы сначала поставить наиболее вероятные элементы, чтобы в большинстве случаев алгоритм завершался рано. Более сложная оптимизация, которую вы можете сделать, состоит в том, чтобы использовать тот факт, что массив отсортирован, и выполнить поиск пополам. Это займетO(log n)
время.O(n)
худшее время выполнения)источник
Примечание: я создал библиотеку C # для этой конкретной проблемы
Другие решения хороши, если у вас есть только небольшое количество предметов, и ваши вероятности никогда не меняются. Однако с большим количеством элементов или изменением вероятностей (например, удаление элементов после их выбора) вам понадобится что-то более мощное.
Вот два наиболее распространенных решения (оба из которых включены в вышеуказанную библиотеку)
Метод псевдонима Уокера
Умное решение, которое очень быстро (
O(1)
!), Если ваши вероятности постоянны. По сути, алгоритм создает 2D-дартс («таблицу псевдонимов») из ваших вероятностей и бросает в нее дротик.Есть много статей в Интернете о том, как это работает, если вы хотите узнать больше.
Единственная проблема заключается в том, что если ваши вероятности изменяются, вам необходимо заново создать таблицу псевдонимов, что является медленным. Таким образом, если вам нужно удалить предметы после того, как они собраны, это не решение для вас.
Основанное на дереве решение
Другое распространенное решение - создать массив, в котором каждый элемент хранит сумму вероятности и всех элементов перед ним. Затем просто сгенерируйте случайное число из [0,1) и выполните двоичный поиск, где это число попадает в список.
Это решение очень легко кодировать / понимать, но выбор выполняется медленнее, чем метод псевдонима Уокера, и изменение вероятностей все еще происходит
O(n)
. Мы можем улучшить его, превратив массив в дерево бинарного поиска, где каждый узел отслеживает сумму вероятностей во всех элементах своего поддерева. Затем, когда мы генерируем число из [0,1), мы можем просто пройтись по дереву, чтобы найти элемент, который оно представляет.Это дает нам
O(log n)
возможность выбрать предмет и изменить вероятности! Это делаетNextWithRemoval()
очень быстро!Результаты
Вот несколько быстрых тестов из приведенной выше библиотеки, сравнивающих эти два подхода
Итак, как вы можете видеть, для особого случая статических (неизменяемых) вероятностей метод псевдонима Уокера работает примерно на 50-100% быстрее. Но в более динамичных случаях дерево на несколько порядков быстрее !
источник
nlog(n)
) при сортировке элементов по весу.Решение Колесо Фортуны
Вы можете использовать этот метод, когда вероятности в вашем пуле предметов имеют довольно большой общий знаменатель, и вам нужно очень часто использовать его.
Создайте массив опций. Но вставляйте каждый элемент в него несколько раз, причем количество дубликатов каждого элемента пропорционально его вероятности появления. В приведенном выше примере все элементы имеют вероятности, которые являются множителями 5%, поэтому вы можете создать массив из 20 элементов, например:
Затем просто выберите случайный элемент этого списка, сгенерировав одно случайное целое число от 0 до длины массива - 1.
Недостатки:
Преимущества:
источник
Epic Scepter of the Apocalypse
. Такой двухуровневый подход использует преимущества обоих подходов.[('gold', 1),('sword',4),...]
суммирую все веса, а затем выбрасываю случайное число от 0 до суммы, затем повторяю массив и вычисляю, где приземляется случайное число (т.е. аreduce
) Прекрасно работает с массивами, которые часто обновляются, и не требует значительных потерь памяти.Жестко-вероятностное решение
Самый простой способ найти случайный элемент из взвешенной коллекции - это пройти по цепочке операторов if-else, где каждое if-else увеличивается, вероятно, так как предыдущий не попадает.
Причина, по которой условные выражения равны его вероятности плюс все предыдущие возможности условных вычислений, заключается в том, что предыдущие условные обозначения уже исключили возможность того, что они являются этими элементами. Таким образом, для условного щита
else if(rand <= 70)
70 равно 45% шанса щита, плюс 5% шанса золота и 20% шанса меча.Преимущества:
Недостатки:
источник
В C # вы можете использовать сканирование Linq для запуска вашего аккумулятора, чтобы проверить случайное число в диапазоне от 0 до 100.0f и .First (), чтобы получить. Так что, как одна строка кода.
Так что-то вроде:
sum
является инициализированным нулем целым числом иa
является списком вероятностей / элементов структуры / кортежей / экземпляров.rand
ранее сгенерированное случайное число в диапазоне.Это просто накапливает сумму по списку диапазонов до тех пор, пока она не превысит ранее выбранное случайное число, и возвращает либо элемент, либо ноль, где ноль будет возвращено, если диапазон случайных чисел (например, 100) меньше общего диапазона взвешивания по ошибке и выбранное случайное число находится за пределами общего диапазона взвешивания.
Однако вы заметите, что веса в OP близко соответствуют нормальному распределению (кривая Белла). Я думаю, что в общем случае вам не понадобятся конкретные диапазоны, вы будете стремиться к распределению, которое сужается либо вокруг кривой колокола, либо только на убывающей экспоненциальной кривой (например). В этом случае вы можете просто использовать математическую формулу для генерации индекса в массив элементов, отсортированных в порядке предпочтительной вероятности. Хорошим примером является CDF в нормальном распределении
Также пример здесь .
Другой пример - вы можете взять случайное значение от 90 градусов до 180 градусов, чтобы получить нижний правый квадрант круга, взять компонент x с помощью cos (r) и использовать его для индексации в списке приоритетов.
С различными формулами вы можете иметь общий подход, при котором вы просто вводите список приоритетов любой длины (например, N) и отображаете результат формулы (например, cos (x) от 0 до 1) путем умножения (например, Ncos (x) ) = От 0 до N), чтобы получить индекс.
источник
Probabilities don’t need to be hard-coded. The items and the thresholds can be together in an array.
You do have to accumulate the thresholds still, but you can do it when creating a parameter file instead of coding it.
источник
random()
in the loop?I done this function: https://github.com/thewheelmaker/GDscript_Weighted_Random Now! in your case you can use it like this:
It gives just a number between 0 to 4 but you can put it in array where you got the items.
Or in function:
Here is the code. I made it on GDscript, you can, but it can alter other language, also check for logic errors:
It works like this: on_normal_case([50,50],0) This gives 0 or 1, it has same probability both.
on_normal_case([50,50],1) This gives 1 or 2, it has same probability both.
on_normal_case([20,80],1) This gives 1 or 2, it has bigger change to get two.
on_normal_case([20,80,20,20,30],1) This give random numbers range 1-5 and bigger numbers are more likely than smaller numbers.
on_normal_case([20,80,0,0,20,20,30,0,0,0,0,33],45) This throw dices between numbers 45,46,49,50,51,56 you see when there is zero it never occure.
So it function returns just one random number that depends lenght of that arrayy array and transformm number, and ints in the array are probability weights that a number might occure, where that number is location on the array, pluss transformm number.
источник