抽奖随机算法的技术探讨与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的出奖速度,不知道各位有没有更好的解决方案。

源代码下载:博客园下载地址

 

posted @ 2016-12-27 14:15 夜の魔王 阅读(...) 评论(...) 编辑 收藏