【发布时间】:2012-03-08 23:30:17
【问题描述】:
我有一个物品清单。这些项目中的每一个都有自己的概率。
谁能推荐一种算法来根据概率挑选物品?
【问题讨论】:
标签: java list random probability
我有一个物品清单。这些项目中的每一个都有自己的概率。
谁能推荐一种算法来根据概率挑选物品?
【问题讨论】:
标签: java list random probability
示例代码:
double p = Math.random();
double cumulativeProbability = 0.0;
for (Item item : items) {
cumulativeProbability += item.probability();
if (p <= cumulativeProbability) {
return item;
}
}
【讨论】:
p 是任何随机数,所以我们怎么能说大多数概率项首先被选中.. 例如:[{A,10},{B,20}] 所以你怎么能说假设在第一次迭代p=2 所以2<=10 为真,第一个项目 {A,10} 被首先选中,尽管第二个项目的概率更大
r的值是一个均匀分布的随机数,也就是说r是某个值的概率等于所有其他值r 可能是。因此,列表中的项目不是“受欢迎的”,它们在列表中的位置无关紧要。
因此,每个项目都存储一个数字来标记其相对概率,例如,如果您有 3 个项目,其中一个项目被选中的可能性应该是其他两个项目的两倍,那么您的列表将包含:
[{A,1},{B,1},{C,2}]
然后对列表的数字求和(在我们的例子中是 4)。 现在生成一个随机数并选择该索引。 int index = rand.nextInt(4); 返回使索引在正确范围内的数字。
Java 代码:
class Item {
int relativeProb;
String name;
//Getters Setters and Constructor
}
...
class RandomSelector {
List<Item> items = new List();
Random rand = new Random();
int totalSum = 0;
RandomSelector() {
for(Item item : items) {
totalSum = totalSum + item.relativeProb;
}
}
public Item getRandom() {
int index = rand.nextInt(totalSum);
int sum = 0;
int i=0;
while(sum < index ) {
sum = sum + items.get(i++).relativeProb;
}
return items.get(Math.max(0,i-1));
}
}
【讨论】:
p 是任何随机数,所以我们怎么能说大多数概率项首先被选中.. 例如:[{A,10},{B,20}] 那么我们怎么能说假设在第一次迭代p=2 所以2<=10 为真,第一个项目 {A,10} 被首先选中,即使第二个项目的概率更大
int index = rand.nextInt(totalSum) + 1 替换int index = rand.nextInt(totalSum) 然后当然取出多余的Math.max,这对我有用。没有这个+1,我得到的列表中“基本”(和第一个)元素的数量是预期的两倍,即默认情况下,列表中的第一个元素的相对可能性为 1,其他一切都与此相关。
假设我们有以下列表
Item A 25%
Item B 15%
Item C 35%
Item D 5%
Item E 20%
假设所有概率都是整数,并为每个项目分配一个“范围”,计算如下。
Start - Sum of probability of all items before
End - Start + own probability
新号码如下
Item A 0 to 25
Item B 26 to 40
Item C 41 to 75
Item D 76 to 80
Item E 81 to 100
现在从 0 到 100 中选择一个随机数。假设您选择 32。32 属于项目 B 的范围。
mj
【讨论】:
你可以试试Roulette Wheel Selection。
首先,将所有概率相加,然后通过将每个概率除以总和,以 1 的比例缩放所有概率。假设概率是A(0.4), B(0.3), C(0.25) and D(0.05)。然后您可以在 [0, 1] 范围内生成一个随机浮点数。现在你可以这样决定:
random number between 0.00 and 0.40 -> pick A
between 0.40 and 0.70 -> pick B
between 0.70 and 0.95 -> pick C
between 0.95 and 1.00 -> pick D
你也可以用随机整数来做——比如你生成一个介于 0 到 99(含)之间的随机整数,然后你可以像上面一样做出决定。
【讨论】:
Ushman's、Brent's 和@kaushaya 的答案中描述的算法在Apache commons-math 库中实现。
看看EnumeratedDistribution类(groovy代码如下):
def probabilities = [
new Pair<String, Double>("one", 25),
new Pair<String, Double>("two", 30),
new Pair<String, Double>("three", 45)]
def distribution = new EnumeratedDistribution<String>(probabilities)
println distribution.sample() // here you get one of your values
请注意,概率总和不必等于 1 或 100 - 它会自动归一化。
【讨论】:
我的方法很简单。生成一个随机数。现在由于您的项目的概率是已知的,只需遍历排序的概率列表并选择概率小于随机生成的数字的项目。
更多详情,请阅读我的回答here。
【讨论】:
一种缓慢但简单的方法是让每个成员根据其概率选择一个随机数,然后选择具有最高值的一个。
类比:
假设需要选择 3 个人中的 1 个,但他们的概率不同。你给他们死了不同数量的面孔。第一人的骰子有 4 面,第二人的骰子有 6,第三人的骰子有 8。他们掷骰子,数字最大的人获胜。
假设我们有以下列表:
[{A,50},{B,100},{C,200}]
伪代码:
A.value = random(0 to 50);
B.value = random(0 to 100);
C.value = random (0 to 200);
我们选择价值最高的那个。
上面的这个方法并不能准确地映射概率。例如,100 的机会不会是 50 的两倍。但是我们可以通过稍微调整方法来做到这一点。
方法二
我们可以将它们从前一个变量的上限限制到当前变量的加法,而不是从 0 到权重。
[{A,50},{B,100},{C,200}]
伪代码:
A.lowLimit= 0; A.topLimit=50;
B.lowLimit= A.topLimit+1; B.topLimit= B.lowLimit+100
C.lowLimit= B.topLimit+1; C.topLimit= C.lowLimit+200
产生的限制
A.limits = 0,50
B.limits = 51,151
C.limits = 152,352
然后我们从 0 到 352 中选择一个随机数,并将其与每个变量的限制进行比较,以查看随机数是否在其限制范围内。
我相信这个调整有更好的性能,因为只有 1 个随机生成。
其他答案中也有类似的方法,但此方法不要求总数为 100 或 1.00。
【讨论】:
Brent's answer 很好,但它没有考虑在 p = 0 的情况下错误选择概率为 0 的项目的可能性。通过检查概率(或者可能不添加项目):
double p = Math.random();
double cumulativeProbability = 0.0;
for (Item item : items) {
cumulativeProbability += item.probability();
if (p <= cumulativeProbability && item.probability() != 0) {
return item;
}
}
【讨论】:
所有提到的解决方案都有线性努力。以下只有对数的努力,也处理非标准化的概率。我建议使用 TreeMap 而不是 List:
import java.util.*;
import java.util.stream.IntStream;
public class ProbabilityMap<T> extends TreeMap<Double,T>{
private static final long serialVersionUID = 1L;
public static Random random = new Random();
public double sumOfProbabilities;
public Map.Entry<Double,T> next() {
return ceilingEntry(random.nextDouble()*sumOfProbabilities);
}
@Override public T put(Double key, T value) {
return super.put(sumOfProbabilities+=key, value);
}
public static void main(String[] args) {
ProbabilityMap<Integer> map = new ProbabilityMap<>();
map.put(0.1,1); map.put(0.3,3); map.put(0.2,2);
IntStream.range(0, 10).forEach(i->System.out.println(map.next()));
}
}
【讨论】:
将https://stackoverflow.com/a/37228927/11257746的代码改编为通用扩展方法。这将允许您从具有
值为 50 的键被选择的可能性是值为 5 的键的 10 倍。
使用 LINQ 的 C# 代码:
/// <summary>
/// Get a random key out of a dictionary which has integer values treated as weights.
/// A key in the dictionary with a weight of 50 is 10 times more likely to be chosen than an element with the weight of 5.
///
/// Example usage to get 1 item:
/// Dictionary<MyType, int> myTypes;
/// MyType chosenType = myTypes.GetWeightedRandomKey<MyType, int>().First();
///
/// Adapted into a general extention method from https://stackoverflow.com/a/37228927/11257746
/// </summary>
public static IEnumerable<TKey> GetWeightedRandomKey<TKey, TValue>(this Dictionary<TKey, int> dictionaryWithWeights)
{
int totalWeights = 0;
foreach (KeyValuePair<TKey, int> pair in dictionaryWithWeights)
{
totalWeights += pair.Value;
}
System.Random random = new System.Random();
while (true)
{
int randomWeight = random.Next(0, totalWeights);
foreach (KeyValuePair<TKey, int> pair in dictionaryWithWeights)
{
int weight = pair.Value;
if (randomWeight - weight > 0)
randomWeight -= weight;
else
{
yield return pair.Key;
break;
}
}
}
}
使用示例:
public enum MyType { Thing1, Thing2, Thing3 }
public Dictionary<MyType, int> MyWeightedDictionary = new Dictionary<MyType, int>();
public void MyVoid()
{
MyWeightedDictionary.Add(MyType.Thing1, 50);
MyWeightedDictionary.Add(MyType.Thing2, 25);
MyWeightedDictionary.Add(MyType.Thing3, 5);
// Get a single random key
MyType myChosenType = MyWeightedDictionary.GetWeightedRandomKey<MyType, int>().First();
// Get 20 random keys
List<MyType> myChosenTypes = MyWeightedDictionary.GetWeightedRandomKey<MyType, int>().Take(20).ToList();
}
【讨论】:
一种耗费空间的方法是将每个项目克隆其概率的次数。选择将在 O(1) 中完成。
例如
//input
[{A,1},{B,1},{C,3}]
// transform into
[{A,1},{B,1},{C,1},{C,1},{C,1}]
然后从这个转换后的列表中随机选择任何项目。
【讨论】:
如果您不介意在代码中添加第三方依赖项,可以使用MockNeat.probabilities() 方法。
例如:
String s = mockNeat.probabilites(String.class)
.add(0.1, "A") // 10% chance to pick A
.add(0.2, "B") // 20% chance to pick B
.add(0.5, "C") // 50% chance to pick C
.add(0.2, "D") // 20% chance to pick D
.val();
免责声明:我是该库的作者,所以我在推荐它时可能会有偏见。
【讨论】:
您可以使用 Julia 代码:
function selrnd(a::Vector{Int})
c = a[:]
sumc = c[1]
for i=2:length(c)
sumc += c[i]
c[i] += c[i-1]
end
r = rand()*sumc
for i=1:length(c)
if r <= c[i]
return i
end
end
end
此函数有效地返回项目的索引。
【讨论】: