[英]Generate Random Weighted value
編輯:我已經重寫了這個問題,希望目標更加清晰。
這是對這個問題的擴展問題在這里 ,我真的很喜歡在所提供的功能, 這個答案 。
在上面的答案中,人們能夠設定擊中極端的概率,較高的數字產生較高數字的概率,反之亦然。 問題是我必須設置3組的概率。 這些組是最低值(LV),最高值(HV)和中間值(MV)。 但是,為了簡化請求,我們可以考慮EVP=HVP=LVP
。
給定任何范圍,HV / LV應基於指定的EVP出現,並且當您從每個極端的范圍內進展/下降時,該范圍中下一個值的概率將根據EVP之間的距離增加或減少。和MVP。
使用1-6的示例范圍,1和6加權為5%(EVP),概率差為1/6為5%,2/4為15%,3/4為30%(MVP) ),總計100%。 反過來也應該是可能的,交換EVP和MVP應該產生下圖的反轉。
這是一個我希望將傳達給定示例預期結果的圖像。
中加權:
額外獎勵:如果我能夠單獨設置HVP和LVP產生類似於下圖的結果,那將是最優秀的( 注意:圖表不符合上述規范 )。
中等加權(獎金):
謝謝!
因為我今天因為流感而被困在家里:(我決定嘗試為你解決這個問題。基本上你要求的是某種插值。我使用最簡單的(線性的)這些是我的結果和代碼。代碼有點混亂,我可能會在即將到來的日子里修復它。
<?php
// this function interpolates $a to $b over $steps steps, starting from key $k
// this can be cleaned up significantly
function interpolate($a, $b, $steps, $k) {
@$per_step = abs($a - $b)/$steps; // suppress warnings in case of division by zero
if ($a > $b)
$decreasing = true;
else
$decreasing = false;
$final = array();
for ($i = 1; $i <= $steps-1; ++$i) {
if ($decreasing)
$final[$i+$k] = $a-=$per_step; // linear interpolation
else
$final[$i+$k] = $a+=$per_step; // linear interpolation
}
return $final;
}
// this function combines probability arrays after the interpolation occurs
// this may happen multiple times, think about 1, 3, 5. interpolation would have to occur
// from 1 -> 2 -> 3, and from 3 -> 4 -> 5.
function interpolateProbabilities ($nodes) {
$pNodes = array();
$pNodes = $nodes;
$keys = array_keys($nodes);
for ($i = 0; $i < count($keys); $i++) {
if ($keys[$i+1] - $keys[$i] != 1) {
$pNodes += interpolate($nodes[$keys[$i]], $nodes[$keys[$i+1]], $keys[$i+1] - $keys[$i], $keys[$i]);
}
}
ksort($pNodes);
return $pNodes;
}
// this generates a weighed random value and is pretty much copy-pasted from:
// http://w-shadow.com/blog/2008/12/10/fast-weighted-random-choice-in-php/
// it's robust and re-writing it would be somewhat pointless
function generateWeighedRandomValue($nodes) {
$weights = array_values($nodes);
$values = array_keys($nodes);
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(0, array_sum($weights));
while($i < $count) {
$n += $weights[$i];
if($n >= $num) {
break;
}
$i++;
}
return $values[$i];
}
// two test cases
$nodes = array( 1 => 12, 5 => 22, 9 => 31, 10 => 35); // test 1
$nodes = array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10); // test 2
$export = array();
// run it 1000 times
for ($i = 0; $i < 1000; ++$i) {
$export[generateWeighedRandomValue(interpolateProbabilities($nodes))]++;
}
// for copy-pasting into excel to test out distribution
print_r($export);
?>
我認為,結果正是您正在尋找的。 如果是:
$nodes = array( 1 => 12, 5 => 22, 9 => 31, 10 => 35); // test 1
我得到了以下(最終)數組:
Array
(
[5] => 92
[7] => 94
[10] => 162
[8] => 140
[3] => 71
[6] => 114
[2] => 75
[4] => 69
[9] => 131
[1] => 52
)
也就是說, 1
應該發生在12%的時間, 5
22%, 9
31%和10
35%的時間。 讓它圖表:
它看起來很有希望,但讓我們嘗試更瘋狂的東西......
$nodes = array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10); // test 2
在這種情況下, 3
應該在50%的時間發生,並且急劇下降到6
。 讓我們看看發生了什么! 這是數組(回想起來,我應該對這些數組進行排序):
Array
(
[4] => 163
[7] => 64
[2] => 180
[10] => 47
[1] => 115
[5] => 81
[3] => 227
[8] => 57
[6] => 6
[9] => 60
)
讓我們看看圖片:
看起來很有效:)
我希望我能夠解決你的問題(或者至少指出你正確的方向)。 請注意,我的代碼目前有許多規定。 也就是說,您提供的初始節點必須具有高達100%的概率,否則您可能會獲得一些不穩定的行為。
此外,代碼有點亂,但概念相對簡單。 其他一些很酷的東西是嘗試而不是使用線性插值,使用其他類型,這將給你更有趣的結果!
為了避免混淆,我將只展示算法的工作原理。 我給PHP一個$node
數組,其形式為integer => frequency in percentage
,最終看起來像array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10)
,這是上面的test 2
。
Test 2
基本上表示你想要在1, 3, 6, 7, and 10
個放置5個控制節點1, 3, 6, 7, and 10
頻率分別為22%, 50%, 2%, 16%, and 10%
。 首先,我需要看正是我需要做插值。 例如,我不需要在6
到7
之間進行,但我確實需要在1
到3
之間(我們需要插值2
)和7
和10
(我們需要插入8
和9
)。
1 -> 3
之間的插值有(3 - 1) - 1 = 1
步,應插入原始數組中的key[2]
。 的值( %
為) 1 -> 3
插是abs($a - $b) / $steps
其轉換為的絕對值%
的1
減去%
的2
,除以steps + 1
,其在我們的情況恰好等於14
。 我們需要看看函數是增加還是減少(你好微積分)。 如果函數增加,我們繼續將步長%
添加到新的插值數組,直到我們填充所有空點(如果函數正在減少,我們減去步長% value
。因為我們只需要填充一個點,我們返回2 => 36
( 22 + 14 = 36
)。
我們組合了數組,結果是(1 => 22, 2 => 36, 3 => 50, 6 => 2, 7 => 16, 10 => 10)
。 程序內插2
,這是我們沒有明確聲明的百分比值。
在7 -> 10
的情況下,有2個步驟,步長百分比是2
,它來自(16-10) / (3 + 1) = 2
。 函數正在減少,所以我們需要重復減去2
。 最終的插值陣列是(8 => 14, 9 => 12)
。 我們結合了所有陣列和瞧。
下圖顯示綠色(初始值)和紅色(插值)。 您可能需要“查看圖像”才能清楚地看到整個事物。 你會注意到我使用±
因為算法需要弄清楚我們是否應該在一段時間內增加或減少。
這段代碼應該用更多的OOP范例編寫。 我使用數組鍵玩很多(例如,我需要傳遞$k
因此一旦我從interpolate($a, $b, $steps, $k)
返回數組,它就更容易組合數組interpolate($a, $b, $steps, $k)
因為它們會自動擁有正確的鍵。這只是一個PHP的特性,回想起來,我應該開始使用更易讀的OOP方法。
這是我的最后一次編輯,我保證:)由於我喜歡使用Excel,這顯示了一旦插入數字后百分比如何標准化。 這一點很重要,特別是考慮到你的第一張照片,你所展示的內容在某種程度上是不可能的。
Test 1
Test 2
您會注意到百分比顯着衰減以適應插值。 你現實中的第二張圖看起來更像是這樣的:
在此圖中,我稱1 = > 1, 5 => 98, 10 => 1
,您可以看到阻尼效應的極值。 畢竟,根據定義,百分比必須加起來為100! 重要的是要意識到阻尼效應與極端之間的步數成正比。
假設您可以處理百分比的整數,只需將0到99之間的每個值分配一個結果 - 例如0-9可能有1的結果而95-99可能有6的結果(給你的10%= 1和5%= 6情景)。 一旦你有了這個翻譯功能(但是你實現了這個 - 你可以使用各種方法),你只需要生成0-99范圍內的隨機數並將其轉換為結果。
你想要的代碼(甚至是哪種語言--C#或PHP?)你的問題並不是很清楚,但希望這會有所幫助。
這里有一些C#代碼可以讓你在合理的范圍內得到任何你喜歡的偏見 - 你不必將它表示為百分比,但你可以這樣做:
static int BiasedRandom(Random rng, params int[] chances)
{
int sum = chances.Sum();
int roll = rng.Next(sum);
for (int i = 0; i < chances.Length - 1; i++)
{
if (roll < chances[i])
{
return i;
}
roll -= chances[i];
}
return chances.Length - 1;
}
例如,您可以使用
int roll = BiasedRandom(rng, 10, 10, 10, 10, 10, 50) + 1;
這樣每1-5分鍾的幾率為10%,獲得6分的幾率為50%。
C#中快速而骯臟的方式:
T PickWeightedRandom<T>(IEnumerable<Tuple<T,double>> items, Random r)
{
var sum = 0.0;
var rand = r.NextDouble();
return items.First(x => { sum += x.Item2; return rand < sum; }).Item1;
}
測試代碼:
var values = new [] {
Tuple.Create(1, 0.05),
Tuple.Create(2, 0.15),
Tuple.Create(3, 0.3),
Tuple.Create(4, 0.3),
Tuple.Create(5, 0.15),
Tuple.Create(6, 0.05),
};
const int iterations = 1000;
var counts = new int[values.Length];
var random = new Random();
for (int i = 0; i < iterations; i++)
{
counts[PickWeightedRandom(values, random)-1]++;
}
foreach (var item in counts)
{
Console.WriteLine(item/(double)iterations);
}
輸出(迭代次數= 1000000):
0.050224
0.150137
0.300592
0.298879
0.150441
0.049727
好像:
生成非均勻隨機數時的一般技術是使用拒絕采樣 。 即使在這種情況下它可能無效,你仍然應該知道如何做到這一點,因為它適用於你提供的任何密度函數。
function random($density, $max) {
do {
$rand = lcg_value();
$rand2 = lcg_value() * $max;
} while ($density($rand) < $rand2);
return $rand;
}
$density
是一個密度函數,它接受0到1之間的浮點數作為參數,並返回一個小於$max
。 對於您的示例,此密度函數可以是:
$density = function($x) {
static $values = array(
1 => 0.05,
2 => 0.15,
3 => 0.30,
4 => 0.30,
5 => 0.15,
6 => 0.05,
);
return $values[ceil($x * 6)];
};
然后一個示例調用是:
ceil(random($density, 0.3) * 6); // 0.3 is the greatest value returned by $density
// round and * 6 are used to map a 0 - 1 float to a 1 - 6 int.
如果您無法輕松計算分布的倒數,則拒絕采樣尤其有用。 在這種情況下,使用逆變換采樣計算逆變得非常容易,這可能是更好的選擇。 但Jon的答案已經涵蓋了這一點。
PS:上面的實現是通用的,因此使用0到1之間的隨機值。通過構建一個僅適用於您的方法的函數,一切都變得更容易:
function random() {
static $values = array(
1 => 0.05,
2 => 0.15,
3 => 0.30,
4 => 0.30,
5 => 0.15,
6 => 0.05,
);
do {
$rand = mt_rand(1, 6);
$rand2 = lcg_value() * 0.3;
} while ($values[$rand] < $rand2);
return $rand;
}
random();
首先,您需要表征當前的隨機數生成器。 在PHP的情況下,rand()函數返回一個漂亮的平面輪廓 - 因此不需要預處理。
重新映射輸出分布函數,使其下面的區域為1,范圍從0開始。然后計算其積分。 存儲積分(例如,作為值的數組)。 然后,當您需要隨機數matchnig時,首先從內置生成器獲取0到1之間的隨機數,然后在積分上找到Y坐標,其中X坐標是您生成的值。 最后,將值縮放到所需范圍(例如,如果查找介於0和10之間的值,則乘以10,如果查找介於-8和+8之間的值,則多次乘以16並減去8)。
如果您的隨機數生成器不生成平面輪廓,那么最簡單的方法是使用上述方法的反向將其轉換為平面輪廓。
我沒試過,但我認為這可能會奏效:
$random($probability)
{
$rnd = rand() / getrandmax();
foreach($probability as $num => $prob)
{
$rnd -= $prob;
if($rnd <=0)
return $num;
}
return -1; //this should never happen
}
並像這樣調用它(使用你的第二個例子):
$distribution = array(
1 => 0.10,
2 => 0.15,
3 => 0.30,
4 => 0.27,
5 => 0.14,
6 => 0.04);
$number = random($distribution);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.