得到一个集中在中心的随机数


238

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

现在,我只是使用基本的Math.random() * 100 + 1





20
我喜欢这个问题的去向,但我认为它应该更具体。您是否需要Z分布(钟形曲线),三角形分布或某种锯齿形分布?我认为回答这个问题有多种可能性。
Patrick Roberts 2015年

12
这可以用javascript完成,但可以肯定与jQuery无关... :)
A. Wolff 2015年

Answers:


397

最简单的方法是从0-50生成两个随机数并将它们相加。

这样可以使分配偏向50,以相同的方式将两个骰子偏向7。

实际上,通过使用大量的“骰子” (如@Falco所建议的),您可以更接近钟形曲线:

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

加权随机数

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


12
这是一个简单快捷的解决方案,可以通过添加更多数字(例如4 x(0-25))来轻松地加权更多,并且可以为您分配一个漂亮的钟形曲线!
法尔科

8
这是一段很棒的代码。我想我爱上了它。简单,快速,高效;好答案。感谢您发布此信息。
ctwheels

14
很好的答案,但是如果有人打算使用它来生成正态分布,则效率很低(并且您需要对其进行变换以获得所需的均值和标准差)。一个更有效的选择是Box-Muller变换,如果您懂一点数学,就很容易实现和理解。
布伦登

1
@RaziShaban这是非常直观的:死角组合只有一个组合,总共有2个(只是蛇眼),但是有6种不同的组合合起来总共有7个(6-1、5-2、4-3、3- 4、2-5、1-6)。如果泛化为N边骰子,则峰值始终为N + 1。
Barmar

2
@RaziShaban对随机变量的研究是统计的核心部分。随着我们增加骰子,我们接近正态分布的事实是著名的中央极限定理
BlueRaja-Danny Pflughoeft 2015年

48

您在这里有一些可以给出具体解决方案的好答案;让我为您介绍一般的解决方案。问题是:

  • 我有一个在0到1之间或多或少均匀分布的随机数的来源。
  • 我希望产生一个遵循不同分布的随机数序列。

这个问题的一般解决方案是制定出位数功能所需的分布,然后应用位数功能制服源的输出。

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

我在这里举一个例子:

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

其中的代码是C#,但是这些原理适用于任何语言。使解决方案适应JavaScript应该很简单。


2
我喜欢这种方法。可能想补充一下,存在一个确实会生成高斯分布(和其他非正态分布)的javascript库:simjs.com/random.html
Floris

36

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

分配


2
我们实际上并不知道需要什么分发。“主要是40-60”对我来说意味着一个钟形曲线。
2015年

是的,您是对的,也许您需要更好的映射,但这并不重要
iCaramba 2015年

3
我相信你,因为这不是我的专业领域。您可以调整功能并显示新曲线吗?
左撇子

1
@Lefty- x0到100之间的简化钟形曲线(取自该问题):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2
Sphinxxx

@Sphinxxx不是钟形曲线,而是正弦曲线。钟形曲线永远不会触及x轴。
BlueRaja-Danny Pflughoeft

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>

小提琴:http : //jsfiddle.net/kbv39s9w/


5
也许有更多统计细节的人可以纠正我,尽管这确实达到了OP的期望(因此我投了赞成票),但这不会真正地在20%的时间内选择#,对吗?在此解决方案中,您有20%的时间将有机会从1-100中选择一个#,其中包括40-60。选择#超出极限实际上不是(0.2 * 0.8)16%,还是我错过了一些东西?
乔什(Josh)2015年

不,你是对的。这只是我的措辞。我会改正的。谢谢!
位创意

1
@Josh-不错。这是jsfiddle.net/v51z8sd5的简单证明。它将显示超出范围的数字百分比,并徘徊在0.16(16%)左右。
特拉维斯J

15

几年前,我需要解决这个问题,而我的解决方案比其他任何答案都容易。

我在边界之间生成了3个随机数并将其平均。这将结果拉向中心,但完全有可能到达四肢。


7
这与BlueRaja的答案有何不同?在那里,他采用(2,3,...您想要的任何数字)随机数的总和并取平均值。当您使用的结果是相同的你BellFactor的3
弗洛里斯

@floris很好,我没有使用C语言族编写代码,因此直到我现在重新阅读它为止,答案似乎都没有像在做我的答案一样。我通过反复试验创建了自己的方法,发现3个随机数是正确的数字。而且,我的操作可以一站式完成,而且仍然易于理解。
2015年

2
真?您不认为JS和C之间没有相似之处吗?好吧,好吧,我只能说我不会说那些语言,也不会说Java,与我熟悉的语言相比,Java对我来说都是相似的。
左撇子

1
公平的说,我实际上只是被标题所吸引,因为这是我自己解决的问题,并对我的工作方式感到非常自豪。再说一次,直到您刚才这么说,我才意识到这是一个js问题。确实很幸运,因为我的技术与语言无关,并且有些人似乎认为这是一个有用的答案。
左撇子

5
JavaScript实际上一种C族语言,但是很好。
Joren 2015年

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个随机数总和,但是正如您所注意到的,它并不偏向范围的中心。其他答案建议使用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>

我编码的方式允许您设置几个变量:
循环 = 尝试的结果数
次数=函数将停止通过while循环运行之前尝试获得40-60之间的数字的次数

额外的好处:它可以同时使用!!!最好的很棒


8

你可以写一个随机值之间的映射功能[0, 1),以[1, 100]按重量。考虑以下示例:

重量百分比0.0-1.0至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>


7

这是超出此范围的3/4 40-60和1/4处的加权解决方案。

function weighted() {

  var w = 4;

  // number 1 to w
  var r = Math.floor(Math.random() * w) + 1;

  if (r === 1) { // 1/w goes to outside 40-60
    var n = Math.floor(Math.random() * 80) + 1;
    if (n >= 40 && n <= 60) n += 40;
    return n
  }
  // w-1/w goes to 40-60 range.
  return Math.floor(Math.random() * 21) + 40;
}

function test() {
  var counts = [];

  for (var i = 0; i < 2000; i++) {
    var n = weighted();
    if (!counts[n]) counts[n] = 0;
    counts[n] ++;
  }
  var output = document.getElementById('output');
  var o = "";
  for (var i = 1; i <= 100; i++) {
    o += i + " - " + (counts[i] | 0) + "\n";
  }
  output.innerHTML = o;
}

test();
<pre id="output"></pre>


6

好的,所以我决定添加另一个答案,因为我觉得我的最后一个答案以及此处的大多数答案都使用某种半统计方法来获取钟形曲线类型的结果返回。我在下面提供的代码与掷骰子时的工作方式相同。因此,最难获得1或99,但最容易获得50。

var loops = 10; //Number of numbers generated
var min = 1,
    max = 50;
var div = $("#results").html(random());

function random() {
    var values = "";
    for (var i = 0; i < loops; i++) {
        var one = generate();
        var two = generate();
        var ans = one + two - 1;
        var num = values += ans + "<br/>";
    }
    return values;
}

function generate() {
    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>
<div id="results"></div>


6

我建议使用Beta发行版生成0-1之间的数字,然后按比例放大。它非常灵活,可以创建许多不同形状的分布。

这是一个快速而肮脏的采样器:

rbeta = function(alpha, beta) {
 var a = 0   
 for(var i = 0; i < alpha; i++)   
    a -= Math.log(Math.random())

 var b = 0   
 for(var i = 0; i < beta; i++)   
    b -= Math.log(Math.random())

  return Math.ceil(100 * a / (a+b))
}

5
var randNum;
// generate random number from 1-5
var freq = Math.floor(Math.random() * (6 - 1) + 1);
// focus on 40-60 if the number is odd (1,3, or 5)
// this should happen %60 of the time
if (freq % 2){
    randNum = Math.floor(Math.random() * (60 - 40) + 40);
}
else {
    randNum = Math.floor(Math.random() * (100 - 1) + 1);
}

5

针对此问题的最佳解决方案是BlueRaja-Danny Pflughoeft提出的解决方案,但我认为也应提及一种更快,更通用的解决方案。


当我必须生成满足两个条件的随机数(字符串,坐标对等)时

  1. 结果集很小。(不超过16K数字)
  2. 结果集谨慎。(仅适用于整数)

我通常首先创建一个满足要求的数字数组(字符串,坐标对等)(在您的情况下:多次包含更可能的数字数组),然后选择该数组的随机项。这样,您只需为每个项目调用一次昂贵的随机函数。


1
如果您要预填充一系列选择,则以后也可以将它们洗牌。然后,您可以按顺序抓住它们,直到用完为止。如果/当您到达列表末尾时,请再次随机播放。
Geobits

@Geobits对列表进行混洗比随机选择其元素之一要耗费大量资源。如果列表必须是可预测的,则这是一个不错的选择。
mg30rg

1
但是您只在列表的每个周期执行一次,而不是每次执行一次。如果您对此进行了预处理(因为无论如何您都需要进行预处理,那么我认为这很好),那么随后获取每个数字将非常快。您可以在有停机时间时重新洗牌,或者知道一点点都不需要随机数。只是提供它作为替代,两者都有(不利)优势。
Geobits,2015年

@Geobits如果您按照自己的方式进行操作,则“单概率”数字将“下降”,直到重新填充后它们才可能出现。(即,如果模拟两个骰子的投掷,您将没有丝毫机会两次获得大于2的数字。)
mg30rg 2015年

1
这是一个很多更好的理由不使用它,除了罕见的应用中,这是好的;)
Geobits

4

分配

 5% for [ 0,39]
90% for [40,59]
 5% for [60,99]

var f = Math.random();
if (f < 0.05) return random(0,39);
else if (f < 0.95) return random(40,59);
else return random(60,99);

通用解决方案

random_choose([series(0,39),series(40,59),series(60,99)],[0.05,0.90,0.05]);

function random_choose (collections,probabilities)
{
    var acc = 0.00;
    var r1 = Math.random();
    var r2 = Math.random();

    for (var i = 0; i < probabilities.length; i++)
    {
      acc += probabilities[i];
      if (r1 < acc)
        return collections[i][Math.floor(r2*collections[i].length)];
    }

    return (-1);
}

function series(min,max)
{
    var i = min; var s = [];
    while (s[s.length-1] < max) s[s.length]=i++;
    return s;
}

4

您可以使用辅助随机数来生成40-60或1-100的随机数:

// 90% of random numbers should be between 40 to 60.
var weight_percentage = 90;

var focuse_on_center = ( (Math.random() * 100) < weight_percentage );

if(focuse_on_center)
{
	// generate a random number within the 40-60 range.
	alert (40 + Math.random() * 20 + 1);
}
else
{
	// generate a random number within the 1-100 range.
	alert (Math.random() * 100 + 1);
}


4

如果可以使用该gaussian功能,请使用它。此函数使用average 0和返回正常数字sigma 1

此数字的95%在内average +/- 2*sigma。您的average = 50sigma = 5等等

randomNumber = 50 + 5*gaussian()

3

最好的方法是生成一个随机数,该随机数平均分布在一组特定的数字中,然后将投影函数应用于0到100之间的集合,其中投影更有可能达到您想要的数字。

通常,实现此目标的数学方法是绘制所需数字的概率函数。我们可以使用钟形曲线,但是为了简化计算,我们仅使用抛物线抛物线。

让我们制作一个抛物线,使其根在0和100处而不会使其偏斜。我们得到以下等式:

f(x) = -(x-0)(x-100) = -x * (x-100) = -x^2 + 100x

现在,曲线下0到100之间的所有区域代表我们要生成数字的第一个集合。在那里,生成是完全随机的。因此,我们所需要做的就是找到第一个集合的边界。

下限当然是0。上限是函数在100处的积分,即

F(x) = -x^3/3 + 50x^2
F(100) = 500,000/3 = 166,666.66666 (let's just use 166,666, because rounding up would make the target out of bounds)

因此,我们知道我们需要生成一个介于0到166,666之间的数字。然后,我们只需要获取该数字并将其投影到我们的第二组(介于0和100之间)即可。

我们知道,我们生成的随机数是抛物线的某个整数,输入x在0到100之间。这意味着我们只需要假设随机数是F(x)的结果,然后求解x。

在这种情况下,F(x)是三次方程式,形式为F(x) = ax^3 + bx^2 + cx + d = 0,以下语句为真:

a = -1/3
b = 50
c = 0
d = -1 * (your random number)

将其求解为x会得到所需的实际随机数,该随机数保证在[0,100]范围内,并且靠近边缘的可能性比边缘高得多。


3

这个答案真的很好。但是我想针对不同情况发布实现说明(我不喜欢JavaScript,所以希望您能理解)。


假设每个范围都有范围和权重:

ranges - [1, 20], [21, 40], [41, 60], [61, 100]
weights - {1, 2, 100, 5}

初始静态信息,可以缓存:

  1. 所有权重之和(样本中为108)
  2. 范围选择边界。它基本上是这个公式:Boundary[n] = Boundary[n - 1] + weigh[n - 1]Boundary[0] = 0。样品有Boundary = {0, 1, 3, 103, 108}

数字生成:

  1. 生成N范围为[0,所有权重之和)的随机数。
  2. for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  3. i第一个范围并生成该范围内的随机数。

有关性能优化的附加说明。范围不必按升序或降序排序,因此,对于较快的范围查找,应首先将权重最高的范围放到最远,而权重最低的范围应放到最后。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.