抽獎隨機算法的技術探討與C#實現


一、模擬客戶需求

1.1 客戶A需求:要求每次都按照下圖的概率隨機,數量不限,每個用戶只能抽一次,抽獎結果的分布與抽獎概率近似。

1.2 客戶B需求:固定獎項10個,抽獎次數不限,每個用戶只能抽一次,抽完為止,抽獎結果必須是固定的那幾個獎項。

二、需求分析

算法1:對於客戶A,由於抽獎次數無限次,出獎分布遵守中獎概率設定值。所以必須在用戶每一次的抽獎行為之前都按照中獎概率隨機出就可以了,這樣隨着樣本的增多,概率分布越來越趨近於設定的抽獎概率。

算法2:對於客戶B,由於獎項固定,需要將獎項硬編碼(或者存儲到DB),獎項抽出來一個就將該獎項從列表中刪除。獎項就抽完后就直接返回未中獎。

三、算法實現

3.1先定義個類,用於存儲獎項和概率的關系:

//算法1.每次都完全隨機的算法
//作者:deepleo
//博客:http://www.deepleo.com/
//郵箱:yemor@qq.com
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RandomAlgorithm
{
    /// <summary>
    /// 獎品
    /// </summary>
    public class Prize
    {
        /// <summary>
        /// 名稱
        /// </summary>
        public string Name { set; get; }
        /// <summary>
        /// 中獎概率
        /// </summary>
        public decimal Probability { set; get; }
    }
}

3.2 實現算法1

//算法1.每次都完全隨機的算法
//作者:deepleo
//博客:http://www.deepleo.com/
//郵箱:yemor@qq.com
using System;
using System.Collections.Generic;

namespace RandomAlgorithm
{
    public class EveryRandomAlgo
    {
        public List<Prize> Prizes
        {
            private set;
            get;
        }
        public EveryRandomAlgo(List<Prize> prizes)
        {
            this.Prizes = prizes;
        }


        /// <summary>
        /// 隨機下一次獎品
        /// </summary>
        /// <returns></returns>
        public string Next()
        {
            long tick = DateTime.Now.Ticks;
            Random ran = new Random((int)(tick & 0xffffffffL) | (int)(tick >> 32));
            var rnum = ran.Next(0, 9999);//將0~100映射到0~10000,提高精度到小數點后2位;生成隨機數rnum;
            //Console.WriteLine(rnum);
            var randomProbability = (decimal)rnum / 100;//再換算為0~100范圍
            var target = 0;//命中index
            var end = Prizes[0].Probability;
            for (int k = -1; k < Prizes.Count - 1; k++)
            {
                var min = k < 0 ? 0 : Prizes[k].Probability;//最小中獎概率
                if (randomProbability >= min && randomProbability <= end)//隨機出來的中獎概率位於中獎概率區間內
                {
                    target = k + 1;
                    break;
                }
                end += Prizes[k + 1].Probability;//最大中獎概率累計值
            }
            return Prizes[target].Name;
        }
    }
}

 3.3 實現算法2

//算法2.從數據表中隨機取出一條記錄的偽隨機算法
//作者:deepleo
//博客:http://www.deepleo.com/
//郵箱:yemor@qq.com

using System.Collections.Generic;

namespace RandomAlgorithm
{
    public class DefineByDBAlgo
    {
        public List<Prize> Prizes
        {
            private set;
            get;
        }
        private Queue<string> _dbRecords;//存儲在數據表中的獎項記錄數據
        public DefineByDBAlgo(List<Prize> prizes)
        {
            this.Prizes = prizes;
            _dbRecords = new Queue<string>();
            //按照中獎概率初始化獎項數據
            _dbRecords.Enqueue(Prizes[0].Name);
            _dbRecords.Enqueue(Prizes[0].Name);
            _dbRecords.Enqueue(Prizes[0].Name);
            _dbRecords.Enqueue(Prizes[0].Name);
            _dbRecords.Enqueue(Prizes[0].Name);
            _dbRecords.Enqueue(Prizes[1].Name);
            _dbRecords.Enqueue(Prizes[1].Name);
            _dbRecords.Enqueue(Prizes[1].Name);
            _dbRecords.Enqueue(Prizes[1].Name);
            _dbRecords.Enqueue(Prizes[2].Name);
         
        }

        /// <summary>
        /// 隨機下一次獎品
        /// </summary>
        /// <returns></returns>
        public string Next()
        {
            if (_dbRecords != null && _dbRecords.Count > 0)
            {
                lock (_dbRecords)
                {
                    var random = _dbRecords.Dequeue();//這里直接取出最上面的那一條記錄。保證出獎順序與數據庫記錄一致,如果要隨機,只需要更改數據庫記錄順序或者這里使用隨機Index
                    return random;
                }
            }
            return "";
        }
    }
}

 

3.4 調用代碼

//摘要:抽獎隨機算法
//作者:deepleo
//博客:http://www.deepleo.com/
//郵箱:yemor@qq.com

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RandomAlgorithm
{
    class Program
    {
        static void Main(string[] args)
        {

            var prizes = new List<Prize>();
            prizes.Add(new Prize { Name = "三等獎50%", Probability = 50 });
            prizes.Add(new Prize { Name = "二等獎40%", Probability = 40 });
            prizes.Add(new Prize { Name = "一等獎10%", Probability = 10 });
            var algo1 = new EveryRandomAlgo(prizes);
            var result1 = new Dictionary<string, int>();
            var count = 5000;
            for (int i = 0; i < count; i++)
            {
                var next = algo1.Next();
               // Console.WriteLine("【{0}】算法1隨機結果:{1}", i + 1, string.IsNullOrEmpty(next) ? "獎項已發完" : next);
                if (!result1.Keys.Any(x => x == next))
                {
                    result1.Add(next, 1);
                }
                else
                {
                    result1[next]++;
                }  
                Thread.Sleep(1);
            }
            foreach (var key in result1.Keys)
            {
                Console.WriteLine("算法1隨最終結果:{0}:{1}個", key, result1[key].ToString());
            }
            Console.WriteLine("=====================================");
            var algo2 = new DefineByDBAlgo(prizes);
            var result2 = new Dictionary<string, int>();
            for (int i = 0; i < count; i++)
            {
                var next = algo2.Next();
                //Console.WriteLine("【{0}】算法2隨機結果:{1}", i + 1, string.IsNullOrEmpty(next) ? "獎項已發完" : next);
                if (!result2.Keys.Any(x => x == next))
                {
                    result2.Add(next, 1);
                }
                else
                {
                    result2[next]++;
                }
            }
            foreach (var key in result2.Keys)
            {
                Console.WriteLine("算法2隨最終結果:{0}:{1}個", key, result2[key].ToString());
            }
            Console.ReadKey();
        }

    }

}

 

四、實驗結果

1. 抽獎次數為10次的實驗結果:

算法1,二等獎和三等獎的個數分別為:3,7。與設定的中獎概率偏差較大。

算法2,符合預期(由於將所有的獎項都放在最上面,所以獎項都抽出來了)。

2.抽獎次數為100次的實驗結果:

算法1,二等獎和三等獎的個數分別為:57,43。與設定的中獎概率偏差較小,已經比較接近設定值。

算法2,符合預期,后面的99個由於獎項已經被抽完,所以都是未中獎的。

3.抽獎次數為1000次的實驗結果:

算法1,二等獎和三等獎的個數分別為:497,503。與設定的中獎概率偏差非常接近了。

算法2,符合預期,后面的990個由於獎項已經被抽完,所以都是未中獎的。

五、總結

本文探討了兩種比較簡單的隨機算法:無限次隨機算法與固定獎項隨機算法,並用C#分別實現了這兩種算法,給出了算法1和算法2的測試結果。結果表明:隨着抽獎次數的增加,算法1的抽獎結果越來越接近設定的中獎概率分布;算法2與設定一致。

實際上這兩個算法都比較簡單的算法,唯一需要注意的的是:在構造Random對象時如果seed是一樣的就很容易產生隨機出來的結果是一樣的。

所以代碼中Thread.Sleep(1);以保證隨機的結果分布均勻,但是這樣又限制了算法1的出獎速度,不知道各位有沒有更好的解決方案。

源代碼下載:博客園下載地址

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM