【问题标题】:Generate an array of random integers with non-uniform distribution生成具有非均匀分布的随机整数数组
【发布时间】:2014-02-04 04:39:26
【问题描述】:

我想编写 Java 代码来生成 [1,4] 范围内的随机整数数组。数组的长度为 N,在运行时提供。问题是范围[1,4]不是均匀分布的:

这意味着如果我创建 N=100 的数组,数字“1”将在一个数组中平均出现 40 次,数字“2”平均出现 10 次,依此类推。

现在我正在使用此代码生成范围 [1,4] 内的均匀分布的随机数:

public static void main(String[] args)
    {
        int N;
        System.out.println();
        System.out.print("Enter an integer number: ");
        N = input.nextInt();
        int[] a = new int[N];
        Random generator = new Random();
        for(int i = 0; i < a.length; i++)
        {
            a[i] = generator.nextInt(4)+1;
        }
    }

如何使用如上图所示的非均匀分布来实现它?

【问题讨论】:

标签: java arrays random probability-density non-uniform-distribution


【解决方案1】:

Miquel 的可扩展性稍强的版本(也是 Teresa 建议的):

    double[] distro=new double[]{.4,.1,.3,.2};        
    int N;
    System.out.println();
    System.out.print("Enter an integer number: ");
    Scanner input = new Scanner(System.in);
    N = input.nextInt();
    int[] a = new int[N];
    Random generator = new Random();
    outer:
    for(int i = 0; i < a.length; i++)
    {
        double rand=generator.nextDouble();
        double val=0;
        for(int j=1;j<distro.length;j++){
            val+=distro[j-1];
            if(rand<val){
                a[i]=j;
                continue outer;
            }
        }
        a[i]=distro.length;
    }

【讨论】:

  • 非常好。此外,@pjs 的建议仍然适用。您可以将发行版转换为地图并按降序概率排序,因此您可以更早地点击continue(统计上)
  • +1。我必须使用循环,因为我的实际范围远大于 [1,4]
【解决方案2】:

对于您上面给出的具体问题,其他人提供的解决方案效果很好,alias method 将是矫枉过正。但是,您在评论中说您实际上打算在范围更大的发行版中使用它。在这种情况下,设置别名表的开销可能值得获得 O(1) 行为以实际生成值。

这里是 Java 的源代码。如果您不想使用 Mersenne Twister,很容易将其恢复为使用 Java 的股票 Random

/*
 * Created on Mar 12, 2007
 *    Feb 13, 2011: Updated to use Mersenne Twister - pjs
 */
package edu.nps.or.simutils;

import java.lang.IllegalArgumentException;
import java.text.DecimalFormat;
import java.util.Comparator;
import java.util.Stack;
import java.util.PriorityQueue;
import java.util.Random;

import net.goui.util.MTRandom;

public class AliasTable<V> {
   private static Random r = new MTRandom();
   private static DecimalFormat df2 = new DecimalFormat(" 0.00;-0.00");

   private V[] primary;
   private V[] alias;
   private double[] primaryP;
   private double[] primaryPgivenCol;

   private static boolean notCloseEnough(double target, double value) {
      return Math.abs(target - value) > 1E-10;
   }

   /**
    * Constructs the AliasTable given the set of values
    * and corresponding probabilities.
    * @param value
    *   An array of the set of outcome values for the distribution. 
    * @param pOfValue
    *   An array of corresponding probabilities for each outcome.
    * @throws IllegalArgumentException
    *   The values and probability arrays must be of the same length,
    *   the probabilities must all be positive, and they must sum to one.
    */
   public AliasTable(V[] value, double[] pOfValue) {
      super();      
      if (value.length != pOfValue.length) {
         throw new IllegalArgumentException(
               "Args to AliasTable must be vectors of the same length.");
      }
      double total = 0.0;
      for (double d : pOfValue) {
         if (d < 0) {
            throw new
               IllegalArgumentException("p_values must all be positive.");
         }
         total += d;
      }
      if (notCloseEnough(1.0, total)) {
         throw new IllegalArgumentException("p_values must sum to 1.0");
      }

      // Done with the safety checks, now let's do the work...

      // Cloning the values prevents people from changing outcomes
      // after the fact.
      primary = value.clone();
      alias = value.clone();
      primaryP = pOfValue.clone();
      primaryPgivenCol = new double[primary.length];
      for (int i = 0; i < primaryPgivenCol.length; ++i) {
         primaryPgivenCol[i] = 1.0;
      }
      double equiProb = 1.0 / primary.length;

      /*
       * Internal classes are UGLY!!!!
       * We're what you call experts.  Don't try this at home!
       */
      class pComparator implements Comparator<Integer> {
         public int compare(Integer i1, Integer i2) {
            return primaryP[i1] < primaryP[i2] ? -1 : 1;
         }
      }

      PriorityQueue<Integer> deficitSet =
         new PriorityQueue<Integer>(primary.length, new pComparator());
      Stack<Integer> surplusSet = new Stack<Integer>();

      // initial allocation of values to deficit/surplus sets
      for (int i = 0; i < primary.length; ++i) {
         if (notCloseEnough(equiProb, primaryP[i])) {
            if (primaryP[i] < equiProb) {
               deficitSet.add(i);
            } else {
               surplusSet.add(i);
            }
         }
      }

      /*
       * Pull the largest deficit element from what remains.  Grab as
       * much probability as you need from a surplus element.  Re-allocate
       * the surplus element based on the amount of probability taken from
       * it to the deficit, surplus, or completed set.
       * 
       * Lather, rinse, repeat.
       */
      while (!deficitSet.isEmpty()) {
         int deficitColumn = deficitSet.poll();
         int surplusColumn = surplusSet.pop();
         primaryPgivenCol[deficitColumn] = primaryP[deficitColumn] / equiProb;
         alias[deficitColumn] = primary[surplusColumn];
         primaryP[surplusColumn] -= equiProb - primaryP[deficitColumn];
         if (notCloseEnough(equiProb, primaryP[surplusColumn])) {
            if (primaryP[surplusColumn] < equiProb) {
               deficitSet.add(surplusColumn);
            } else {
               surplusSet.add(surplusColumn);
            }
         }
      }
   }

   /**
    * Generate a value from the input distribution.  The alias table
    * does this in O(1) time, regardless of the number of elements in
    * the distribution.
    * @return
    *   A value from the specified distribution.
    */
   public V generate() {
      int column = (int) (primary.length * r.nextDouble());
      return r.nextDouble() <= primaryPgivenCol[column] ?
                  primary[column] : alias[column];
   }

   public void printAliasTable() {
      System.err.println("Primary\t\tprimaryPgivenCol\tAlias");
      for(int i = 0; i < primary.length; ++i) {
         System.err.println(primary[i] + "\t\t\t"
            + df2.format(primaryPgivenCol[i]) + "\t\t" + alias[i]);
      }
      System.err.println();
   }
}

【讨论】:

  • 我不知道别名方法,发现它很有趣,谢谢!如果有人想阅读它,维基百科页面相当不完整,因此您可能需要查看this instead
【解决方案3】:

对于更通用的方法,您可以使用分布概率填充 NavigableMap

double[] probs = {0.4, 0.1, 0.2, 0.3};
NavigableMap<Double, Integer> distribution = new TreeMap<Double, Integer>();
for(double p : probs) {
    distribution.put(distribution.isEmpty() ? p : distribution.lastKey() + p, distribution.size() + 1);
}

然后用 [0, 1> 范围内的均匀分布随机键查询地图:

Random rnd = new Random();
for(int i=0; i<20; i++) {
    System.out.println(distribution.ceilingEntry(rnd.nextDouble()).getValue());
}

这将使用以下键/值对填充地图:

0.4 -> 1
0.5 -> 2
0.7 -> 3
1.0 -> 4

要查询地图,首先要在 0 到 1 范围内生成一个均匀分布的双精度数。使用 ceilingEntry 方法查询地图并传递随机数将返回 "mapping associated with the least key greater than or equal to the given key",例如传递 0.5 -> 2 的条目。因此,在返回的映射条目上使用 getValue() 将返回 2。

【讨论】:

  • +1。我不完全理解你的方法,但会调查它。它似乎比其他人短得多。 tnx
  • 同样的想法和出色的实现。多亏了这一点,我不必手动对分布值进行排序。
【解决方案4】:

这是一种方法,从您的代码开始:

public static void main(String[] args){
    int N;
    System.out.println();
    System.out.print("Enter an integer number: ");
    N = input.nextInt();
    int[] a = new int[N];
    Random generator = new Random();
    for (int i = 0; i < a.length; i++) {
        float n = generator.nextFloat();
        if (n <= 0.4) {
            a[i] = 1;
        } else if (n <= 0.7) {
            a[i] = 3;
        } else if (n <= 0.9) {
            a[i] = 4;
        } else {
            a[i] = 2;
        }
    }
}

更新:根据@pjs 的建议,按降序概率的顺序选择数字,这样您就倾向于提前退出 if 块

【讨论】:

  • 您可以通过按概率降序搜索来稍微提高效率,这样您就有更大的机会提前终止。相应的阈值将是 [0.4, 0.7, 0.9, else] 以分别返回 [1,3, 4, 2]。
  • @Miquel 你需要按[1, 3, 4, 2]的顺序返回数字,以符合pjs的想法。
  • 谢谢@Duncan 我的错。现已修复。
  • 从均匀分布中制作离散非均匀分布的好主意。回答接受。我只需要使用@Teresa 和 Vandale 循环方法进一步改进代码
【解决方案5】:

a1, a2, a3a4 成为指定相对概率的双精度数和s = a1+a2+a3+a4 这意味着1 的概率是a1/s2 的概率是a2/s,...

然后使用generator.nextDouble() 创建一个随机双精度d。

如果0 &lt;= d &lt; a1/s那么整数应该是1,

如果a1/s &lt;= d &lt; (a1+a2)/s 那么整数应该是2

如果(a1+a2)/s &lt;= d &lt; (a1+a2+a3)/s,那么整数应该是3

如果(a1+a2+a3)/s &lt;= d &lt; 1,那么整数应该是4

【讨论】:

    【解决方案6】:

    另一个简单的解决方案是使用 nextDouble() 在 [0,1) 中生成随机双精度。如果值

    【讨论】:

      猜你喜欢
      • 2017-07-16
      • 2011-08-23
      • 2011-05-31
      • 1970-01-01
      • 1970-01-01
      • 2020-12-12
      • 2015-04-04
      • 2013-07-22
      • 1970-01-01
      相关资源
      最近更新 更多