关于javascript:获取一个专注于中心的随机数

Get a random number focused on center

是否可以得到1-100之间的随机数,并将结果主要保持在40-60范围内?我的意思是,它很少会超出那个范围,但我希望它主要在那个范围内…是否可以使用javascript/jquery?

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


最简单的方法是从0到50生成两个随机数,然后将它们相加。

这给出了一个偏向50的分布,同样地,将两个骰子偏向7。

事实上,通过使用更多的"骰子"(如@falco建议的那样),您可以更接近钟形曲线:

1
2
3
4
5
6
7
function weightedRandom(max, numDice) {
    var num = 0;
    for (var i = 0; i < numDice; i++) {
        num += Math.random() * (max/numDice);
    }    
    return num;
}

Weighted random numbers

J小提琴:http://jsfiddle.net/797qhcza/1/


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

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

这个问题的一般解决方案是计算出所需分布的分位数函数,然后将分位数函数应用到统一源的输出。

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

我在这里举一个例子说明如何做到这一点:

Generating random non-uniform data in C#

其中的代码是C,但原则适用于任何语言;将解决方案改编为JavaScript应该很简单。


获取数字数组等是不有效的。您应该使用一个0到100之间随机数的映射,并映射到您需要的分布。因此,在您的例子中,您可以使用f(x)=-(1/25)x2+4x得到一个分布,其中最大的值在您的范围中间。

Distribution


我可能会做一些类似设置一个"机会",让这个号码"越界"。在这个例子中,20%的概率这个数字将是1-100,否则,40-60:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(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;
    }
});
1
2
3
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">

<button>Generate</button>

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


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

我在边界之间生成了3个随机数,并取其平均值。这会将结果拉向中心,但使其完全可能到达末端。


它看起来很愚蠢,但你可以用兰德两次:

1
2
3
4
5
6
7
8
9
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;
}

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


产生这种随机数的方法有很多种。一种方法是计算多个均匀随机数的和。求和的随机数及其范围将决定最终分布的外观。

你总结的数字越多,它就越偏向中心。使用1个随机数之和已经在你的问题中提出,但正如你注意到的,并没有偏向于范围的中心。其他答案建议使用2个随机数之和或3个随机数之和。

通过取更多随机数的和,可以得到对范围中心更大的偏差。在极端情况下,你可以取99个随机数之和,每一个都是0或1。这是二项式分布。(二项分布在某种意义上可以看作是正态分布的离散形式)。理论上,这仍然可以覆盖整个范围,但是它对中心有太多的偏向,你不应该期望看到它到达终点。

这种方法意味着你可以调整你想要多少偏见。


您可以编写一个函数,根据权重在[0, 1)[1, 100]之间映射随机值。考虑这个例子:

0.0-1.0 to 1-100 by percentage weight

这里,值0.95映射到[61, 100]之间的值。实际上,我们有.05 / .1 = 0.5,当映射到[61, 100]时,生成81

功能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
 * 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);
1
<script src="https://www.google.com/jsapi">


用这样的东西怎么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}
1
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">

我对它的编码方式允许您设置几个变量:
循环=结果数
尝试次数=函数在停止运行while循环之前尝试获取介于40到60之间的数字的次数

附加奖励:使用do while!!!!最棒的是


这是一个在3/4 40-60和1/4范围之外的加权解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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) +"
"
;
  }
  output.innerHTML = o;
}

test();
1
[cc lang="javascript"]

1
2
3
4
5
6
7
8
9
10
11
</P><hr><P>我建议使用beta发行版生成一个介于0-1之间的数字,然后将其放大。它非常灵活,可以创建许多不同的分布形状。</P><P>这是一个快速而肮脏的取样器:</P>[cc lang="javascript"]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))
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
1
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">


针对这个问题的最佳解决方案是Blueraja-Danny Pflughoeft提出的解决方案,但我认为一个更快更通用的解决方案也值得一提。

当我必须生成满足以下两个要求的随机数(字符串、坐标对等)

  • 结果集非常小。(不大于16K数字)
  • 结果集是谨慎的。(仅与整数类似)
  • 我通常首先创建一个满足要求的数字数组(字符串、坐标对等)(在您的例子中:一个包含更多可能的数字的数组多次),然后选择该数组的一个随机项。这样,每个项目只需要调用一次昂贵的随机函数。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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);
    }

    分布

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

    解决方案

    1
    2
    3
    4
    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);

    通用解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 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);
    }


    如果可以使用gaussian函数,请使用它。此函数返回带有average 0sigma 1的正常数。

    这个数字的95%在average +/- 2*sigma之内。你的average = 50sigma = 5所以

    1
    randomNumber = 50 + 5*gaussian()

    实现这一点的最佳方法是生成一个随机数,该随机数平均分布在一组特定的数字中,然后对介于0和100之间的集合应用投影函数,在该集合中投影更可能达到所需的数字。

    通常,实现这一点的数学方法是绘制所需数字的概率函数。我们可以使用钟形曲线,但是为了更简单的计算,我们只使用翻转抛物线。

    让我们做一个抛物线,它的根在0和100之间,不会使它倾斜。我们得到如下方程:

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

    现在,曲线下0到100之间的所有区域都代表了我们想要生成数字的第一组区域。在那里,这一代完全是随机的。所以,我们需要做的就是找到第一组的边界。

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

    1
    2
    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和166666之间的数字。然后,我们只需要取这个数字,并把它投影到第二个集合,即0到100之间。

    我们知道我们生成的随机数是抛物线的一个积分,输入x在0到100之间。这意味着我们只需要假设随机数是f(x)的结果,并求出x。

    在这种情况下,f(x)是一个三次方程,以F(x) = ax^3 + bx^2 + cx + d = 0的形式,以下陈述是正确的:

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

    对x求出实际的随机数,它保证在[0,100]范围内,并且接近中心的可能性比边缘大得多。


    这个答案很好。但我想发布不同情况下的实现指令(我不喜欢JavaScript,所以希望您能理解)。

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

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

    可以缓存初始静态信息:

  • 所有重量之和(样本108)
  • 范围选择边界。基本上是这个公式:Boundary[n] = Boundary[n - 1] + weigh[n - 1]Boundary[0] = 0。样品有EDOCX1[2]
  • 数字生成:

  • 从范围[0,所有权重之和]中生成随机数N
  • for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  • ith区间,生成该区间的随机数。
  • 性能优化的附加说明。范围不必按升序或降序排序,因此对于更快的范围,具有最高权重的查找范围应首先进行,具有最低权重的查找范围应最后进行。