2016-07-16 212 views
5

我想知道是否有一個算法來生成隨機數字,最有可能將在從最小到最大範圍內低。例如,如果您生成1到100之間的隨機數,那麼如果您使用f(min: 1, max: 100, avg: 30)調用函數,則大多數時間應該在30以下,但如果您使用f(min: 1, max: 200, avg: 10)調用該函數,那麼平均值應該最高爲10.很多遊戲會這樣做,但我根本找不到用公式來做到這一點的方法。我見過的大多數例子都使用了「drop table」或類似的東西。爲RPG遊戲生成隨機數

我想出了一個相當簡單的方法來體重卷的結果,但它是不是很有效,你沒有大量的控制了它

var pseudoRand = function(min, max, n) { 
    if (n > 0) { 
     return pseudoRand(min, Math.random() * (max - min) + min, n - 1) 
    } 

    return max; 
} 

rands = [] 
for (var i = 0; i < 20000; i++) { 
    rands.push(pseudoRand(0, 100, 1)) 
} 

avg = rands.reduce(function(x, y) { return x + y })/rands.length 
console.log(avg); // ~50 

這個程序只是選秀權在最小和最大N次之間的隨機數,其中它對於每次迭代在最後一次滾動時更新最大值。所以,如果你有N = 2, and max = 100調用它,那麼它必須推出連續100分兩次,以返回100

我已經看過了維基百科上一些發行,但我不太明白他們足夠知道我怎麼能控制最小和最大輸出等

任何幫助是非常歡迎

+0

期望的中位數是多少? – Dinesh

回答

3

的概率分佈函數就是這樣,當你把一個值X,將返回獲得該概率函數值X.累積分佈函數是獲得小於或等於X的數字的概率.CDF是PDF的積分。 CDF幾乎總是一對一的功能,所以它幾乎總是反過來。

要生成PDF,繪製在x軸上的值和在y軸的概率。所有概率的總和(離散)或積分(連續)應該加起來爲1.找到一些能正確模擬該方程的函數。要做到這一點,你可能需要查找一些PDF文件。

基本算法

https://en.wikipedia.org/wiki/Inverse_transform_sampling

該算法基於關閉逆變換採樣。 ITS背後的想法是,你在CDF的y軸上隨機選取一個值,並找到它所對應的x值。這是有道理的,因爲隨機選擇一個值的可能性越大,它將在CDF的y軸上佔用的「空間」就越多。

  1. 拿出一定的概率分佈公式。例如,如果你想這樣做,那麼隨着數字越高,他們被選中的機率就越大,你可以使用類似f(x)= x或f(x)= x^2的東西。如果你想要在中間凸起的東西,你可以使用高斯分佈或1 /(1 + x^2)。如果你想要一個有界的公式,你可以使用Beta分佈或Kumaraswamy分佈。
  2. 整合PDF得到累積分佈函數。
  3. 查找CDF的逆。
  4. 生成一個隨機數,並將其插入到CDF的倒數。
  5. 將結果乘以(max-min),然後加上min
  6. 將結果四捨五入爲最接近的整數。

步驟1到3件事情你必須硬編碼到遊戲中。對於任何PDF來說,唯一的解決辦法就是解決與其平均值相對應的形狀參數,並根據您想要的形狀參數的約束來解決這些問題。如果您想使用Kumaraswamy分佈,您將設置它以使形狀參數a和b總是大於1。

我會建議使用Kumaraswamy分佈,因爲它是有界的,它有一個非常好的封閉形式和封閉形式的逆。它只有兩個參數a和b,並且它非常靈活,因爲它可以模擬許多不同的場景,包括多項式行爲,鐘形曲線行爲以及在兩邊都有峯值的盆狀行爲。另外,使用此功能建模並不難。形狀參數b越高,它向左傾斜得越多,形狀參數a越高,傾斜度就越大。如果a和b都小於1,則分佈將看起來像一個低谷或盆地。如果a或b等於1,則分佈將是一個多項式,它不會將凹度從0更改爲1.如果a和b都等於1,則分佈是一條直線。如果a和b大於1,那麼函數看起來像鐘形曲線。你可以做的最好的事情是學習這些功能,或者只是運行逆變換抽樣算法。

https://en.wikipedia.org/wiki/Kumaraswamy_distribution

例如,如果我想有形狀像這樣與概率分佈的= 2和b = 5從0到100去:

https://www.wolframalpha.com/input/?i=2*5*x%5E(2-1)*(1-x%5E2)%5E(5-1)+from+x%3D0+to+x%3D1

及其CDF將是:

CDF(x)= 1-(1-X^2)^ 5

及其逆將是:

CDF^-1(X)=(1-(1-X)^(1/5))^(1/2)

的Kumaraswamy的分配的一般逆是: CDF^-1 (x)=(1-(1-x)^(1/b))^(1/a)

然後我會產生一個從0到1的數字,把它放入CDF^-1 ),以及由100

優點

  • 非常精確
  • 0123相乘的結果
  • 連續,不慎重
  • 使用一個公式和空間非常小
  • 給你很多控制權的隨機性究竟是如何展開
  • 許多公式有某種
  • 的逆的CDF有兩種方法可以在兩端綁定函數。例如,Kumaraswamy分佈的範圍是從0到1,所以你只需輸入一個介於0和1之間的浮點數,然後將結果乘以(max-min)並加上min。 Beta分佈根據傳遞給它的值而有所不同。對於類似於PDF(x)= x的東西,CDF(x)=(x^2)/ 2,因此您可以從CDF(0)生成隨機值到CDF(max-min)。

缺點

  • 你需要拿出你打算使用
  • 你打算使用需要每一個通式的確切分佈,它們的形狀被硬編碼到遊戲。換句話說,您可以將一般的Kumaraswamy分佈編程到遊戲中,並且可以根據分佈及其參數a和b生成隨機數的函數,但不能根據平均值爲您生成分佈函數。如果你想使用Distribution x,你必須找出a和b的值最適合你想要看到的數據,並將這些值硬編碼到遊戲中。
+0

這正是我一直在尋找的。謝謝 – chrs

0

你可以保持你從功能在while循環到目前爲止返回,並基於該移動平均值獲得一個隨機數滿足平均水平,調整運行平均值並返回數字

8

簡單的wa y生成一個具有給定分佈的隨機數是從列表中選擇一個隨機數,根據所需的分佈重複應該更經常發生的數字。例如,如果你創建一個列表[1,1,1,2,2,2,3,3,3,4],並挑選從09隨機指標選擇從列表中的元素,你會得到一個編號<4 90%的概率

可替代地,使用上述從實施例的分佈,生成陣列[2,5,8,9]並挑選從0一個隨機整數9,如果它是≤2然後返回1,如果是(這將與30%的概率發生)>2≤5(這也將有30%的概率)返回2

這裏解釋發生:https://softwareengineering.stackexchange.com/a/150618

+0

如果你想讓結果4僅爲每100萬次,這將導致非常大的數組。要麼? – chrs

+0

@chrs是的,這不是一個實用的方法,它只適用於小案例 – user2314737

0

使用DROP TABLE允許一個非常快的滾動,在實時遊戲的問題。實際上,它只是一個範圍內隨機生成的一個數字,然後根據概率表(該範圍的高斯分佈),一個if語句具有多項選擇。類似的東西:

num = random.randint(1,100) 
if num<10 : 
    case 1 
if num<20 and num>10 : 
    case 2 
... 

這不是很乾淨,但是當你有有限數量的選擇它可以非常快。

0

有很多方法可以這樣做,所有這些基本上歸結爲從skewed(也稱爲正偏態)分佈產生。您沒有說清楚您是想要整數還是浮點結果,但是有離散和連續分佈都適合賬單。

其中一個最簡單的選擇是離散或連續的權利,但儘管這會讓您逐漸減少對更大值的渴望,但它不會給您獨立控制均值。

另一種選擇是截斷指數(對於連續)或幾何(對於離散)分佈。您需要截斷,因爲原始指數或幾何分佈的範圍從零到無窮大,所以您必須切掉較高的尾部。這反過來會要求你做一些微積分來找到速率&lambda;這在截斷後產生期望的平均值。

第三種選擇是使用分佈的混合,例如在較低的範圍內以一定的概率P也同樣選擇的數,並在上範圍的概率(1-P)。整體平均值是p乘以較低範圍的平均值+(1-p)乘以平均值的上限,您可以通過調整範圍和p的值來撥打所需的整體平均值。如果您對子範圍使用非均勻分佈選擇,則此方法也可用。這一切歸結爲你願意投入多少工作來推導適當的參數選擇。

0

一種方法不是最精確的方法,但根據您的需要可以被視爲「足夠好」。

該算法將選擇一個最小值和最大值之間的數字。將有一個保證最大g_max和一個潛在的最大p_max。根據另一個隨機電話的結果,您的真實最大值將滑動。這會給你一個你正在尋找的傾斜分佈。以下是Python中的解決方案。

import random 

def get_roll(min, g_max, p_max) 

    max = g_max + (random.random() * (p_max - g_max)) 

    return random.randint(min, int(max)) 

get_roll(1, 10, 20) 

下面是使用(1,10,20)函數運行100,000次的直方圖。

enter image description here

1

您可以合併2個隨機過程。例如:

first rand R1 = f(min:1,max:20,avg:10); second rand R2 = f(min:1,max:10,avg:1);

然後乘以R1 * R2爲具有[1-200]之間的結果和平均約10(平均將被移位的位)

另一個選項是找到想要的隨機函數的逆使用。這個選項必須在程序啓動時初始化,但不需要重新計算。這裏使用的數學可以在很多數學庫中找到。我將逐點解釋一個未知隨機函數的例子,其中只有四個點是已知的:

  1. 首先,擬合具有3階或更高階多項式函數的四點曲線。
  2. 你應該然後有類型的參數化函數:斧+ BX^2 + CX^3 + d。
  3. 找到函數的不定積分(積分形式爲a/2x^2 + b/3x^3 + c/4x^4 + dx,我們將其稱爲quarticEq)。
  4. 計算多項式從最小值到最大值的積分。
  5. 取0-1之間的均勻隨機數,然後乘以步驟5中計算的積分值。(我們將結果命名爲「R」)
  6. 現在求解方程對於x,R = quarticEq

希望最後一部分是衆所周知的,你應該能夠找到一個可以做這種計算的庫(見wiki)。如果積分函數的逆函數沒有封閉形式解(例如任何具有五次或更高次數的普通多項式),則可以使用根搜索方法,例如Newton's Method

這種計算可能會使用到創建任何類型的隨機分佈的。

編輯:

您可能會發現逆變換中wikipedia上述採樣,我發現這個implementation(我還沒有嘗試過。)

0
private int roll(int minRoll, int avgRoll, int maxRoll) { 
    // Generating random number #1 
    int firstRoll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1); 

    // Iterating 3 times will result in the roll being relatively close to 
    // the average roll. 
    if (firstRoll > avgRoll) { 
     // If the first roll is higher than the (set) average roll: 
     for (int i = 0; i < 3; i++) { 
      int verificationRoll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1); 

      if (firstRoll > verificationRoll && verificationRoll >= avgRoll) { 
       // If the following condition is met: 
       // The iteration-roll is closer to 30 than the first roll 
       firstRoll = verificationRoll; 
      } 
     } 
    } else if (firstRoll < avgRoll) { 
     // If the first roll is lower than the (set) average roll: 
     for (int i = 0; i < 3; i++) { 
      int verificationRoll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1); 

      if (firstRoll < verificationRoll && verificationRoll <= avgRoll) { 
       // If the following condition is met: 
       // The iteration-roll is closer to 30 than the first roll 
       firstRoll = verificationRoll; 
      } 
     } 
    } 
    return firstRoll; 
} 

說明:

  • 檢查,如果軋輥的正上方,BEL流或正好30
  • 如果以上,重擲3次&根據新的軋輥設置的輥,如果低,但> = 30
  • 如果下面,重擲3次&根據新的軋輥設置的輥,如果 較高,但< = 30
  • 如果正好是30,不設置輥重新
  • 回捲

優點:

  • 簡單
  • 有效
  • 執行以及

缺點:

  • 你自然會有更多的結果是在30-40比你的範圍內」由於30-70之間的關係,將會在20-30的範圍內變得簡單。

測試:

您可以通過與roll() - 方法結合使用下面的方法進行測試。數據保存在散列圖中(將數字映射到出現次數)。

public void rollTheD100() { 

    int maxNr = 100; 
    int minNr = 1; 
    int avgNr = 30; 

    Map<Integer, Integer> numberOccurenceMap = new HashMap<>(); 

    // "Initialization" of the map (please don't hit me for calling it initialization) 
    for (int i = 1; i <= 100; i++) { 
     numberOccurenceMap.put(i, 0); 
    } 

    // Rolling (100k times) 
    for (int i = 0; i < 100000; i++) { 
     int dummy = roll(minNr, avgNr, maxNr); 
     numberOccurenceMap.put(dummy, numberOccurenceMap.get(dummy) + 1); 
    } 

    int numberPack = 0; 

    for (int i = 1; i <= 100; i++) { 
     numberPack = numberPack + numberOccurenceMap.get(i); 
     if (i % 10 == 0) { 
      System.out.println("<" + i + ": " + numberPack); 
      numberPack = 0; 
     } 
    } 
} 

結果(100.000輥):

這些是如預期。請注意,您可以隨時微調的結果,只需在roll() - 方法(越接近30平均應該是,更多的反覆測試應包括修改迭代數(注意,這可以在性能傷害到一定度))。還要注意的是,到目前爲止,30人(如預期的)出現次數最多。

  • < 10:4994
  • < 20:9425
  • < 30:18184
  • < 40:29640
  • < 50:18283
  • < 60:10426
  • < 70 :5396
  • < 80:2532
  • < 90:897
  • < 100:223
0

模擬軟件如SLX用於創建僞數據的算法是所謂的線性同餘發生器。 Wikipedialink here

那裏的等式和消除非常好。你將不得不將這個方程寫入一個方法並設置一個相應的返回值。

0

試試這個, 爲低於平均值的數字範圍生成一個隨機數,併爲高於平均值的數字範圍生成第二個隨機數。

然後隨機選擇其中一個,每個範圍將被選擇50%的時間。

var psuedoRand = function(min, max, avg) { 
    var upperRand = (int)(Math.random() * (max - avg) + avg); 
    var lowerRand = (int)(Math.random() * (avg - min) + min); 

    if (math.random() < 0.5) 
    return lowerRand; 
    else 
    return upperRand; 
} 
+0

注意:在if語句中增加十進制數字「0.5」會增加所選數字的頻率,這會低於「平均」,反之亦然。 – Luke

1

我會爲此使用一個簡單的數學函數。從你所描述的,你需要像y = x^2這樣的指數級數。平均值(平均值爲x = 0.5,因爲rand從0到1得到一個數),你會得到0.25。如果希望更低的平均數目,則可以使用更高的指數像Y = X^3將導致Y = 0.125在什麼X = 0.5 實施例: http://www.meta-calculator.com/online/?panel-102-graph&data-bounds-xMin=-2&data-bounds-xMax=2&data-bounds-yMin=-2&data-bounds-yMax=2&data-equations-0=%22y%3Dx%5E2%22&data-rand=undefined&data-hideGrid=false

PS:我調整,以計算所需要的功能指數來得到平均結果。 代碼示例:

function expRand (min, max, exponent) { 
    return Math.round(Math.pow(Math.random(), exponent) * (max - min) + min); 
} 

function averageRand (min, max, average) { 
    var exponent = Math.log(((average - min)/(max - min)))/Math.log(0.5); 
    return expRand(min, max, exponent); 
} 

alert(averageRand(1, 100, 10)); 
0

我們已經看到很多很好的解釋和一些好的想法,我仍然認爲這可以幫助你:

你可以採取任何的分佈函數˚F周圍0,並替換您的間隔興趣到你想要的時間間隔[1,100]f - >f'

然後喂C++discrete_distribution結果爲f'

我有與下面的正態分佈的例子,但我無法將因此進入這個功能:-S

#include <iostream> 
#include <random> 
#include <chrono> 
#include <cmath> 


using namespace std; 


double p1(double x, double mean, double sigma); // p(x|x_avg,sigma) 
double p2(int x, int x_min, int x_max, double x_avg, double z_min, double z_max); // transform ("stretch") it to the interval 
int plot_ps(int x_avg, int x_min, int x_max, double sigma); 

int main() 
{ 
    int x_min = 1; 
    int x_max = 20; 
    int x_avg = 6; 

    double sigma = 5; 

    /* 
    int p[]={2,1,3,1,2,5,1,1,1,1}; 

    default_random_engine generator (chrono::system_clock::now().time_since_epoch().count()); 
    discrete_distribution<int> distribution {p*}; 

    for (int i=0; i< 10; i++) 
     cout << i << "\t" << distribution(generator) << endl; 
    */ 
    plot_ps(x_avg, x_min, x_max, sigma); 

    return 0; //*/ 
} 

// Normal distribution function 
double p1(double x, double mean, double sigma) 
{ 
    return 1/(sigma*sqrt(2*M_PI)) 
     * exp(-(x-mean)*(x-mean)/(2*sigma*sigma)); 
} 

// Transforms intervals to your wishes ;) 
// z_min and z_max are the desired values f'(x_min) and f'(x_max) 
double p2(int x, int x_min, int x_max, double x_avg, double z_min, double z_max) 
{ 
    double y; 
    double sigma = 1.0; 
    double y_min = -sigma*sqrt(-2*log(z_min)); 
    double y_max = sigma*sqrt(-2*log(z_max)); 
    if(x < x_avg) 
     y = -(x-x_avg)/(x_avg-x_min)*y_min; 
    else 
     y = -(x-x_avg)/(x_avg-x_max)*y_max; 
    return p1(y, 0.0, sigma); 
} 

//plots both distribution functions 
int plot_ps(int x_avg, int x_min, int x_max, double sigma) 
{ 
    double z = (1.0+x_max-x_min); 

    // plot p1 
    for (int i=1; i<=20; i++) 
    { 
     cout << i << "\t" << 
     string(int(p1(i, x_avg, sigma)*(sigma*sqrt(2*M_PI)*20.0)+0.5), '*') 
     << endl; 
    } 

    cout << endl; 

    // plot p2 
    for (int i=1; i<=20; i++) 
    { 
     cout << i << "\t" << 
     string(int(p2(i, x_min, x_max, x_avg, 1.0/z, 1.0/z)*(20.0*sqrt(2*M_PI))+0.5), '*') 
     << endl; 
    } 
} 

結果如下,如果我讓他們陰謀:

1 ************ 
2 *************** 
3 ***************** 
4 ****************** 
5 ******************** 
6 ******************** 
7 ******************** 
8 ****************** 
9 ***************** 
10 *************** 
11 ************ 
12 ********** 
13 ******** 
14 ****** 
15 **** 
16 *** 
17 ** 
18 * 
19 * 
20 

1 * 
2 *** 
3 ******* 
4 ************ 
5 ****************** 
6 ******************** 
7 ******************** 
8 ******************* 
9 ***************** 
10 **************** 
11 ************** 
12 ************ 
13 ********* 
14 ******** 
15 ****** 
16 **** 
17 *** 
18 ** 
19 ** 
20 * 

所以 - 如果你可以把這個結果給discrete_distribution<int> distribution {},你得到了你想要的一切......

0

好,從我可以看到你的問題,我想對於解決符合這些標準:

a)屬於單一分佈:如果我們需要每次函數調用不止一次「滾動」(調用math.Random),然後聚合或丟棄一些結果,它將停止根據給定的功能。 b)不是計算密集型的:一些解決方案使用積分,(伽瑪分佈,高斯分佈),這些計算密集型。在你的描述中,你提到你希望能夠「用公式計算它」,它符合這個描述(基本上,你想要一個O(1)函數)。 c)相對「均勻分佈」,例如,沒有高峯和低谷,而是大多數結果集中在平均值附近,並且朝向兩端具有良好的可預測的斜率,並且還有最小值和最大值不爲零的概率。

d)不需要在內存中存儲大型數組,如下拉表。

我覺得這個功能是否符合要求:通過調用

(int) Math.round(pseudoRand(...))

var pseudoRand = function(min, max, avg) 
    { 
     var randomFraction = Math.random(); 
     var head = (avg - min); 
     var tail = (max - avg); 
     var skewdness = tail/(head + tail); 
     if (randomFraction < skewdness) 
      return min + (randomFraction/skewdness) * head; 
     else 
      return avg + (1 - randomFraction)/(1 - skewdness) * tail; 
    } 

這將返回浮動,但你可以輕鬆地將它們變成整數它在所有返回正確的平均我的測試,而且它也很好地分配到目的。希望這可以幫助。祝你好運。