如何按概率选择项目?

时间:2012-02-17 14:56:14

标签: java list random probability

我有一个项目列表。每个项目都有自己的概率。

任何人都可以根据其概率建议选择项目的算法吗?

13 个答案:

答案 0 :(得分:70)

  1. 生成均匀分布的随机数。
  2. 遍历您的列表,直到被访问元素的累积概率大于随机数
  3. 示例代码:

    double p = Math.random();
    double cumulativeProbability = 0.0;
    for (Item item : items) {
        cumulativeProbability += item.probability();
        if (p <= cumulativeProbability) {
            return item;
        }
    }
    

答案 1 :(得分:32)

因此,每个项目都会存储一个标记其相对概率的数字,例如,如果您有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));
    }
}

答案 2 :(得分:26)

假装我们有以下列表

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

答案 3 :(得分:12)

您可以尝试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(含)之间的随机整数,然后就可以像上面那样做出决定。

答案 4 :(得分:10)

Ushman'sBrent'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 - 它将自动归一化。

答案 5 :(得分:5)

我的方法非常简单。生成一个随机数。现在,由于您的项目概率已知,只需遍历排序的概率列表,然后选择概率小于随机生成数字的项目。

有关详细信息,请阅读我的回答here

答案 6 :(得分:4)

一种缓慢但简单的方法是让每个成员根据其概率选择一个随机数,并选择一个具有最高值的随机数。

<强>打个比方:

想象一下,需要选择3个人中的1人,但他们有不同的概率。你给他们死的面孔不同。第一个人的骰子有4个面,第2个人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倍的机会。但是我们可以通过调整方法来做到这一点。

方法2

我们可以将它们从上一个变量的上限限制为添加当前变量,而不是从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。

答案 7 :(得分:1)

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

答案 8 :(得分:1)

https://stackoverflow.com/a/37228927/11257746中的代码改编为通用扩展方法。这将使您可以从具有结构的Dictionary中获得加权随机值,其中int是权重值。

选择值为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();
}

答案 9 :(得分:0)

如果您不介意在代码中添加第三方依赖项,则可以使用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();

免责声明:我是该库的作者,因此当我推荐该库时可能会有所偏见。

答案 10 :(得分:0)

所有提到的解决方案都是线性的。以下仅是对数工作,并且还处理未归一化的概率。我建议使用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()));
        }
    }

答案 11 :(得分:0)

一种空间成本高的方法是按其概率的次数克隆每个项目。选择将在 O(1) 内完成。

例如

//input
[{A,1},{B,1},{C,3}]

// transform into
[{A,1},{B,1},{C,1},{C,1},{C,1}]

然后简单地从这个转换后的列表中随机选择任何项目。

答案 12 :(得分:-3)

您可以使用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

此函数有效地返回项目的索引。