Java: случайное целое с неравномерным распределением
Как создать случайное целое число n
в Java, между 1
и k
с "линейным нисходящим распределением", т.е. 1
, скорее всего, 2
менее вероятно, 3
менее вероятно,..., k
наименее вероятно, а вероятности спускаются линейно, например:
![enter image description here]()
Я знаю, что уже есть домены потоков в этой теме, и я приношу свои извинения за создание нового, но я, похоже, не могу создать то, что мне нужно от них. Я знаю, что используя import java.util.*;
, код
Random r=new Random();
int n=r.nextInt(k)+1;
создает случайное целое число между 1
и k
, распределенное равномерно.
GENERALIZATION: Любые подсказки для создания произвольно распределенного целого числа, то есть f(n)=some function
, P(n)=f(n)/(f(1)+...+f(k))
), также будут оценены, например:
.
Ответы
Ответ 1
Это должно дать вам то, что вам нужно:
public static int getLinnearRandomNumber(int maxSize){
//Get a linearly multiplied random number
int randomMultiplier = maxSize * (maxSize + 1) / 2;
Random r=new Random();
int randomInt = r.nextInt(randomMultiplier);
//Linearly iterate through the possible values to find the correct one
int linearRandomNumber = 0;
for(int i=maxSize; randomInt >= 0; i--){
randomInt -= i;
linearRandomNumber++;
}
return linearRandomNumber;
}
Кроме того, здесь представлено общее решение для функций POSITIVE (отрицательные функции на самом деле не имеют смысла) по диапазону от начального индекса до stopIndex:
public static int getYourPositiveFunctionRandomNumber(int startIndex, int stopIndex) {
//Generate a random number whose value ranges from 0.0 to the sum of the values of yourFunction for all the possible integer return values from startIndex to stopIndex.
double randomMultiplier = 0;
for (int i = startIndex; i <= stopIndex; i++) {
randomMultiplier += yourFunction(i);//yourFunction(startIndex) + yourFunction(startIndex + 1) + .. yourFunction(stopIndex -1) + yourFunction(stopIndex)
}
Random r = new Random();
double randomDouble = r.nextDouble() * randomMultiplier;
//For each possible integer return value, subtract yourFunction value for that possible return value till you get below 0. Once you get below 0, return the current value.
int yourFunctionRandomNumber = startIndex;
randomDouble = randomDouble - yourFunction(yourFunctionRandomNumber);
while (randomDouble >= 0) {
yourFunctionRandomNumber++;
randomDouble = randomDouble - yourFunction(yourFunctionRandomNumber);
}
return yourFunctionRandomNumber;
}
Примечание. Для функций, которые могут возвращать отрицательные значения, одним из методов может быть принятие абсолютного значения этой функции и применение ее к вышеуказанному решению для каждого вызова функции.
Ответ 2
Итак, нам нужно следующее распределение, от наименее вероятного до вероятного:
*
**
***
****
*****
и др.
Давайте попробуем сопоставить равномерно распределенную целочисленную случайную переменную с этим распределением:
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15
и др.
Таким образом, если мы генерируем равномерно распределенное случайное целое число от 1 до, скажем, 15 в этом случае для K = 5
, нам просто нужно выяснить, какое именно ведро оно подходит. Трудная часть заключается в том, как это сделать.
Заметим, что числа справа представляют собой треугольные числа! Это означает, что для произвольно сгенерированного X
от 1
до T_n
нам просто нужно найти N
такое, что T_(n-1) < X <= T_n
. К счастью, существует четкая формула чтобы найти "треугольный корень" заданного числа, который мы можем использовать в качестве ядра нашего отображения из равномерное распределение в ковш:
// Assume k is given, via parameter or otherwise
int k;
// Assume also that r has already been initialized as a valid Random instance
Random r = new Random();
// First, generate a number from 1 to T_k
int triangularK = k * (k + 1) / 2;
int x = r.nextInt(triangularK) + 1;
// Next, figure out which bucket x fits into, bounded by
// triangular numbers by taking the triangular root
// We're dealing strictly with positive integers, so we can
// safely ignore the - part of the +/- in the triangular root equation
double triangularRoot = (Math.sqrt(8 * x + 1) - 1) / 2;
int bucket = (int) Math.ceil(triangularRoot);
// Buckets start at 1 as the least likely; we want k to be the least likely
int n = k - bucket + 1;
N
должен теперь иметь указанное распределение.
Ответ 3
Существует много способов сделать это, но, пожалуй, проще всего просто создать
два случайных числа, один между 0
и k
, назовите его x
, один между 0
и h
, назовите его y
. Если y > mx + b
(m
и b
выбрано соответственно...), то
k-x
, else x
.
Изменить: ответ на комментарии здесь, поэтому я могу иметь немного больше места.
В основном мое решение использует симметрию в исходном дистрибутиве, где p(x)
является линейной функцией x
. Я ответил перед вашим правлением об обобщении, и это решение не работает в общем случае (потому что в общем случае такой симметрии нет).
Я представил себе такую проблему:
- У вас есть два правильных треугольника, каждый
k x h
, с общей гипотенузой. Композитная форма представляет собой прямоугольник k x h
.
- Сгенерировать случайную точку, которая с равной вероятностью падает на каждую точку внутри прямоугольника.
- Половина времени, когда он упадет в один треугольник, половина времени в другой.
- Предположим, что точка попадает в нижний треугольник.
- Треугольник в основном описывает P.M.F., а "высота" треугольника над каждым значением x описывает вероятность того, что точка будет иметь такое значение x. (Помните, что мы имеем дело только с точками нижнего треугольника.) Таким образом, выведите значение x.
- Предположим, что точка попадает в верхний треугольник.
- Инвертируйте координаты и обработайте их, как указано выше, с нижним треугольником.
Вам также придется позаботиться о крайних случаях (я не стал беспокоиться). Например. Теперь я вижу, что ваш дистрибутив начинается с 1, а не 0, поэтому там есть один за другим, но он легко фиксируется.
Ответ 4
Позвольте мне попробовать еще один ответ, вдохновленный rlibby. Это конкретное распределение также является распределением меньшего из двух значений, выбранных равномерно и случайным из того же диапазона.
Ответ 5
Нет необходимости имитировать это с помощью массивов и т.д., если ваше распределение таково, что вы можете вычислить его кумулятивную функцию распределения (cdf). Выше есть функция распределения вероятности (pdf). h фактически определяется, так как площадь под кривой должна быть равна 1. Для простоты математики позвольте мне также предположить, что вы выбираете число в [0, k).
В этом формате f (x) = (2/k) * (1 - x/k), если я правильно вас прочитаю. Cdf является просто интегралом PDF. Здесь F (x) = (2/k) * (x - x ^ 2/2k). (Вы можете повторить эту логику для любой функции PDF, если она интегрируется.)
Затем вам нужно вычислить обратную функцию cdf, F ^ -1 (x), и если бы я не ленился, я сделал бы это для вас.
Но хорошая новость такова: если у вас есть F ^ -1 (x), все, что вы делаете, равномерно применяет его к распределению случайного значения в [0,1] и применяет к нему функцию. java.util.Random может обеспечить это с некоторой осторожностью. Это ваше случайно выбранное значение из вашего дистрибутива.
Ответ 6
Это называется треугольным распределением, хотя ваш - вырожденный случай с режимом, равным минимальному значению. У Википедии есть уравнения для того, как создать одну заданную равномерно распределенную (0,1) переменную.
Ответ 7
Первое решение, которое приходит на ум, - использовать заблокированный массив. Каждый индекс будет указывать диапазон значений в зависимости от того, насколько "вероятным" вы его хотите. В этом случае вы бы использовали более широкий диапазон для 1, менее широкий для 2 и так далее, пока не достигнете небольшого значения (скажем 1) для k.
int [] indexBound = new int[k];
int prevBound =0;
for(int i=0;i<k;i++){
indexBound[i] = prevBound+prob(i);
prevBound=indexBound[i];
}
int r = new Random().nextInt(prevBound);
for(int i=0;i<k;i++){
if(r > indexBound[i];
return i;
}
Теперь проблема состоит в том, чтобы просто найти случайное число, а затем отобразить это число в его ведро.
вы можете сделать это для любого распространения, если вы можете дискретизировать ширину каждого интервала.
Дайте мне знать, если я что-то упускаю, объясняя алгоритм или его правильность. Излишне говорить, что это нужно оптимизировать.
Ответ 8
Что-то вроде этого....
class DiscreteDistribution
{
// cumulative distribution
final private double[] cdf;
final private int k;
public DiscreteDistribution(Function<Integer, Double> pdf, int k)
{
this.k = k;
this.cdf = new double[k];
double S = 0;
for (int i = 0; i < k; ++i)
{
double p = pdf.apply(i+1);
S += p;
this.cdf[i] = S;
}
for (int i = 0; i < k; ++i)
{
this.cdf[i] /= S;
}
}
/**
* transform a cumulative distribution between 0 (inclusive) and 1 (exclusive)
* to an integer between 1 and k.
*/
public int transform(double q)
{
// exercise for the reader:
// binary search on cdf for the lowest index i where q < cdf[i]
// return this number + 1 (to get into a 1-based index.
// If q >= 1, return k.
}
}
Ответ 9
Функция кумулятивного распределения x^2
для треугольного распределения [0,1]
с режимом (наивысшая взвешенная вероятность) 1, как показано здесь.
Поэтому все, что нам нужно сделать, чтобы преобразовать равномерное распределение (например, Java Random::nextDouble
) в удобное треугольное распределение, взвешенное по отношению к 1, - это просто взять квадратный корень Math.sqrt(rand.nextDouble())
, который затем может быть умножен на любой желаемый диапазон.
В вашем примере:
int a = 1; // lower bound, inclusive
int b = k; // upper bound, exclusive
double weightedRand = Math.sqrt(rand.nextDouble()); // use triangular distribution
weightedRand = 1.0 - weightedRand; // invert the distribution (greater density at bottom)
int result = (int) Math.floor((b-a) * weightedRand);
result += a; // offset by lower bound
if(result >= b) result = a; // handle the edge case
Ответ 10
Простейшая задача сделать это для создания списка или массива всех возможных значений в их весах.
int k = /* possible values */
int[] results = new int[k*(k+1)/2];
for(int i=1,r=0;i<=k;i++)
for(int j=0;j<=k-i;j++)
results[r++] = i;
// k=4 => { 1,1,1,1,2,2,2,3,3,4 }
// to get a value with a given distribution.
int n = results[random.nextInt(results.length)];
Это лучше всего работает при относительно малых значениях k.ie. k < 1000.;)
Для больших чисел вы можете использовать подход с ковшом
int k =
int[] buckets = new int[k+1];
for(int i=1;i<k;i++)
buckets[i] = buckets[i-1] + k - i + 1;
int r = random.nextInt(buckets[buckets.length-1]);
int n = Arrays.binarySearch(buckets, r);
n = n < 0 ? -n : n + 1;
Стоимость бинарного поиска довольно мала, но не так эффективна, как прямой поиск (для небольшого массива)
Для произвольного распределения вы можете использовать double[]
для кумулятивного распределения и использовать двоичный поиск, чтобы найти значение.