Java 从百分比列表中获取随机对象

Java 从百分比列表中获取随机对象,java,random,Java,Random,我似乎找不到一种有效的方法来按每个对象的百分比选择一个随机对象 基本上,我给这个方法一个对象的映射,以及一个对象被拾取的机会。最好是我想要它,这样它可以有很多小数,就像最长的那个一样,但1个小数也可以 public static <T> T random(Map<T, Double> chances) { T toReturn = null; double rand = new Random().nextInt(1000) / 10.0D; //

我似乎找不到一种有效的方法来按每个对象的百分比选择一个随机对象

基本上,我给这个方法一个对象的映射,以及一个对象被拾取的机会。最好是我想要它,这样它可以有很多小数,就像最长的那个一样,但1个小数也可以

public static <T> T random(Map<T, Double> chances) {
    T toReturn = null;
    double rand = new Random().nextInt(1000) / 10.0D;

    // ???

    return toReturn;
}
公共静态T随机(地图机会){
T toReturn=null;
双随机数=新随机数().nextInt(1000)/10.0D;
// ???
回归回归;
}
提前感谢。

基本策略:

  • 计算从0%包含到100%不包含的随机百分比
  • 遍历映射的入口集
  • 对于每个条目,如果值小于Rand,则返回键
  • 否则,请为Rand添加值并继续

  • 在这种情况下,我会使用一个累积机会的列表。假设有五个对象的概率为0.1,0.3,0.2,0.1,0.3。然后我将创建一个新数组a,其值为a=[0.1,0.4,0.6,0.7,1.0],也就是说,如果n>0,则a[n]=a[n-1]+p_n,其中p_n是对象n的可能性。然后你可以创建一个随机数r,并在a中找到最高的元素x,这样a[x]>r,x是你想要的对象的索引

    伪:

    cumulative_chances = [chances[0]]
    for i=1 to chances.size()
        cumulative_chances.append(chances[i]+cumulative_chances[i-1])
    r=random(0, cumulative_chances.last())
    i=0
    while cumulative_chances[i]<r
       i++
    return i
    
    累积机会=[机会[0]]
    对于i=1到0.size()
    累积机会。追加(机会[i]+累积机会[i-1])
    r=随机(0,累积概率。最后一次()
    i=0
    而所有n的累积概率[i]0。如果您想将任何项目的概率设置为零,则必须稍微修改该值


    如果应用程序中没有对性能敏感的内容,则可以在函数内部执行此计算。在其他情况下,我会预先计算。请注意,伪代码对值的规范化不敏感。他们不需要将upp添加到1(或者100,如果您想要百分比),它无论如何都可以工作。

    如果每个对象都映射到一个概率(可能会添加到100),那么您别无选择,只能遍历它们:

    public static <T> T getRandomItem(Map<T, Double> chances) {
        double chance = random.nextDouble() * 100.0;
        double cumulative = 0.0;
        for (T item: chances.keySet()) {
            cumulative += chances.get(item);
            if (chance < cumulative)
                return item;
        }
        throw new IllegalStateException("chances don't sum to 100");
    }
    
    publicstatict getRandomItem(映射机会){
    double chance=random.nextDouble()*100.0;
    双倍累积=0.0;
    对于(T项:chancess.keySet()){
    累计+=机会。获取(项目);
    如果(机会<累积)
    退货项目;
    }
    抛出新的非法状态例外(“机会不等于100”);
    }
    
    我做了帕特里克·帕克让我做的事,这很好用!非常感谢

    public static <T> T random(CustomMap<T, Double> map) {
        double rand = (double) new Random().nextInt(10000001) / 100000.0D;
        double total = 0.0D;
    
        for (T t : map.getKeys()) {
            double chance = map.get(t);
            total += chance;
    
            if (total >= rand) return t;
        }
    
        return null;
    }
    
    publicstatict随机(CustomMap){
    双随机数=(双)新随机数().nextInt(10000001)/100000.0D;
    双总=0.0D;
    for(T:map.getKeys()){
    双机会=map.get(t);
    总+=机会;
    如果(总计>=rand)返回t;
    }
    返回null;
    }
    
    此问题称为“适合度比例选择”,您无法在其上找到更多信息(和示例代码)。

    您的地图可以有多个不同的选项,并具有与随机值匹配的条件。按百分比复制项目(或重新迭代)如何这样,您将有公平的分布,您可以根据新列表调用random。这里有一个小问题。您可以返回有0%几率的项目。这就是为什么我说要使用“小于”而不是“小于或等于”始终仔细检查您的边缘情况。在这段代码中有两种主要的边缘情况。a) 概率为0%的项目,概率为100%的项目是的,尽管我做了大约10000次测试,但没有得到一个概率为0%的字符串,但我现在删除了
    =
    ,仍然非常有效,再次感谢您的帮助!不,那还是不对。现在,您在另一个边缘会遇到问题,如果权重为100,则有可能未被选中。提示:除了删除
    =
    @PatrickParker外,将此数字10_000_001更改为10_000_000,您是对的。我将操作符从>切换到0表示所有n。