C# 权重控制随机抽取率

增加、减少随机抽中几率——此算法可用于题库随机抽题、赌博机控制出彩率,甚至俄罗斯方块等游戏,有广泛的用途!也希望能帮得到你!

强调

     在随机的基础上增控制抽中几率,注意随机性!!

正文

      一、文字解说:

           为待随机抽取的集合每个项加一个权值,这个权值就是随机几率,比如正常被抽正的几率为1,那么将希望被抽中几率更大的项的权值设置为3或5,然后随机抽取集合中的项,并将随机数乘以每个项对应的权值,然后排序!!提取前N个项即可!大家可以发现权值更高被乘之后有更高几率排在前面而被抽中,如果将权值设为0将永远也不会被抽中!

      二、应用场景:

           1.      随机抽题:如果题A去年考过了,那么我希望今年出现的几率更小或者不出现,那么我将题A的权值设置为0,这道题将在以后的考试随机抽题中永远不会被随机抽中;而另外题B是本院今年模拟考试中的一道题目,我将这道题权值增加到5,根据算法,那么这道题目在下次随机抽题抽中率将比普通题目提高数倍!

           2.      赌博机:大家知道游戏厅里面的赌博机是可以调的,被人调了之后出彩率明显提高或者降低,我觉得本算法适合解释。假设赌博机有24个赌项可供选择,分别是A-Z各个字母,按正常几率的话每个项的权值都是1,调机师可以通过动态改变权值来达到提高或降低中奖率。假如你投三个币,分别选了A、B、C,赌博机根据调机师的设置动态改变了A、B、C的权值,让灯转3-4圈后更大的几率停留在这三个选择中奖金较少的一个。

           3.      俄罗斯方块:大家在打QQ俄罗斯方块对打的时候,有时候明显感觉堆得越高,出的东西反而不顺意,我觉得本算法也可以达到这个效果。计算机能算得出下一个最优方案是出条还是出角最好,所以可以通过调整权值来打破平均出现的几率来达到这个目的!

      三、代码实现(C#实现):

      RandomController.cs

using System;
using System.Collections.Generic;
using System.Text;

/*
为待随机抽取的集合每个项加一个权值,这个权值就是随机几率,比如正常被抽正的几率为1,
那么将希望被抽中几率更大的项的权值设置为3或5,然后随机抽取集合中的项,并将随机数
乘以每个项对应的权值,然后排序!!提取前N个项即可!大家可以发现权值更高被乘之后
有更高几率排在前面而被抽中,如果将权值设为0将永远也不会被抽中!
*/

namespace 随机抽奖
{
    public class RandomController
    {
        #region Properties
        private int _Count;
        /// <summary>
        /// 随机抽取个数
        /// </summary>
        public int Count
        {
            get{return _Count;}
            set{_Count = value;}
        }

        #endregion

        #region Member Variables

        /// <summary>
        /// 待随机抽取数据集合
        /// </summary>
        public List<char> datas = new List<char>(
            new char[]{
            'A','B','C','D','E','F',
            'G','H','I','J','K','L',
            'M','N','O','P','Q','R',
            'S','T','U','V','W','X',
            'Y','Z'
    });

        /// <summary>
        /// 对应奖项权值
        /// </summary>
        public List<ushort> weights = new List<ushort>(
            new ushort[]{
            1,2,3,4,5,6,
            7,8,9,0,1,1,
            1,1,1,1,1,1,
            1,1,1,1,1,1,
            1,1
    });

        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="count">随机抽取个数</param>
        public RandomController(ushort count)
        {
            if (count > 26)
                throw new Exception("抽取个数不能超过数据集合大小!!");

            _Count = count;
        }


        #region 普通随机抽取
        /// <summary>
        /// 随机抽取
        /// </summary>
        /// <param name="rand">随机数生成器</param>
        /// <returns></returns>
        public char[] RandomExtract(Random rand)
        {
            List<char> result = new List<char>();
            if (rand != null)
            {
                for (int i = Count; i > 0; )
                {
                    char item = datas[rand.Next(25)];
                    if (result.Contains(item))
                        continue;
                    else
                    {
                        result.Add(item);
                        i--;
                    }
                }
            }
            return result.ToArray();
        }
        #endregion

        #region 受控随机抽取
        /// <summary>
        /// 随机抽取
        /// </summary>
        /// <param name="rand">随机数生成器</param>
        /// <returns></returns>
        public char[] ControllerRandomExtract(Random rand)
        {
            List<char> result = new List<char>();
            if (rand != null)
            {
                //临时变量
                Dictionary<char, int> dict = new Dictionary<char, int>(26);

                //为每个项算一个随机数并乘以相应的权值
                for (int i = datas.Count - 1; i >= 0; i--)
                {
                    dict.Add(datas[i], rand.Next(100) * weights[i]);
                }

                //排序
                List<KeyValuePair<char, int>> listDict = SortByValue(dict);

                //拷贝抽取权值最大的前Count项
                foreach (KeyValuePair<char, int> kvp in listDict.GetRange(0, Count))
                {
                    result.Add(kvp.Key);
                }
            }
            return result.ToArray();
        }
        #endregion

        #region Tools
        /// <summary>
        /// 排序集合
        /// </summary>
        /// <param name="dict"></param>
        /// <returns></returns>
        private List<KeyValuePair<char, int>> SortByValue(Dictionary<char, int> dict)
        {
            List<KeyValuePair<char, int>> list = new List<KeyValuePair<char, int>>();

            if (dict != null)
            {
                list.AddRange(dict);

                list.Sort(
                  delegate(KeyValuePair<char, int> kvp1, KeyValuePair<char, int> kvp2)
                  {
                      return kvp2.Value - kvp1.Value;
                  });
            }
            return list;
        }
        #endregion
    }
}

----------------------------------------------------------------------------------------

      调用测试代码:

         static  void Main( string[] args)
         {
             // 从集合中随机抽取个数
             const  ushort COUNT =  6;
             // 循环次数
             const  int FOR_COUNT =  1000; // 10000

             #region 1000、10000次随机抽取,每次抽取6个

             RandomController rc =  new RandomController(COUNT);
             // 累积器
             Dictionary< charint> result =  new Dictionary< charint>();
             // 随机数生成器
             Random rand =  new Random();
             // 循环生成随机数
             for ( int i =  0; i < FOR_COUNT; i++)
             {
                 char[] rands = rc.RandomExtract(rand);
                 for ( int j =  0; j < COUNT; j++)
                 {
                     char item = rands[j];
                     if (result.ContainsKey(item))
                         result[item] +=  1;
                     else
                         result.Add(item,  1);
                 }
                 Thread.Sleep( 5);
             }

             Console.WriteLine( " \t\t出现次数\t占总共出现次数百分比 ");

             foreach (KeyValuePair< charint> item  in result)
             {
                 Console.WriteLine(item.Key +  " \t\t " + item.Value.ToString() +  " \t\t " +(( double)item.Value / ( double)(FOR_COUNT * COUNT)).ToString( " 0.00% "));
             }
         }

      普通随机抽取分别进行1000次测试显示:

  C 权重控制随机抽取率 - yandavid - 我的博客

      控制随机几率随机抽取进行1000次代码修改:

           1.      将rc.RandomExtract(rand)改为rc.ControllerRandomExtract(rand)

           2.      注释掉上面输出部分代码,加上以下代码:

            Dictionary< char, ushort> items =  new Dictionary< char, ushort>();
             for ( int i =  0,j = rc.datas.Count; i < j; i++)
             {
                 items.Add(rc.datas[i],rc.weights[i]);
             }

             Console.WriteLine( " \t\t出现次数\t占总共出现次数百分比\t权值 ");
            
             foreach (KeyValuePair< charint> item  in result)
             {
                 Console.WriteLine(item.Key +  " \t\t " + item.Value.ToString() +  " \t\t " +(( double)item.Value / ( double)(FOR_COUNT * COUNT)).ToString( " 0.00% ") +  " \t\t\t " +items[item.Key]);
             }

测试结果:  C 权重控制随机抽取率 - yandavid - 我的博客

小结

      从上面统计结果可以看出,普通随机数分布比较均匀,随机抽中的几率相对持平;但是经过控制随机抽中几率,权值高的明显抽中几率要高,另外需要注意的是这里只输出了25个字母,也就是还有一个字母没有被抽中过,因为按算法他是始终不会出现的,除非一次抽26个!!

      需要注意的是:

           1.      合理的调配权值和随机数生成的大小也很有关系,大家可以看到权值5的和权值1的出现几率相差不是5倍,而是30-50倍。

           2.      如果数据源随机的数据大,比如上千上万条,按现在的程序是不可行的,可以先随机抽取比所需抽取个数多2-5倍的数据,然后直接按权值排序然后抽取前N位来达到目的。

           3.      最重要的一点就是注意随机性,这个算法如果不是建立在随机的机制上是毫无价值的!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值