获得一个以中心为焦点的随机数

243

是否可能在1-100之间获得一个随机数并将结果主要保持在40-60范围内?我的意思是,它很少会超出那个范围,但我希望它主要在那个范围内...这可用JavaScript/jQuery实现吗?

目前我只是使用基本的 Math.random() * 100 + 1


7
http://en.wikipedia.org/wiki/Random_number_generation#Generation_from_a_probability_distribution - Roko C. Buljan
4
http://codetheory.in/weighted-biased-random-number-generation-with-javascript-based-on-probability/ - Josh
1
可能重复:https://dev59.com/-nI_5IYBdhLWcg3wF_B3 - Mahedi Sabuj
20
我很喜欢这个问题,但我认为它应该更加具体。你想要一个 Z 分布(钟形曲线),一个三角分布还是某种锯齿形分布?在我看来,有多种可能的回答方式。 - Patrick Roberts
12
这可以在 JavaScript 中完成,但肯定与 jQuery 无关... :) - A. Wolff
显示剩余8条评论
20个回答

402

最简单的方法是从0-50之间生成两个随机数,然后将它们加起来。

这会导致分布偏向于50,就像掷两个骰子偏向于7一样。

事实上,通过使用更多的“骰子”(如@Falco建议的),你可以更接近于钟形曲线的近似:

function weightedRandom(max, numDice) {
    let num = 0;
    for (let i = 0; i < numDice; i++) {
        num += Math.random() * (max/numDice);
    }    
    return num;
}

加权随机数

JSFiddle: http://jsfiddle.net/797qhcza/1/


12
这是一个简单快捷的解决方案,可以通过添加更多数字(例如4 x(0-25))轻松加权,从而为分布提供漂亮的钟形曲线! - Falco
8
这是一段非常棒的代码,我觉得我爱上它了。简单、快速、高效;非常好的答案。谢谢你发布这个。 - ctwheels
14
很好的回答,但是如果有人想用它来生成正态分布,这种方法效率相当低(而且你需要进行转换才能获得所需的平均值和标准差)。更高效的选择是Box-Muller变换,如果你懂一点数学,实现和理解起来都很容易。 - Brendon
1
@RaziShaban 这很直观:只有一种骰子组合可以加起来得到2(就是两个1),但是有6种不同的组合可以加起来得到7(6-1,5-2,4-3,3-4,2-5,1-6)。如果你将其推广到N面骰子,那么峰值总是N+1。 - Barmar
2
@RaziShaban 随机变量的研究是统计学中的核心部分。随着骰子数量的增加,我们逐渐接近正态分布的事实是著名的中心极限定理。 - BlueRaja - Danny Pflughoeft
显示剩余3条评论

49

这里有一些具体的解决方案,让我为您描述一下通用解决方案。问题是:

  • 我有一个源,提供0到1之间大致均匀分布的随机数。
  • 我希望产生一系列遵循不同分布的随机数。

这个问题的通用解决方案是计算您所需分布的分位函数,然后将分位函数应用于均匀源的输出。

分位函数是所需分布函数反函数。分布函数是指曲线下部分面积等于随机选择项在该部分内的概率的函数。

在此处提供如何实现它的示例:

http://ericlippert.com/2012/02/21/generating-random-non-uniform-data/

其中的代码是 C# 的,但原理适用于任何语言;应该可以轻松地将解决方案适应到 JavaScript 中。


2
我喜欢这个方法。可能想要补充一下,有一个JavaScript库可以生成高斯(和其他非正态)分布:http://simjs.com/random.html - Floris

36

取数值数组等方式效率不高,应该采用一个映射函数,将0到100之间的随机数映射到所需分布。因此,在您的情况下,可以采用f(x)=-(1/25)x2+4x来获得一个在范围中间有最多数值的分布。

Distribution


2
我们实际上不知道需要什么分布。 "主要是40-60" 对我来说意味着一个钟形曲线。 - Lefty
是的,你说得对,也许你需要更好的映射,但这很琐碎。 - iCaramba
3
我相信你说的话,因为这不是我的专业领域。你能调整函数并显示新曲线吗? - Lefty
1
@Lefty - 简化的钟形曲线,适用于0到100之间的x(取自此问题):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2 - Sphinxxx
@Sphinxxx,那不是正态分布曲线,那是正弦曲线。正态分布曲线从未触及x轴。 - BlueRaja - Danny Pflughoeft
@BlueRaja-DannyPflughoeft 这是真的,但正如我所说,这只是一种简化,而且仅限于0到100之间。 - Sphinxxx

17

我可能会设置一个“机会”,让数字有可能“越界”。在这个例子中,有20%的机会数字会在1-100之间,否则在40-60之间:

$(function () {
    $('button').click(function () {
        var outOfBoundsChance = .2;
        var num = 0;
        if (Math.random() <= outOfBoundsChance) {
            num = getRandomInt(1, 100);
        } else {
            num = getRandomInt(40, 60);
        }
        $('#out').text(num);
    });
    
    function getRandomInt(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<button>Generate</button>
<div id="out"></div>

fiddle: http://jsfiddle.net/kbv39s9w/


5
也许有更多统计细节的人可以纠正我,虽然这样做已经达到了OP所要求的目的(所以我投了赞成票),但这并不能真正地以20%的概率选择一个超出界限的数字,对吗?在这个解决方案中,有20%的机会从1-100中选择一个数字,其中包括40-60。这实际上不是(0.2 * 0.8)16%的概率去选择一个超出范围的数字吗,还是我漏掉了什么? - Josh
不,你说得对。只是我的措辞有误。我会纠正的。谢谢! - Bitwise Creative
1
@Josh - 这非常准确。这是一个简单的证明,看起来像这样 http://jsfiddle.net/v51z8sd5/ 。它将显示超出范围的数字百分比,并在0.16(16%)左右徘徊。 - Travis J

15

几年前我需要解决这个问题,我的解决方案比其他答案都更为简单。

我生成了在范围内的三个随机数,并取其平均值。这将结果拉向中心,但仍然可以完全达到极端值。


7
相较于BlueRaja的回答,本回答有何优点/不同之处?在那里,他对(2,3,...任意数量)个随机数求和并取平均值。当你使用BellFactor为3时,结果与你的相同。 - Floris
@floris 好的,我不是用C系列语言编程的,所以那个答案看起来甚至不像是在做与我的答案一样的事情,直到我现在重新阅读它。我通过一点试错创建了我的方法,并发现3个随机数是正确的选择。此外,我的方法可以在一行中完成,并且仍然容易理解。 - Lefty
2
真的吗?你不认为JS和C之间有任何相似之处吗?好吧,那么,我只能说我都不会这些语言,也不会Java,对我来说,它们与我熟悉的语言相比都很相似。 - Lefty
1
公正的观点,我实际上只被标题所吸引,因为这是我自己解决的问题,而且我对自己的解决方法感到非常自豪。再次强调,直到你刚才说了这句话,我才意识到这是一个js问题。真幸运,因为我的技巧并不依赖于语言,有些人似乎认为这是一个有用的答案。 - Lefty
5
JavaScript实际上是一种C家族语言,但是嗯,好吧。 - Joren
显示剩余4条评论

14

看起来很蠢,但你可以使用rand两次:

var choice = Math.random() * 3;
var result;

if (choice < 2){
    result = Math.random() * 20 + 40; //you have 2/3 chance to go there
}
else {
    result = Math.random() * 100 + 1;
}

11

当然可以。生成1-100的随机数,如果数字小于30,则在1-100范围内生成数字,否则在40-60范围内生成。


11

有很多不同的方法来生成这样的随机数。其中一种方法是计算多个均匀随机数的总和。您计算的随机数数量以及它们的范围将决定最终分布的外观。

您总结的数字越多,它就越偏向于中心。在您的问题中已经提出了使用一个随机数的总和,但正如您所注意到的那样,它不偏向范围的中心。其他答案建议使用2个随机数的和3个随机数的和

您可以通过取更多随机数的总和来使其更加偏向范围的中心。极端情况下,您可以取99个随机数的总和,每个随机数都是0或1。那将是一个二项式分布。(在某种意义上,二项式分布可以看作是正态分布的离散版本)。理论上,这仍然可以覆盖整个范围,但它对中心的偏倚太大,您永远不应该期望看到它达到端点。

这种方法意味着您可以调整偏差的程度。


8
使用类似以下方式的内容怎么样:

像这样使用:

var loops = 10;
var tries = 10;
var div = $("#results").html(random());
function random() {
    var values = "";
    for(var i=0; i < loops; i++) {
        var numTries = tries;
        do {
            var num = Math.floor((Math.random() * 100) + 1);
            numTries--;
        }
        while((num < 40 || num >60) && numTries > 1)
        values += num + "<br/>";
    }
    return values;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="results"></div>

我编写的代码允许设置几个变量:
loops = 结果数量
tries = 函数尝试在while循环中运行之前获取40-60之间数字的次数。
额外的好处:它使用了do while!!! 是最棒的。

8
您可以编写函数,根据权重将介于[0,1)之间的随机值映射到[1,100]。考虑下面的例子:在这里,值0.95映射到[61,100]之间的值。实际上,我们有.05/.1=0.5,当映射到[61,100]时,得到81。以下是该函数:

/*
 * Function that returns a function that maps random number to value according to map of probability
 */
function createDistributionFunction(data) {
  // cache data + some pre-calculations
  var cache = [];
  var i;
  for (i = 0; i < data.length; i++) {
    cache[i] = {};
    cache[i].valueMin = data[i].values[0];
    cache[i].valueMax = data[i].values[1];
    cache[i].rangeMin = i === 0 ? 0 : cache[i - 1].rangeMax;
    cache[i].rangeMax = cache[i].rangeMin + data[i].weight;
  }
  return function(random) {
    var value;
    for (i = 0; i < cache.length; i++) {
      // this maps random number to the bracket and the value inside that bracket
      if (cache[i].rangeMin <= random && random < cache[i].rangeMax) {
        value = (random - cache[i].rangeMin) / (cache[i].rangeMax - cache[i].rangeMin);
        value *= cache[i].valueMax - cache[i].valueMin + 1;
        value += cache[i].valueMin;
        return Math.floor(value);
      }
    }
  };
}

/*
 * Example usage
 */
var distributionFunction = createDistributionFunction([
  { weight: 0.1, values: [1, 40] },
  { weight: 0.8, values: [41, 60] },
  { weight: 0.1, values: [61, 100] }
]);

/*
 * Test the example and draw results using Google charts API
 */
function testAndDrawResult() {
  var counts = [];
  var i;
  var value;
  // run the function in a loop and count the number of occurrences of each value
  for (i = 0; i < 10000; i++) {
    value = distributionFunction(Math.random());
    counts[value] = (counts[value] || 0) + 1;
  }
  // convert results to datatable and display
  var data = new google.visualization.DataTable();
  data.addColumn("number", "Value");
  data.addColumn("number", "Count");
  for (value = 0; value < counts.length; value++) {
    if (counts[value] !== undefined) {
      data.addRow([value, counts[value]]);
    }
  }
  var chart = new google.visualization.ColumnChart(document.getElementById("chart"));
  chart.draw(data);
}
google.load("visualization", "1", { packages: ["corechart"] });
google.setOnLoadCallback(testAndDrawResult);
<script src="https://www.google.com/jsapi"></script>
<div id="chart"></div>


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