[英]Distributed probability random number generator
我想根據分布概率生成一個數字。 例如,假設每個數字出現以下情況:
Number| Count
1 | 150
2 | 40
3 | 15
4 | 3
with a total of (150+40+15+3) = 208
then the probability of a 1 is 150/208= 0.72
and the probability of a 2 is 40/208 = 0.192
如何制作基於此概率分布返回數字的隨機數生成器?
我很高興目前基於靜態的硬編碼集,但我最終希望它從數據庫查詢中導出概率分布。
我見過像類似的例子這一個,但他們都不是很普通的。 有什么建議?
一般方法是將均勻分布的隨機數從 0..1 區間輸入到所需分布的累積分布函數的倒數中。
因此,在您的情況下,只需從 0..1 中抽取一個隨機數 x (例如使用Random.NextDouble()
)並基於其值返回
只做一次:
每次都這樣做:
運行時間將與給定 pdf 數組大小的 log 成正比。 哪個好。 但是,如果您的數組大小總是很小(在您的示例中為 4),那么執行線性搜索會更容易並且性能也會更好。
有很多方法可以生成具有自定義分布(也稱為離散分布)的隨機整數。 選擇取決於許多因素,包括可供選擇的整數數量、分布的形狀以及分布是否會隨時間變化。
使用自定義權重函數f(x)
選擇整數的最簡單方法之一是拒絕采樣方法。 下面假設f
的max
可能值是max
。 拒絕采樣的時間復雜度平均是恆定的,但在很大程度上取決於分布的形狀,最壞的情況是永遠運行。 要使用拒絕采樣在 [1, k
] 中選擇一個整數:
k
] 中選擇一個統一的隨機整數i
。f(i)/max
,返回i
。 否則,轉到步驟 1。其他算法的平均采樣時間不太依賴於分布(通常是常數或對數),但通常需要您在設置步驟中預先計算權重並將它們存儲在數據結構中。 其中一些在平均使用的隨機位數量方面也是經濟的。 這些算法包括別名方法、 Fast Loaded Dice Roller 、Knuth-Yao 算法、MVN 數據結構等。 有關調查,請參閱我的“ 帶替代品的加權選擇”部分。
以下 C# 代碼實現了 Michael Vose 版本的別名方法,如本文所述; 另見這個問題。 為了您的方便,我編寫了此代碼並在此處提供。
public class LoadedDie {
// Initializes a new loaded die. Probs
// is an array of numbers indicating the relative
// probability of each choice relative to all the
// others. For example, if probs is [3,4,2], then
// the chances are 3/9, 4/9, and 2/9, since the probabilities
// add up to 9.
public LoadedDie(int probs){
this.prob=new List<long>();
this.alias=new List<int>();
this.total=0;
this.n=probs;
this.even=true;
}
Random random=new Random();
List<long> prob;
List<int> alias;
long total;
int n;
bool even;
public LoadedDie(IEnumerable<int> probs){
// Raise an error if nil
if(probs==null)throw new ArgumentNullException("probs");
this.prob=new List<long>();
this.alias=new List<int>();
this.total=0;
this.even=false;
var small=new List<int>();
var large=new List<int>();
var tmpprobs=new List<long>();
foreach(var p in probs){
tmpprobs.Add(p);
}
this.n=tmpprobs.Count;
// Get the max and min choice and calculate total
long mx=-1, mn=-1;
foreach(var p in tmpprobs){
if(p<0)throw new ArgumentException("probs contains a negative probability.");
mx=(mx<0 || p>mx) ? P : mx;
mn=(mn<0 || p<mn) ? P : mn;
this.total+=p;
}
// We use a shortcut if all probabilities are equal
if(mx==mn){
this.even=true;
return;
}
// Clone the probabilities and scale them by
// the number of probabilities
for(var i=0;i<tmpprobs.Count;i++){
tmpprobs[i]*=this.n;
this.alias.Add(0);
this.prob.Add(0);
}
// Use Michael Vose's alias method
for(var i=0;i<tmpprobs.Count;i++){
if(tmpprobs[i]<this.total)
small.Add(i); // Smaller than probability sum
else
large.Add(i); // Probability sum or greater
}
// Calculate probabilities and aliases
while(small.Count>0 && large.Count>0){
var l=small[small.Count-1];small.RemoveAt(small.Count-1);
var g=large[large.Count-1];large.RemoveAt(large.Count-1);
this.prob[l]=tmpprobs[l];
this.alias[l]=g;
var newprob=(tmpprobs[g]+tmpprobs[l])-this.total;
tmpprobs[g]=newprob;
if(newprob<this.total)
small.Add(g);
else
large.Add(g);
}
foreach(var g in large)
this.prob[g]=this.total;
foreach(var l in small)
this.prob[l]=this.total;
}
// Returns the number of choices.
public int Count {
get {
return this.n;
}
}
// Chooses a choice at random, ranging from 0 to the number of choices
// minus 1.
public int NextValue(){
var i=random.Next(this.n);
return (this.even || random.Next((int)this.total)<this.prob[i]) ? I : this.alias[i];
}
}
例子:
var loadedDie=new LoadedDie(new int[]{150,40,15,3}); // list of probabilities for each number:
// 0 is 150, 1 is 40, and so on
int number=loadedDie.nextValue(); // return a number from 0-3 according to given probabilities;
// the number can be an index to another array, if needed
我將此代碼放在公共領域。
我知道這是一篇舊帖子,但我也搜索過這樣的生成器,但對我找到的解決方案並不滿意。 所以我寫了我自己的,想分享給全世界。
只需在調用“NextItem(...)”之前多次調用“Add(...)”
/// <summary> A class that will return one of the given items with a specified possibility. </summary>
/// <typeparam name="T"> The type to return. </typeparam>
/// <example> If the generator has only one item, it will always return that item.
/// If there are two items with possibilities of 0.4 and 0.6 (you could also use 4 and 6 or 2 and 3)
/// it will return the first item 4 times out of ten, the second item 6 times out of ten. </example>
public class RandomNumberGenerator<T>
{
private List<Tuple<double, T>> _items = new List<Tuple<double, T>>();
private Random _random = new Random();
/// <summary>
/// All items possibilities sum.
/// </summary>
private double _totalPossibility = 0;
/// <summary>
/// Adds a new item to return.
/// </summary>
/// <param name="possibility"> The possibility to return this item. Is relative to the other possibilites passed in. </param>
/// <param name="item"> The item to return. </param>
public void Add(double possibility, T item)
{
_items.Add(new Tuple<double, T>(possibility, item));
_totalPossibility += possibility;
}
/// <summary>
/// Returns a random item from the list with the specified relative possibility.
/// </summary>
/// <exception cref="InvalidOperationException"> If there are no items to return from. </exception>
public T NextItem()
{
var rand = _random.NextDouble() * _totalPossibility;
double value = 0;
foreach (var item in _items)
{
value += item.Item1;
if (rand <= value)
return item.Item2;
}
return _items.Last().Item2; // Should never happen
}
}
用我的方法。 它簡單易懂。 我不計算 0...1 范圍內的部分,我只使用“Probabilityp Pool”(聽起來很酷,是嗎?)
`
// Some c`lass or struct for represent items you want to roulette
public class Item
{
public string name; // not only string, any type of data
public int chance; // chance of getting this Item
}
public class ProportionalWheelSelection
{
public static Random rnd = new Random();
// Static method for using from anywhere. You can make its overload for accepting not only List, but arrays also:
// public static Item SelectItem (Item[] items)...
public static Item SelectItem(List<Item> items)
{
// Calculate the summa of all portions.
int poolSize = 0;
for (int i = 0; i < items.Count; i++)
{
poolSize += items[i].chance;
}
// Get a random integer from 0 to PoolSize.
int randomNumber = rnd.Next(0, poolSize) + 1;
// Detect the item, which corresponds to current random number.
int accumulatedProbability = 0;
for (int i = 0; i < items.Count; i++)
{
accumulatedProbability += items[i].chance;
if (randomNumber <= accumulatedProbability)
return items[i];
}
return null; // this code will never come while you use this programm right :)
}
}
// Example of using somewhere in your program:
static void Main(string[] args)
{
List<Item> items = new List<Item>();
items.Add(new Item() { name = "Anna", chance = 100});
items.Add(new Item() { name = "Alex", chance = 125});
items.Add(new Item() { name = "Dog", chance = 50});
items.Add(new Item() { name = "Cat", chance = 35});
Item newItem = ProportionalWheelSelection.SelectItem(items);
}
這是使用逆分布函數的實現:
using System;
using System.Linq;
// ...
private static readonly Random RandomGenerator = new Random();
private int GetDistributedRandomNumber()
{
double totalCount = 208;
var number1Prob = 150 / totalCount;
var number2Prob = (150 + 40) / totalCount;
var number3Prob = (150 + 40 + 15) / totalCount;
var randomNumber = RandomGenerator.NextDouble();
int selectedNumber;
if (randomNumber < number1Prob)
{
selectedNumber = 1;
}
else if (randomNumber >= number1Prob && randomNumber < number2Prob)
{
selectedNumber = 2;
}
else if (randomNumber >= number2Prob && randomNumber < number3Prob)
{
selectedNumber = 3;
}
else
{
selectedNumber = 4;
}
return selectedNumber;
}
驗證隨機分布的示例:
int totalNumber1Count = 0;
int totalNumber2Count = 0;
int totalNumber3Count = 0;
int totalNumber4Count = 0;
int testTotalCount = 100;
foreach (var unused in Enumerable.Range(1, testTotalCount))
{
int selectedNumber = GetDistributedRandomNumber();
Console.WriteLine($"selected number is {selectedNumber}");
if (selectedNumber == 1)
{
totalNumber1Count += 1;
}
if (selectedNumber == 2)
{
totalNumber2Count += 1;
}
if (selectedNumber == 3)
{
totalNumber3Count += 1;
}
if (selectedNumber == 4)
{
totalNumber4Count += 1;
}
}
Console.WriteLine("");
Console.WriteLine($"number 1 -> total selected count is {totalNumber1Count} ({100 * (totalNumber1Count / (double) testTotalCount):0.0} %) ");
Console.WriteLine($"number 2 -> total selected count is {totalNumber2Count} ({100 * (totalNumber2Count / (double) testTotalCount):0.0} %) ");
Console.WriteLine($"number 3 -> total selected count is {totalNumber3Count} ({100 * (totalNumber3Count / (double) testTotalCount):0.0} %) ");
Console.WriteLine($"number 4 -> total selected count is {totalNumber4Count} ({100 * (totalNumber4Count / (double) testTotalCount):0.0} %) ");
示例輸出:
selected number is 1 selected number is 1 selected number is 1 selected number is 1 selected number is 2 selected number is 1 ... selected number is 2 selected number is 3 selected number is 1 selected number is 1 selected number is 1 selected number is 1 selected number is 1 number 1 -> total selected count is 71 (71.0 %) number 2 -> total selected count is 20 (20.0 %) number 3 -> total selected count is 8 (8.0 %) number 4 -> total selected count is 1 (1.0 %)
感謝您的所有解決方案! 非常感激!
@Menjaraz 我嘗試實施您的解決方案,因為它看起來對資源非常友好,但是在語法上有一些困難。
所以現在,我只是使用 LINQ SelectMany() 和 Enumerable.Repeat() 將我的摘要轉換為一個平面的值列表。
public class InventoryItemQuantityRandomGenerator
{
private readonly Random _random;
private readonly IQueryable<int> _quantities;
public InventoryItemQuantityRandomGenerator(IRepository database, int max)
{
_quantities = database.AsQueryable<ReceiptItem>()
.Where(x => x.Quantity <= max)
.GroupBy(x => x.Quantity)
.Select(x => new
{
Quantity = x.Key,
Count = x.Count()
})
.SelectMany(x => Enumerable.Repeat(x.Quantity, x.Count));
_random = new Random();
}
public int Next()
{
return _quantities.ElementAt(_random.Next(0, _quantities.Count() - 1));
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.