如何在一个范围内生成有偏向某个值的随机数?

30

假设我想在minmax之间生成一个无偏的随机数,我会这样做:

var rand = function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

但如果我想生成一个介于minmax之间的随机数,但更偏向于介于minmax之间的值N,程度为D,该怎么办呢?最好用概率曲线来说明:

enter image description here


3
http://en.wikipedia.org/wiki/Gaussian_function - Tesseract
8
@ryan这不是重复,因为它是随机的,没有偏见。 OP要求一个有偏差的结果。 - user1693593
你不需要另一个参数吗?控制“扩散”的东西? - Salman A
@SalmanA:我理解可以让它变得更加复杂,所以我认为它不需要更多的参数。 - c00000fd
OP,你知道“概率分布”和“密度函数”吗?你可以在任何概率论入门书中找到它们。这些术语可以帮助你准确地表达你想要的内容(并思考如何解决问题)。你的图片很好,那是一个密度函数。 - Colonel Panic
4个回答

47

以下是一种方法:

  • 在最小和最大范围内获取随机数
  • 获取标准化的随机混合值
  • 基于随机混合将随机数与偏差混合

即,伪代码如下:

变量:
  最小 = 0
  最大 = 100
  偏差 = 67      (N)
  影响因素 = 1  (D) [0.0, 1.0]

公式:
  随机数 = random() x (最大 - 最小) + 最小
  混合值 = random() x 影响因素
  结果 = 随机数 x (1 - 混合值) + 偏差 x 混合值

混合因素可以通过第二个因素来降低以设置其影响程度(即混合值 x 因素,其中因素为[0, 1])。

演示

这将绘制一个有偏差的随机范围。上部带有1的影响力,下部带有0.75的影响力。此处将偏差设置在范围的2/3位置。 底部条带没有(故意的)偏差以进行比较。

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red"; ctx.fillRect(399,0,2,110);  // draw bias target
ctx.fillStyle = "rgba(0,0,0,0.07)";

function getRndBias(min, max, bias, influence) {
    var rnd = Math.random() * (max - min) + min,   // random in range
        mix = Math.random() * influence;           // random mixer
    return rnd * (1 - mix) + bias * mix;           // mix full range and bias
}

// plot biased result
(function loop() {
  for(var i = 0; i < 5; i++) {  // just sub-frames (speedier plot)
    ctx.fillRect( getRndBias(0, 600, 400, 1.00),  4, 2, 50);
    ctx.fillRect( getRndBias(0, 600, 400, 0.75), 55, 2, 50);
    ctx.fillRect( Math.random() * 600          ,115, 2, 35);
  }
  requestAnimationFrame(loop);
})();
<canvas width=600></canvas>


4
非常好。感谢演示。 - c00000fd
2
@c00000fd 没问题!我忘了提到偏置可以通过超过1来对混合结果产生更大的影响并将其夹紧。但你可能已经知道了这一点。祝你的项目好运! - user1693593
如果我想要取值为1或0,但希望结果是67%的1,会发生什么? - codenamezero
2
生成一个1到100之间的随机数。如果它大于等于67,则选择0,否则选择1。 - Mooing Duck
我的天,这太棒了!干得好! - aggregate1166877
在Stack Overflow上有一些答案让我感到惊讶,这些人的知识和智慧真是令人难以置信。绝对疯狂的回答,太神奇了,太神奇了,太神奇了! - PedroSantana

8

有趣的方法:使用图像作为密度函数。随机采样像素,直到获取黑色像素,然后取x坐标。

enter image description here

代码:

getPixels = require("get-pixels"); // npm install get-pixels

getPixels("distribution.png", function(err, pixels) {
  var height, r, s, width, x, y;
  if (err) {
    return;
  }
  width = pixels.shape[0];
  height = pixels.shape[1];
  while (pixels.get(x, y, 0) !== 0) {
    r = Math.random();
    s = Math.random();
    x = Math.floor(r * width);
    y = Math.floor(s * height);
  }
  return console.log(r);
});

示例输出:

0.7892316638026386
0.8595335511490703
0.5459279934875667
0.9044852438382804
0.35129814594984055
0.5352215224411339
0.8271261665504426
0.4871773284394294
0.8202084102667868
0.39301465335302055

根据口味进行缩放。

5
这实在是愚蠢又精彩,我真的笑出了声。它也是这个页面上最准确的一个! - Mooing Duck
ikr @MooingDuck - user13944038
女士们,先生们,天才与疯狂之间的界限是如此微妙而模糊。 - A-Tech

5
这里有一个有趣的版本,它依赖于 高斯函数,就像 SpiderPig 在你的问题中提到的那样。高斯函数应用于介于1和100之间的随机数,钟形曲线的高度表示最终值接近 N 的程度。我认为度数 D 意味着最终值接近 N 的可能性有多大,因此 D 对应于钟形曲线的宽度 - D 越小,偏差就越不可能发生。显然,该示例可以进一步校准。
(我复制了 Ken Fyrstenberg 的画布方法来演示函数。)

function randBias(min, max, N, D) {
  var a = 1,
      b = 50,
      c = D;

  var influence = Math.floor(Math.random() * (101)),
    x = Math.floor(Math.random() * (max - min + 1)) + min;

  return x > N 
         ? x + Math.floor(gauss(influence) * (N - x)) 
         : x - Math.floor(gauss(influence) * (x - N));

  function gauss(x) {
    return a * Math.exp(-(x - b) * (x - b) / (2 * c * c));
  }
}

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(399, 0, 2, 110);
ctx.fillStyle = "rgba(0,0,0,0.07)";

(function loop() {
  for (var i = 0; i < 5; i++) {
    ctx.fillRect(randBias(0, 600, 400, 50), 4, 2, 50);
    ctx.fillRect(randBias(0, 600, 400, 10), 55, 2, 50);
    ctx.fillRect(Math.random() * 600, 115, 2, 35);
  }
  requestAnimationFrame(loop);
})();
<canvas width=600></canvas>


谢谢。如果您像Ken Fyrstenberg在他的帖子中那样添加一个实时示例,那将非常有帮助。这真的有助于看到PRNG的有效性或偏差。 - c00000fd
@c00000fd已添加!考虑“钟形曲线”的形状和位置,以及其他参数,可以帮助优化结果。 - גלעד ברקן

4
当你使用Math.floor(Math.random() * (max - min + 1)) + min;时,实际上你正在创建一个均匀分布。要在图表中获得数据分布,你需要具有非零偏度的分布。
有不同的技术来获得这些类型的分布。这里是一个在stackoverflow上找到的beta分布示例

这是从链接中总结出来的示例:

unif = Math.random()  // The original uniform distribution.

我们可以通过以下方式将其转换为beta分布

beta = sin(unif*pi/2)^2 // The standard beta distribution

要在您的图表中显示偏度,
beta_right = (beta > 0.5) ? 2*beta-1 : 2*(1-beta)-1;

你可以将值1更改为其他任何值,以使其偏向其他值。

2
感谢您的解释。虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。 - Lea Cohen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接