【问题标题】:Java: random integer with non-uniform distributionJava:具有非均匀分布的随机整数
【发布时间】:2011-08-23 14:09:48
【问题描述】:

如何在 Java 中创建一个随机整数 n,介于 1k 之间,具有“线性递减分布”,即 1 最有可能,2 不太可能,3不太可能,...,k 最不可能,并且概率线性下降,如下所示:

我知道这个主题已经有几十个主题了,我很抱歉创建一个新主题,但我似乎无法从它们中创建我需要的内容。我知道使用import java.util.*;,代码

Random r=new Random();
int n=r.nextInt(k)+1;

1k 之间创建一个随机整数,均匀分布。

GENERALIZATION:任何有关创建任意分布整数的提示,即f(n)=some functionP(n)=f(n)/(f(1)+...+f(k))),也将不胜感激,例如:

.

【问题讨论】:

    标签: java random non-uniform-distribution


    【解决方案1】:

    想到的第一个解决方案是使用阻塞数组。每个索引将根据您希望它的“可能”程度指定一系列值。在这种情况下,您可以为 1 使用更大的范围,为 2 使用更小的范围,依此类推,直到您为 k 达到一个较小的值(比如说 1)。

    int [] indexBound = new int[k];
    int prevBound =0;
    for(int i=0;i<k;i++){
        indexBound[i] = prevBound+prob(i);
        prevBound=indexBound[i];
    }
    int r = new Random().nextInt(prevBound);
    for(int i=0;i<k;i++){
        if(r > indexBound[i];
            return i;
    }
    

    现在的问题只是找到一个随机数,然后将该数字映射到它的存储桶。 只要您可以离散化每个区间的宽度,您就可以对任何分布执行此操作。 如果我在解释算法或其正确性时遗漏了什么,请告诉我。不用说,这需要优化。

    【讨论】:

    • 这里不需要数组。如果按照您的描述将随机数放入存储桶中,您只需执行执行的计算。
    • 是的,“应该”是这样做的方式。谢谢。
    • 是的,这是一个非常直观的解决方案。但是,我认为 Java 中已经有一个内置函数可以做到这一点。我很困惑,为什么 Java 中的几乎所有东西都必须“从头开始”创建。谢谢:)。
    【解决方案2】:

    这应该可以满足您的需求:

    public static int getLinnearRandomNumber(int maxSize){
        //Get a linearly multiplied random number
        int randomMultiplier = maxSize * (maxSize + 1) / 2;
        Random r=new Random();
        int randomInt = r.nextInt(randomMultiplier);
    
        //Linearly iterate through the possible values to find the correct one
        int linearRandomNumber = 0;
        for(int i=maxSize; randomInt >= 0; i--){
            randomInt -= i;
            linearRandomNumber++;
        }
    
        return linearRandomNumber;
    }
    

    此外,这里是一个从开始索引到停止索引范围内的正函数(负函数没有真正意义)的一般解决方案:

    public static int getYourPositiveFunctionRandomNumber(int startIndex, int stopIndex) {
        //Generate a random number whose value ranges from 0.0 to the sum of the values of yourFunction for all the possible integer return values from startIndex to stopIndex.
        double randomMultiplier = 0;
        for (int i = startIndex; i <= stopIndex; i++) {
            randomMultiplier += yourFunction(i);//yourFunction(startIndex) + yourFunction(startIndex + 1) + .. yourFunction(stopIndex -1) + yourFunction(stopIndex)
        }
        Random r = new Random();
        double randomDouble = r.nextDouble() * randomMultiplier;
    
        //For each possible integer return value, subtract yourFunction value for that possible return value till you get below 0.  Once you get below 0, return the current value.  
        int yourFunctionRandomNumber = startIndex;
        randomDouble = randomDouble - yourFunction(yourFunctionRandomNumber);
        while (randomDouble >= 0) {
            yourFunctionRandomNumber++;
            randomDouble = randomDouble - yourFunction(yourFunctionRandomNumber);
        }
    
        return yourFunctionRandomNumber;
    }
    

    注意:对于可能返回负值的函数,一种方法是获取该函数的绝对值并将其应用于上述解决方案中的每个 yourFunction 调用。

    【讨论】:

    • 你可以使用randomMultiplier = maxSize * (maxSize + 1) /2;
    • 它可以工作,在 Mathematica 中测试过。谢谢你。现在我只在寻找最简单的解决方案。
    • 根据彼得的评论编辑了通用解决方案和简化的原始解决方案。
    • +1 值得一提的是,这个算法被称为Inverse Transform Method
    【解决方案3】:

    有很多方法可以做到这一点,但可能最简单的方法就是生成 两个随机整数,一个在0k之间,叫它x,一个在0h之间,叫它y。如果y &gt; mx + bmb 选择得当...)则 k-x,否则x

    编辑:在此处回复 cmets,以便我可以有更多空间。

    基本上,我的解决方案利用了原始分布中的对称性,其中p(x)x 的线性函数。我在您对泛化进行编辑之前做出了回应,并且此解决方案在一般情况下不起作用(因为在一般情况下没有这种对称性)。

    我想象过这样的问题:

    1. 你有两个直角三角形,每个k x h,都有一个共同的斜边。复合形状是一个k x h 矩形。
    2. 生成一个随机点,该点以相等的概率落在矩形内的每个点上。
    3. 一半时间落在一个三角形中,一半时间落在另一个三角形中。
    4. 假设点落在下三角形中。
      • 三角形基本上描述了 P.M.F.,三角形在每个 x 值上的“高度”描述了该点具有此类 x 值的概率。 (请记住,我们只处理下三角形中的点。)所以通过产生 x 值。
    5. 假设点落在上三角形中。
      • 倒置坐标并像上面一样用下面的三角形处理。

    您还必须处理边缘情况(我没有打扰)。例如。我现在看到你的分布是从 1 开始的,而不是 0,所以那里有一个逐一的,但很容易修复。

    【讨论】:

    • @rlibby:我对最简单的解决方案非常感兴趣。您能详细说明一下吗?
    • 至于更一般的问题:这里是f = mx + b,这种方法适用于 any f 代表分布 - 我认为。对吗?
    • @Robin Green, @rlibby:这里到底发生了什么?为什么then k-x, else x
    • @rlibby:嗯?我不这么认为,你的分布会产生其他东西。
    • @rlibby: 嗯,我应该如何在0h 之间生成一个整数y,因为h=2/k&lt;1?我应该在这里使用双打吗?
    【解决方案4】:

    最简单的方法是生成权重中所有可能值的列表或数组。

    int k = /* possible values */
    int[] results = new int[k*(k+1)/2];
    for(int i=1,r=0;i<=k;i++)
       for(int j=0;j<=k-i;j++)
           results[r++] = i;
    // k=4 => { 1,1,1,1,2,2,2,3,3,4 }
    
    // to get a value with a given distribution.
    int n = results[random.nextInt(results.length)];
    

    这最适用于相对较小的 k 值。即。 k

    对于较大的数字,您可以使用存储桶方法

    int k = 
    int[] buckets = new int[k+1];
    for(int i=1;i<k;i++)
       buckets[i] = buckets[i-1] + k - i + 1;
    
    int r = random.nextInt(buckets[buckets.length-1]);
    int n = Arrays.binarySearch(buckets, r);
    n = n < 0 ? -n : n + 1;
    

    二分查找的成本相当小,但不如直接查找效率高(对于小数组)


    对于任意分布,您可以使用 double[] 进行累积分布并使用二分搜索来查找值。

    【讨论】:

      【解决方案5】:

      如果您的分布可以计算其累积分布函数 (cdf),则无需使用数组等进行模拟。上面你有一个概率分布函数(pdf)。 h 实际上是确定的,因为曲线下的面积必须为 1。为简单起见,我还假设您在 [0,k) 中选择一个数字。

      如果我没看错的话,这里的 pdf 是 f(x) = (2/k) * (1 - x/k)。 cdf 只是 pdf 的组成部分。在这里,这是 F(x) = (2/k) * (x - x^2 / 2k)。 (如果它是可积的,你可以对任何 pdf 函数重复这个逻辑。)

      然后你需要计算 cdf 函数的倒数,F^-1(x),如果我不懒惰,我会为你做。

      但好消息是:一旦你有了 F^-1(x),你所做的就是将它应用到 [0,1] 中均匀分布的随机值上,然后将函数应用到它上面。 java.util.Random 可以提供一些照顾。这是您从分布中随机抽取的值。

      【讨论】:

        【解决方案6】:

        让我也尝试另一个答案,灵感来自 rlibby。这种特定的分布也是从同一范围内均匀随机选择的两个值中较小的的分布。

        【讨论】:

          【解决方案7】:

          所以我们需要以下分布,从最不可能到最有可能:

          *
          **
          ***
          ****
          *****
          

          等等

          让我们尝试将一个均匀分布的整数随机变量映射到该分布:

          1
          2  3
          4  5  6
          7  8  9  10
          11 12 13 14 15
          

          等等

          这样,如果我们为K = 5 生成一个从 1 到 15 的均匀分布的随机整数,我们只需要弄清楚它适合哪个桶。棘手的部分是如何做到这一点。

          注意右边的数字是三角数!这意味着对于从1T_n 随机生成的X,我们只需要找到N 这样T_(n-1) &lt; X &lt;= T_n。幸运的是有一个well-defined formula to find the 'triangular root' of a given number,我们可以用它作为我们从均匀分布到桶的映射的核心:

          // Assume k is given, via parameter or otherwise
          int k;
          
          // Assume also that r has already been initialized as a valid Random instance
          Random r = new Random();
          
          // First, generate a number from 1 to T_k
          int triangularK = k * (k + 1) / 2;
          
          int x = r.nextInt(triangularK) + 1;
          
          // Next, figure out which bucket x fits into, bounded by
          // triangular numbers by taking the triangular root    
          // We're dealing strictly with positive integers, so we can
          // safely ignore the - part of the +/- in the triangular root equation
          double triangularRoot = (Math.sqrt(8 * x + 1) - 1) / 2;
          
          int bucket = (int) Math.ceil(triangularRoot);
          
          // Buckets start at 1 as the least likely; we want k to be the least likely
          int n = k - bucket + 1;
          

          n 现在应该具有指定的分布。

          【讨论】:

          • 感谢您提及“三角根”。
          【解决方案8】:

          这样的……

          class DiscreteDistribution
          {
              // cumulative distribution
              final private double[] cdf;
              final private int k;
          
              public DiscreteDistribution(Function<Integer, Double> pdf, int k)
              {
                  this.k = k;
                  this.cdf = new double[k];
                  double S = 0;
                  for (int i = 0; i < k; ++i)
                  {
                      double p = pdf.apply(i+1);         
                      S += p;
                      this.cdf[i] = S;
                  }
                  for (int i = 0; i < k; ++i)
                  {
                      this.cdf[i] /= S;
                  }
              }
              /**
               * transform a cumulative distribution between 0 (inclusive) and 1 (exclusive)
               * to an integer between 1 and k.
               */
              public int transform(double q)
              {
                  // exercise for the reader:
                  // binary search on cdf for the lowest index i where q < cdf[i]
                  // return this number + 1 (to get into a 1-based index.
                  // If q >= 1, return k.
              }
          }
          

          【讨论】:

            【解决方案9】:

            This is called a triangular distribution,尽管您的情况是模式等于最小值的退化情况。维基百科有关于如何创建一个给定均匀分布 (0,1) 变量的方程。

            【讨论】:

            • 谢谢。对于那些不了解所涉及数学的人,我根据链接信息发布了更详细的answer
            【解决方案10】:

            对于众数(最高加权概率)为 1 的三角分布 [0,1],累积分布函数为 x^2,如图所示 here

            因此,将均匀分布(例如 Java 的 Random::nextDouble)转换为方便的加权为 1 的三角形分布所需要做的就是:简单地取平方根 Math.sqrt(rand.nextDouble()),然后可以乘以任何所需的范围.

            你的例子:

            int a = 1; // lower bound, inclusive
            int b = k; // upper bound, exclusive
            double weightedRand = Math.sqrt(rand.nextDouble()); // use triangular distribution
            weightedRand = 1.0 - weightedRand; // invert the distribution (greater density at bottom)
            int result = (int) Math.floor((b-a) * weightedRand);
            result += a; // offset by lower bound
            if(result >= b) result = a; // handle the edge case 
            

            【讨论】:

              【解决方案11】:

              有多种方法可以生成具有自定义分布(也称为离散分布)的随机整数。选择取决于很多因素,包括可供选择的整数数量、分布的形状以及分布是否会随时间变化。

              使用自定义权重函数f(x) 选择整数的最简单方法之一是拒绝采样方法。以下假设f 的最高可能值为max。拒绝抽样的时间复杂度平均是恒定的,但很大程度上取决于分布的形状,并且最坏的情况是永远运行。使用拒绝采样在 [1, k] 中选择一个整数:

              1. 在 [1, k] 中选择一个统一的随机整数 i
              2. 有概率f(i)/max,返回i。否则,请转到第 1 步。

              其他算法的平均采样时间不太依赖于分布(通常是常数或对数),但通常需要您在设置步骤中预先计算权重并将它们存储在数据结构中。其中一些在平均使用的随机位数方面也很经济。这些算法包括别名方法Fast Loaded Dice Roller、Knuth-Yao 算法、MVN 数据结构等。请参阅我的“Weighted Choice With Replacement”部分进行调查。

              【讨论】:

                猜你喜欢
                • 2017-07-16
                • 2014-02-04
                • 1970-01-01
                • 2011-05-31
                • 2011-03-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多