生成加权随机数

我试图devise一个(好)的方式来从一个可能的数字范围内select一个随机数字,范围中的每个数字都被赋予一个权重。 简单地说:给定数字范围(0,1,2)select一个数字,其中0有80%的概率被选中,1有10%的几率,2有10%的几率。

从我的大学统计课程开始已经有8年了,所以你可以想象现在正确的方法逃脱了我。

这是我提出的“廉价和肮脏”的方法。 该解决scheme使用ColdFusion。 你可以使用任何你想要的语言。 我是程序员,我想我可以处理它。 最终,我的解决scheme需要在Groovy中 – 我在ColdFusion中编写了这个解决scheme,因为它很容易在CF中编写/testing。

public function weightedRandom( Struct options ) { var tempArr = []; for( var o in arguments.options ) { var weight = arguments.options[ o ] * 10; for ( var i = 1; i<= weight; i++ ) { arrayAppend( tempArr, o ); } } return tempArr[ randRange( 1, arrayLen( tempArr ) ) ]; } // test it opts = { 0=.8, 1=.1, 2=.1 }; for( x = 1; x<=10; x++ ) { writeDump( weightedRandom( opts ) ); } 

我正在寻找更好的解决scheme,请提出改进​​build议或替代scheme。

拒绝抽样 (例如在您的解决scheme中)是首先想到的,您可以使用其重量分布填充元素来构build查找表,然后在表中随机select一个位置并将其返回。 作为一个实现的select,我会创build一个更高阶的函数,它接受一个规范并返回一个函数,它返回基于规范中的分布的值,这样可以避免为每个调用构build表。 缺点是构build表的algorithm性能与项目数量呈线性关系,对于大型规格(或具有非常小或精确权重的成员,例如{0:0.99999,1 :0.00001})。 好处是,select一个价值的时间是不变的,如果性能是至关重要的,这可能是可取的。 在JavaScript中:

 function weightedRand(spec) { var i, j, table=[]; for (i in spec) { // The constant 10 below should be computed based on the // weights in the spec for a correct and optimal table size. // Eg the spec {0:0.999, 1:0.001} will break this impl. for (j=0; j<spec[i]*10; j++) { table.push(i); } } return function() { return table[Math.floor(Math.random() * table.length)]; } } var rand012 = weightedRand({0:0.8, 1:0.1, 2:0.1}); rand012(); // random in distribution... 

另一个策略是在[0,1)select一个随机数,然后迭代权重总和权重,如果随机数小于和,则返回相关的值。 当然,这假定权重总和为1。 该解决scheme没有前期成本,但是algorithm性能的平均值与规范中的条目数量成线性关系。 例如,在JavaScript中:

 function weightedRand2(spec) { var i, sum=0, r=Math.random(); for (i in spec) { sum += spec[i]; if (r <= sum) return i; } } weightedRand2({0:0.8, 1:0.1, 2:0.1}); // random in distribution... 

生成一个0到1之间的随机数R.

如果R在[0,0.1) – > 1

如果R在[0.1,0.2) – > 2

如果R在[0.2,1] – > 3

如果您不能直接得到0到1之间的数字,请在范围内生成一个数字,以便按照您的要求生成尽可能多的精度。 例如,如果你有权重

(1,83.7%)和(2,16.3%),从1到1000滚动一个数字。1-837是1. 838-1000是2。

这或多或less是@trinithis在Java中写成的通用版本:我使用整数而不是浮点数来避免混乱的舍入错误。

 static class Weighting { int value; int weighting; public Weighting(int v, int w) { this.value = v; this.weighting = w; } } public static int weightedRandom(List<Weighting> weightingOptions) { //determine sum of all weightings int total = 0; for (Weighting w : weightingOptions) { total += w.weighting; } //select a random value between 0 and our total int random = new Random().nextInt(total); //loop thru our weightings until we arrive at the correct one int current = 0; for (Weighting w : weightingOptions) { current += w.weighting; if (random < current) return w.value; } //shouldn't happen. return -1; } public static void main(String[] args) { List<Weighting> weightings = new ArrayList<Weighting>(); weightings.add(new Weighting(0, 8)); weightings.add(new Weighting(1, 1)); weightings.add(new Weighting(2, 1)); for (int i = 0; i < 100; i++) { System.out.println(weightedRandom(weightings)); } } 

这里有3个解决scheme,因为我不确定你想要哪种语言。根据你的需求,前两个可能工作,但第三个可能是最容易实现的大数字集。

 function randomSimple(){ return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)]; } function randomCase(){ var n=Math.floor(Math.random()*100) switch(n){ case n<80: return 0; case n<90: return 1; case n<100: return 2; } } function randomLoop(weight,num){ var n=Math.floor(Math.random()*100),amt=0; for(var i=0;i<weight.length;i++){ //amt+=weight[i]; *alternative method //if(n<amt){ if(n<weight[i]){ return num[i]; } } } weight=[80,90,100]; //weight=[80,10,10]; *alternative method num=[0,1,2] 

怎么样

int [] numbers = {0,0,0,0,0,0,0,1,2};

那么你可以从数字中随机select,0将有80%的机会,1 10%,2 10%

这个是在Mathematica中,但很容易复制到另一种语言,我在游戏中使用它,它可以处理十进制的重量:

 weights = {0.5,1,2}; // The weights weights = N@weights/Total@weights // Normalize weights so that the list's sum is always 1. min = 0; // First min value should be 0 max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0. random = RandomReal[]; // Generate a random float from 0 to 1; For[i = 1, i <= Length@weights, i++, If[random >= min && random < max, Print["Chosen index number: " <> ToString@i] ]; min += weights[[i]]; If[i == Length@weights, max = 1, max += weights[[i + 1]] ] ] 

(现在我正在讨论列表第一个元素的索引等于0)这背后的想法是,有一个归一化的列表权重有一个权重[n]返回索引n的机会 ,所以最小值和最大值之间的距离在步骤n应该是权重[n] 。 从最小最小值(我们把它设为0)到最大最大值的总距离就是列表权重的总和。

这背后的好处是,你不会追加到任何数组或嵌套的循环,这大大增加了执行时间。

这里是C#中的代码,无需标准化权重列表并删除一些代码:

 int WeightedRandom(List<float> weights) { float total = 0f; foreach (float weight in weights) { total += weight; } float max = weights [0], random = Random.Range(0f, total); for (int index = 0; index < weights.Count; index++) { if (random < max) { return index; } else if (index == weights.Count - 1) { return weights.Count-1; } max += weights[index+1]; } return -1; } 

这里是input和比例:0(80%),1(10%),2(10%)

让我们把它们画出来,这样就很容易看出来。

  0 1 2 -------------------------------------________+++++++++ 

让我们把总重量加起来,并称之为总比率TR。 所以在这种情况下100.让我们从(0-TR)或(在这种情况下0到100)随机获得一个数字。 100是你的总重量。 把它叫做随机数的RN。

所以现在我们有TR作为总权重,RN是0到TR之间的随机数。

所以让我们想象一下,我们从0到100中随机select了一个。说21,那么实际上是21%。

我们必须改变/匹配这个到我们的input数字,但如何?

让循环每个重量(80,10,10),并保持我们已经访问的权重的总和。 当我们循环的权重之和大于随机数RN(在这种情况下为21)时,我们停止循环并返回该元素的位置。

 double sum = 0; int position = -1; for(double weight : weight){ position ++; sum = sum + weight; if(sum > 21) //(80 > 21) so break on first pass break; } //position will be 0 so we return array[0]--> 0 

可以说随机数(0到100之间)是83.让我们再来看看:

 double sum = 0; int position = -1; for(double weight : weight){ position ++; sum = sum + weight; if(sum > 83) //(90 > 83) so break break; } //we did two passes in the loop so position is 1 so we return array[1]---> 1 

我使用以下

 function weightedRandom(min, max) { return Math.round(max / (Math.random() * max + min)); } 

这是我的“加权”随机,其中我使用“x”(其中x是最小值和最大值之间的随机值)的反函数来生成加权结果,其中最小值是最重的元素,最大值最轻的(获得结果的机会最less)

所以基本上,使用weightedRandom(1, 5)意味着得到1的概率高于高于3的2,高于4,高于5。

可能对您的使用案例没有用处,但可能对使用Googlesearch相同问题的用户有用。

经过100次迭代尝试,它给了我:

 ================== | Result | Times | ================== | 1 | 55 | | 2 | 28 | | 3 | 8 | | 4 | 7 | | 5 | 2 | ================== 

我build议使用连续检查的概率和随机数的其余部分。

该函数首先将返回值设置为最后一个可能的索引并迭代,直到随机值的其余部分小于实际概率。

概率必须总和为1。

 function getRandomIndexByProbability(probabilities) { var r = Math.random(), index = probabilities.length - 1; probabilities.some(function (probability, i) { if (r < probability) { index = i; return true; } r -= probability; }); return index; } var i, probabilities = [0.8, 0.1, 0.1], count = probabilities.map(function () { return 0; }); for (i = 0; i < 1e6; i++) { count[getRandomIndexByProbability(probabilities)]++; } console.log(count); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

我有一个slotmachine,我用下面的代码来生成随机数字。 在probabilitiesSlotMachine中,键是slotmachine中的输出,值表示权重。

 const probabilitiesSlotMachine = [{0 : 1000}, {1 : 100}, {2 : 50}, {3 : 30}, {4 : 20}, {5 : 10}, {6 : 5}, {7 : 4}, {8 : 2}, {9 : 1}] var allSlotMachineResults = [] probabilitiesSlotMachine.forEach(function(obj, index){ for (var key in obj){ for (var loop = 0; loop < obj[key]; loop ++){ allSlotMachineResults.push(key) } } }); 

现在要生成一个随机的输出,我使用这个代码:

 const random = allSlotMachineResults[Math.floor(Math.random() * allSlotMachineResults.length)]