Получить случайное число, ориентированное на центр

Можно ли получить случайное число между 1-100 и сохранить результаты в основном в диапазоне 40-60? Я имею в виду, он редко выходит из этого диапазона, но я хочу, чтобы он был в основном в этом диапазоне... Возможно ли с помощью JavaScript/jQuery?

Сейчас я просто использую базовый Math.random() * 100 + 1.

Ответ 1

Самый простой способ - создать два случайных числа из 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;
}

Weighted random numbers

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

Ответ 2

Здесь у вас есть хорошие ответы, которые дают конкретные решения; позвольте мне описать вам общее решение. Проблема заключается в следующем:

  • У меня есть источник более или менее равномерно распределенных случайных чисел от 0 до 1.
  • Я хочу создать последовательность случайных чисел, которые следуют за другим распределением.

Общее решение этой проблемы состоит в том, чтобы выработать функцию квантиля вашего желаемого распределения, а затем применить функцию квантиля к выходу вашего однородного источника.

Функция квантиля является обратной интегралом от вашей желаемой функции распределения. Функция распределения - это функция, в которой площадь под частью кривой равна вероятности того, что случайно выбранный элемент будет в этой части.

Приведу пример, как это сделать:

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

Код в С#, но принципы применимы к любому языку; это должно быть легко адаптировать решение к JavaScript.

Ответ 3

Взятие массивов чисел и т.д. неэффективно. Вы должны взять картографию, которая принимает случайное число от 0 до 100 и карты к распределению, в котором вы нуждаетесь. Поэтому в вашем случае вы можете взять f(x)=-(1/25)x2+4x, чтобы получить дистрибутив с наибольшим значением в середине вашего диапазона.

Distribution

Ответ 4

Я мог бы сделать что-то вроде настройки "шанса" для того, чтобы число было разрешено "выйти за пределы". В этом примере вероятность 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>

Ответ 5

Мне нужно было решить эту проблему несколько лет назад, и мое решение было проще, чем любой другой ответ.

Я создал 3 рандома между границами и усреднил их. Это приводит результат к центру, но оставляет его полностью возможным для достижения конечностей.

Ответ 6

Это выглядит глупо, но вы можете использовать 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;
}

Ответ 7

Конечно, это возможно. Сделайте случайный 1-100. Если число равно < 30, то генерируйте число в диапазоне 1-100, если оно не генерируется в диапазоне 40-60.

Ответ 8

Существует множество способов генерации таких случайных чисел. Один из способов сделать это - вычислить сумму кратных равномерно случайных чисел. Сколько случайных чисел вы суммируете и какой их диапазон будет определять, как будет выглядеть окончательное распределение.

Чем больше цифр вы суммируете, тем больше будет смещено к центру. Использование суммы 1 случайного числа уже было предложено в вашем вопросе, но, как вы заметили, не смещается в сторону центра диапазона. Другие ответы предлагают использовать сумму из 2 случайных чисел или сумму из трех случайных чисел.

Вы можете получить еще большее смещение к центру диапазона, взяв сумму более случайных чисел. В крайнем случае вы могли взять сумму из 99 случайных чисел, каждая из которых была либо 0, либо 1. Это было бы биномиальное распределение. (Биномиальные распределения в некотором смысле можно рассматривать как дискретную версию нормальных распределений). Это теоретически может охватывать весь диапазон, но у него так много предвзятости по отношению к центру, что вы никогда не должны ожидать, что он достигнет конечных точек.

Этот подход означает, что вы можете настроить только то, сколько вы хотите.

Ответ 9

Как насчет того, чтобы использовать что-то вроде этого:

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>

Ответ 10

Вы можете написать функцию, которая отображает случайные значения между [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.

Вот функция:

/*
 * 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>

Ответ 11

Здесь взвешенное решение при 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>

Ответ 12

Хорошо, поэтому я решил добавить еще один ответ, потому что мне показалось, что мой последний ответ, а также большинство ответов здесь, используют какой-то полустатистический способ получения результата возврата типа колокольчика. Код, который я приводил ниже, работает так же, как при броске кубика. Поэтому сложнее всего получить 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>

Ответ 13

Я бы рекомендовал использовать бета-дистрибутив , чтобы создать число от 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))
}

Ответ 14

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);
}

Ответ 15

Лучшее решение, нацеленное на эту проблему, - это вариант, предложенный BlueRaja - Danny Pflughoeft, но я думаю, что стоит упомянуть еще более быстрое и более общее решение.


Когда мне приходится генерировать случайные числа (строки, координатные пары и т.д.), удовлетворяющие двум требованиям

  • Результирующий набор довольно мал. (не более 16K номеров)
  • Результирующий набор нечеткий. (например, только целые числа)

Обычно я начинаю с создания массива чисел (строк, координатных пар и т.д.), выполняющих требование (в вашем случае: массив чисел, содержащих более вероятные несколько раз.), затем выберите случайный элемент этого массив. Таким образом, вам нужно только вызвать дорогостоящую случайную функцию один раз за элемент.

Ответ 16

Вы можете использовать случайное число помощников для генерации случайных чисел в 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);
}

Ответ 17

Распределение

 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;
}

Ответ 18

Лучший способ сделать это - создать случайное число, которое равномерно распределено по определенному набору чисел, а затем применить функцию проецирования к множеству между 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 just use 166,666, because rounding up would make the target out of bounds)

Итак, мы знаем, что нам нужно сгенерировать число где-то между 0 и 166 666. Тогда нам просто нужно взять это число и проецировать его на наш второй набор, который находится между 0 и 100.

Мы знаем, что случайное число, которое мы породили, является некоторым интегралом нашей параболы с входом х между 0 и 100. Это означает, что мы просто должны предположить, что случайное число является результатом F (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] и гораздо более высокая вероятность быть ближе к центру, чем кромки.

Ответ 19

Если вы можете использовать функцию gaussian, используйте ее. Эта функция возвращает нормальное число с average 0 и sigma 1.

95% этого числа находится в пределах average +/- 2*sigma. Ваши average = 50 и sigma = 5 так

randomNumber = 50 + 5*gaussian()

Ответ 20

Этот ответ действительно хорош. Но я бы хотел опубликовать инструкции по внедрению (я не в JavaScript, поэтому надеюсь, что вы поймете) для разных ситуаций.


Предположим, что у вас есть диапазоны и веса для каждого диапазона:

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. Образец имеет Boundary = {0, 1, 3, 103, 108}

Генерация чисел:

  • Создать случайное число N из диапазона [0, сумма всех весов).
  • for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
  • Возьмите i th диапазон и создайте случайное число в этом диапазоне.

Дополнительное примечание для оптимизации производительности. Диапазоны не нужно заказывать ни по возрастанию, ни по убыванию, поэтому для более быстрого поиска диапазона диапазона, который имеет наибольший вес, следует идти первым, а один с наименьшим весом должен длиться последним.