Distributed probability random number generator

前端 未结 7 1025
挽巷
挽巷 2020-12-05 10:47

I want to generate a number based on a distributed probability. For example, just say there are the following occurences of each numbers:

Number| Count               


        
相关标签:
7条回答
  • 2020-12-05 11:21

    There are many ways to generate a random integer with a custom distribution (also known as a discrete distribution). The choice depends on many things, including the number of integers to choose from, the shape of the distribution, and whether the distribution will change over time.

    One of the simplest ways to choose an integer with a custom weight function f(x) is the rejection sampling method. The following assumes that the highest possible value of f is max. The time complexity for rejection sampling is constant on average, but depends greatly on the shape of the distribution and has a worst case of running forever. To choose an integer in [1, k] using rejection sampling:

    1. Choose a uniform random integer i in [1, k].
    2. With probability f(i)/max, return i. Otherwise, go to step 1.

    Other algorithms have an average sampling time that doesn't depend so greatly on the distribution (usually either constant or logarithmic), but often require you to precalculate the weights in a setup step and store them in a data structure. Some of them are also economical in terms of the number of random bits they use on average. These algorithms include the alias method, the Fast Loaded Dice Roller, the Knuth–Yao algorithm, the MVN data structure, and more. See my section "A Note on Weighted Choice Algorithms" for a survey.


    The following C# code implements Michael Vose's version of the alias method, as described in this article; see also this question. I have written this code for your convenience and provide it here.

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

    Example:

     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
    

    I place this code in the public domain.

    0 讨论(0)
提交回复
热议问题