C# 分布式概率随机数发生器

C# 分布式概率随机数发生器,c#,random,probability,probability-theory,C#,Random,Probability,Probability Theory,我想根据分布概率生成一个数字。例如,只需假设每个数字都出现以下情况: 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 probabili

我想根据分布概率生成一个数字。例如,只需假设每个数字都出现以下情况:

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

  • 1如果0仅执行一次:

    • 编写一个函数,计算给定pdf数组的cdf数组。在您的示例中,pdf数组为[150,40,15,3],cdf数组为[150190205208]
    每次都要这样做:

    • 在[0,1]中得到一个随机数,乘以208,向上截断(或向下截断:我让你考虑一下角的情况)在1..208中得到一个整数。将其命名为r
    • 对cdf数组中的r执行二进制搜索。返回包含r的单元格的索引

    运行时间将与给定pdf数组大小的对数成正比。这很好。但是,如果数组大小总是很小(在您的示例中为4),则执行线性搜索会更容易,也会执行得更好。

    有许多方法可以生成具有自定义分布的随机整数(也称为离散分布)。选择取决于许多因素,包括可供选择的整数数量、分布的形状以及分布是否会随时间而变化

    使用自定义权重函数选择整数的最简单方法之一
    f(x)
    是拒绝采样方法。以下假设
    f
    的最大可能值为
    max
    。拒绝采样的时间复杂度平均为常数,但在很大程度上取决于分布的形状,并且存在永远运行的最坏情况。要在[1,
    k
    中选择整数使用拒绝抽样:

  • 在[1,
    k
    ]中选择一个统一的随机整数
    i
  • 使用概率
    f(i)/max
    ,返回
    i
    。否则,转到步骤1
  • 其他算法的平均采样时间不太依赖于分布(通常为常数或对数),但通常要求您在设置步骤中预先计算权重并将其存储在数据结构中。其中一些算法在平均使用的随机位数方面也很经济。这些算法包括alias方法、Knuth–Yao算法、MVN数据结构等。有关调查,请参阅我的“”部分


    下面的C#代码实现了Michael Vose版本的alias方法,如中所述;另请参见。为了方便起见,我编写了此代码,并在此处提供

    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];
        }
    }
    

    我将此代码放在公共域中。

    感谢各位的解决方案!非常感谢

    @Menjaraz我尝试实现您的解决方案,因为它看起来非常资源友好,但是在语法方面有一些困难

    现在,我只是使用LINQ SelectMany()和Enumerable.Repeat()将我的摘要转换成一个简单的值列表

    公共类InventoryItemQuantityDomainGenerator
    {
    私有只读随机\u随机;
    私人只读可计量单位数量;
    公共InventoryItemQuantityDomainGenerator(IRepository数据库,int max)
    {
    _数量=数据库。AsQueryable()
    .其中(x=>x.数量x.数量)
    .选择(x=>new
    {
    数量=x.键,
    Count=x.Count()
    })
    .SelectMany(x=>Enumerable.Repeat(x.Quantity,x.Count));
    _随机=新随机();
    }
    公共int Next()
    {
    返回_数量.ElementAt(_random.Next(0,_数量.Count()-1));
    }
    }
    
    我知道这是一篇老文章,但我也搜索了这样一个生成器,对我找到的解决方案不满意。所以我写了自己的,并想与全世界分享

    在调用“NextItem(…)”之前,只需多次调用“Add(…)”

    ///将返回具有指定可能性的给定项之一的类。
    ///要返回的类型。
    ///如果生成器只有一个项目,它将始终返回该项目。
    ///如果有两个项目的可能性为0.4和0.6(也可以使用4和6或2和3)
    ///它将十次返回第一项4次,十次返回第二项6次。
    公共类随机数生成器
    {
    私有列表_items=新列表();
    私有随机_Random=新随机();
    /// 
    ///所有项目的可能性总和。
    /// 
    私有双精度_totalprobability=0;
    /// 
    ///添加要返回的新项目。
    /// 
    ///返回此项目的可能性。与传入的其他可能性相关。
    ///要返回的项目。
    公共无效添加(双重可能性,T项)
    {
    _添加(新元组(可能性,项));
    _总可能性+=可能性;
    }
    /// 
    ///从列表中返回具有指定相对可能性的随机项。
    /// 
    ///如果没有要返回的项目。
    公共T NextItem()
    {
    var rand=\u random.NextDouble()*\u totalprobability;
    双值=0;
    foreach(var项目在_项目中)
    {
    值+=项目1;
    
    如果(rand使用我的方法,它简单易懂。 我不计算0…1范围内的部分,我只使用“概率池”(听起来很酷,
     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
    
    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));
        }
    }
    
    /// <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
        }
    }
    
    `
    
    // 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 %)