2012-03-31 84 views
15

Tôi muốn tạo một số dựa trên xác suất được phân phối. Ví dụ: chỉ cần nói có các trường hợp sau đây của mỗi số:Trình tạo số ngẫu nhiên phân phối ngẫu nhiên

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  

Làm cách nào để tạo một trình tạo số ngẫu nhiên trả về số dựa trên phân phối xác suất này?

Tôi rất vui vì điều này được dựa trên bộ mã hóa tĩnh, hiện tại nhưng cuối cùng tôi muốn nó lấy được phân phối xác suất từ ​​truy vấn cơ sở dữ liệu.

Tôi đã xem các ví dụ tương tự như this one nhưng chúng không phải là rất chung chung. Bất kỳ đề xuất?

Trả lời

27

Cách tiếp cận chung là cung cấp các số ngẫu nhiên được phân phối thống nhất từ ​​khoảng 0..1 vào the inverse of the cumulative distribution function của phân phối bạn muốn.

Như vậy trong trường hợp của bạn, chỉ cần vẽ một số ngẫu nhiên x từ 0..1 (ví dụ với Random.NextDouble()) và dựa trên lợi nhuận giá trị của nó

  • 1 nếu 0 < = x < 150/208,
  • 2 nếu 150/208 < = x < 190/208,
  • 3 nếu 190/208 < = x < 205/208 và
  • 4 khác.
+0

tuyệt vời! Giải pháp nạc tốt đẹp :) Cảm ơn. –

+0

Tôi đang bối rối như những gì mà tuyên bố IF sẽ như thế nào. Bạn có thể vui lòng hiển thị những gì sẽ trông giống như trong mã (C#, JS, vv)? – smatthews1999

2

Làm điều này một lần duy nhất:

  • Viết một hàm để tính toán một mảng lũy ​​cho một mảng pdf. Trong mảng pdf ví dụ của bạn là [150,40,15,3], mảng cdf sẽ là [150,190,205,208].

Làm điều này mỗi khi:

  • Nhận một số ngẫu nhiên trong [0,1), nhân với 208, cắt xén lên (hoặc xuống: Tôi rời khỏi nó để bạn suy nghĩ về các trường hợp góc) Bạn sẽ có một số nguyên trong 1,208. Đặt tên nó là r.
  • Thực hiện tìm kiếm nhị phân nhị phân trên mảng cdf cho r. Trả về chỉ mục của ô chứa r.

Thời gian chạy sẽ tỷ lệ thuận với nhật ký kích thước của mảng pdf đã cho. Cái nào tốt. Tuy nhiên, nếu kích thước mảng của bạn sẽ luôn nhỏ (ví dụ 4) thì thực hiện tìm kiếm tuyến tính dễ dàng hơn và cũng sẽ hoạt động tốt hơn.

+0

Nếu phân phối thực sự có một số lượng lớn các giá trị, hashtable sẽ hiệu quả hơn nhiều so với tìm kiếm nhị phân. –

+0

@Zalcman Có, điều đó là có thể. Tuy nhiên kích thước của hashtable bằng tổng của các mục trong mảng pdf, có thể tùy ý lớn hơn kích thước của mảng pdf. Xem xét trường hợp khi mảng pdf có hàng triệu mục và mục trung bình là 100. Phụ thuộc vào hoàn cảnh, nhưng tôi có thể thích tìm kiếm nhị phân hơn (khoảng 20 lần so sánh mỗi lần tra cứu) để có được 100 triệu mục nhập. –

4

This question giải thích các cách tiếp cận khác nhau để tạo số ngẫu nhiên với các xác suất khác nhau. Theo this article, được hiển thị trên câu hỏi đó, cách tiếp cận tốt nhất như vậy (về độ phức tạp thời gian) là cái gọi là "phương pháp bí danh" của Michael Vose.

Để tiện cho bạn, tôi đã viết và cung cấp ở đây một C# thi hành một bộ tạo số ngẫu nhiên thực hiện các phương pháp bí danh:

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

Ví dụ:

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 

Tôi đặt mã này ở nơi công cộng miền.

+0

Cảm ơn bạn đã đăng bài này. Đó là một phần quan trọng của một dự án tôi đã làm việc và tôi đánh giá cao việc bạn đặt nó vào miền công cộng. – Carl

+0

Hoạt động hoàn hảo. Và tinh chỉnh mã một chút cho phép bạn tạo một lớp Random seed. –

0

Cảm ơn tất cả các bạn giải pháp! Nhiều đánh giá cao!

@Menjaraz Tôi đã thử triển khai giải pháp của bạn vì nó trông rất thân thiện với tài nguyên, tuy nhiên có một số khó khăn với cú pháp.

Vì vậy, hiện tại, tôi vừa chuyển bản tóm tắt của mình thành danh sách giá trị bằng LINQ SelectMany() và 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)); 
    } 
} 
0

Tôi biết đây là một bài đăng cũ, nhưng tôi cũng đã tìm kiếm một trình tạo như vậy và không hài lòng với các giải pháp tôi đã tìm thấy. Vì vậy, tôi đã viết của riêng tôi và muốn chia sẻ nó với thế giới.

Chỉ cần gọi "Add (...)" một số lần trước khi bạn gọi là "NextItem (...)"

/// <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

Sử dụng phương pháp của tôi. Nó đơn giản và dễ hiểu. Tôi không đếm phần trong phạm vi 0 ... 1, tôi chỉ cần sử dụng "Probabilityp Pool" (âm thanh mát mẻ, yeah?)

At circle diagram you can see weight of every element in pool

Here you can see an implementing of accumulative probability for roulette

` 

// 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); 
     } 
Các vấn đề liên quan