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

238

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

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

Даррил хаффман
источник
7
en.wikipedia.org/wiki/…
Роко Бульян
4
codetheory.in/…
Джош
1
возможный дубликат: stackoverflow.com/questions/1527803/…
Махеди Сабудж
20
Мне нравится, где этот вопрос идет, но я думаю, что он должен быть более конкретным. Вы хотите Z-распределение (кривая колокола), треугольное распределение или какое-то пилообразное распределение? Есть несколько возможностей ответить на этот вопрос, на мой взгляд.
Патрик Робертс
12
Это можно сделать в javascript, но наверняка не имеет ничего общего с jQuery ... :)
A. Wolff

Ответы:

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/

BlueRaja - Дэнни Пфлугхофт
источник
12
Это простое и быстрое решение, которое можно легко взвесить больше, добавив больше чисел, например, 4 x (0-25), и даст вам хорошую кривую для распределения!
Фалько,
8
Это фантастический кусок кода. Я думаю, что я влюблен в это. Простой, быстрый, эффективный; отличный ответ. Спасибо за публикацию.
ctwheels
14
Отличный ответ, но в случае, если кто-то намеревается использовать это для генерации нормального распределения, это довольно неэффективно (и вам нужно преобразовать его, чтобы получить желаемое среднее значение и стандартное отклонение). Более эффективным вариантом будет преобразование Бокса-Мюллера, которое довольно легко реализовать и понять, если вы немного разбираетесь в математике.
Брендон
1
@RaziShaban Это довольно интуитивно понятно: есть только одна комбинация бросков кубика, которая добавляет до 2 (только змеиные глаза), но есть 6 различных комбинаций, которые добавляют до 7 (6-1, 5-2, 4-3, 3- 4, 2-5, 1-6). Если вы обобщаете на N-сторонние кости, пик всегда N + 1.
Бармар
2
@RaziShaban Изучение случайных величин является центральной частью статистики. Тот факт, что по мере увеличения игральных костей мы приближаемся к нормальному распределению, является знаменитой центральной предельной теоремой .
BlueRaja - Дэнни Пфлугхофт
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

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

iCaramba
источник
2
Мы на самом деле не знаем, какое распределение необходимо. «В основном 40-60» подразумевает для меня колокольчик.
Левша
да, вы правы, может быть, вам нужно лучшее отображение, но это тривиально
iCaramba
3
Я поверю на ваше слово, потому что это не моя область знаний. Не могли бы вы настроить функцию и отобразить новую кривую?
Левша
1
@Lefty - Упрощенная кривая звонка xот 0 до 100 (взято из этого вопроса ):y = (Math.sin(2 * Math.PI * (x/100 - 1/4)) + 1) / 2
Sphinxxx
@Sphinxxx Это не кривая колокола, это кривая греха. Кривая колокола никогда не касается оси X.
BlueRaja - Дэнни Пфлюгофт
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
Может быть, кто-то с большим количеством статистических данных может исправить меня, и хотя это действительно дает то, что ищет ОП (поэтому я проголосовал), но это не будет действительно выбирать # из границ 20% времени, верно? В этом решении 20% времени у вас будет возможность выбрать # из 1-100, что включает 40-60. Разве это не будет (0,2 * 0,8) 16%, чтобы выбрать # из границ, или я что-то упустил?
Джош
Нет, ты прав. Это просто моя формулировка. Я исправлю это. Спасибо!
Побитовый креатив
1
@ Джош - Это прекрасное место. Вот простое доказательство того, как это выглядит jsfiddle.net/v51z8sd5 . Он покажет процент чисел, найденных за пределами границ, и колеблется около 0,16 (16%).
Трэвис Дж
15

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

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

Левша
источник
7
Чем это лучше / отличается от ответа BlueRaja? Там он берет сумму (2,3, ... любое число, которое вы хотите) случайных чисел и принимает среднее значение. Результат совпадает с вашим, когда вы используете BellFactor3
Флорис
@ Хорошо, я не пишу код в семействе языков c, так что ответ даже не выглядел так, как будто он делал то же самое, что и мой ответ, пока я просто не перечитал его сейчас. Я создал метод методом проб и ошибок и обнаружил, что 3 случайных числа были правильным числом. Кроме того, мой можно сделать в одну строку и все же быть легко понять.
Левша
2
В самом деле? Вы не думаете, есть ли сходство между JS и C? Хорошо, давайте просто скажем, что я не могу говорить ни с этими языками, ни с Java, которые для меня все похожи по сравнению с языками, с которыми я знаком.
Левша
1
Честно говоря, меня привлекло только название, потому что я решил это сам, и был очень горд тем, как я это сделал. Опять же, я не знал, что это вопрос JS, пока вы просто не сказали это. К счастью, потому что моя техника не зависит от языка, и некоторые люди думают, что это полезный ответ.
Левша
5
JavaScript на самом деле является языком семейства C ... но ну хорошо.
Джорен
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;
}
max890
источник
11

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

Лука Крайнц
источник
11

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

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

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

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

kasperd
источник
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 = количество результатов
попытки = количество раз, которое функция будет пытаться получить число в диапазоне 40-60, прежде чем она прекратит работу в цикле while

Дополнительный бонус: использует do while !!! Awesomeness в лучшем виде

ctwheels
источник
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>

wolfhammer
источник
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>

ctwheels
источник
6

Я бы порекомендовал использовать бета-дистрибутив для генерации числа от 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);
}
Olaide Oyekoya
источник
5

Лучшее решение, предназначенное именно для этой проблемы, - это решение, предложенное BlueRaja - Дэнни Пфлюгофт, но я думаю, что стоит упомянуть и более быстрое и более общее решение.


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

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

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

mg30rg
источник
1
Если вы собираетесь предварительно заполнить массив вариантов, вы можете также перетасовать их позже. Тогда вы можете просто захватить их по порядку, пока не закончится. Перемешайте снова, если / когда вы дойдете до конца списка.
Геобиц
@Geobits Перетасовка списка - намного более ресурсоемкая задача, чем случайный выбор одного из его элементов. Это хороший выбор, если список должен быть предсказуемым.
mg30rg
1
Но вы делаете это только один раз за цикл списка, а не каждый раз. Если вы предварительно обработаете это (так как у вас все равно есть шаг предварительной обработки, я предполагаю, что это нормально), то очень быстро получить каждое число после. Вы можете переставлять, когда у вас есть время простоя, или знаете, что вам не понадобится какое-то случайное число. Просто предлагая это как альтернативу, оба имеют (не) преимущества.
Geobits
@Geobits Если вы сделаете это по-своему, числа с «единой вероятностью» будут «выпадать», и в результате они не могут появиться до повторной комбинации. (т.е. если вы симулируете бросок двух кубиков, у вас не будет ни малейшего шанса получить номер 2 более чем в два раза.)
mg30rg
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;
}
Khaled.K
источник
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 = 50и sigma = 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)

Решив это для х, вы получите фактическое случайное число, которое вы ищете, которое гарантированно будет в диапазоне [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. Возьмите ith-й диапазон и сгенерируйте случайное число в этом диапазоне.

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

ST3
источник