概率计算(抽奖活动、命中率)

最近在做移动端的营销活动,其中包含刮刮卡、大转盘等小游戏,对于用户来说他们不关心Code只关心我是否中奖了,之前也在群里看到有人要概率的“算法”或者说是计算工具类。

ps:这里不得不提一下,每当自己在做什么东西的时候总会在一些地方发现相似的需求或者文章,来源有很多比如:cnblogs、QQ群等各种渠道,这不刚刚还有人发表随机数相关的文章,哈哈可能是我运气好。


营销活动核心——我是不是中奖了呢?

是不是中奖都有一个概率和巧合性那么肯定少不了随机数了,说到随机数各位Coder们肯定想到了Random这个类,是的这一次的概率计算的实现也是基于随机数的。

下面我们来看概率服务接口:

IProbabilityService    /// <summary>
/// 一个抽象的概率服务。
/// </summary>
public interface IProbabilityService
{
/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">概率百分比(如概率为30%请传入30)。</param>
/// <param name="getRandomNumber">获取随机数的委托(一般为Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
bool IsHit(double probabilityPercentage, Func<double> getRandomNumber);

/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">概率百分比(如概率为30%请传入30)。</param>
/// <param name="hasHitCount">命中次数。</param>
/// <param name="noHitCount">没有命中的次数。</param>
/// <param name="getRandomNumber">获取随机数的委托(一般为Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
bool IsHit(double probabilityPercentage, int hasHitCount, int noHitCount, Func<double> getRandomNumber);
}

接口十分的简单一起有两个方法,第一个方法比较纯粹的计算概率,而第二个则添加了一些修正概率所需的数据。

为什么需要“getRandomNumber”参数,而不直接在内部使用Random?


这边就需要引入“随机数是骗人的,.Net、Java、C为我作证”今天的热乎文章了,内部使用Random有很多的不确定性,而且不易于扩展所以这边提供了一个委托提供随机数,而概率服务本身只专注于计算,如果非要高大上点就引入设计原则——单一职责

服务实现

ProbabilityServiceinternal sealed class ProbabilityService : IProbabilityService
{
#region Implementation of IProbabilityService

/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">概率百分比(如概率为30%请传入30)。</param>
/// <param name="getRandomNumber">获取随机数的委托(一般为Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
public bool IsHit(double probabilityPercentage, Func<double> getRandomNumber)
{
//如果概率大等于100则每次都命中。
if (probabilityPercentage >= 100)
return true;

//得到概率的百分比。
probabilityPercentage = probabilityPercentage / 100;

return InternalIsHit(probabilityPercentage, getRandomNumber);
}

/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">概率百分比(如概率为30%请传入30)。</param>
/// <param name="hasHitCount">命中次数。</param>
/// <param name="noHitCount">没有命中的次数。</param>
/// <param name="getRandomNumber">获取随机数的委托(一般为Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
public bool IsHit(double probabilityPercentage, int hasHitCount, int noHitCount, Func<double> getRandomNumber)
{
//如果概率大等于100则每次都命中。
if (probabilityPercentage >= 100)
return true;

//得到概率的百分比。
probabilityPercentage = probabilityPercentage / 100;

//得到总计算次数。
var totalCount = (double)(hasHitCount + noHitCount);

//得到当前命中的概率。
var currentProbability = hasHitCount / totalCount;

//如果当前命中的概率大于传入的概率则不会命中。
if (currentProbability > probabilityPercentage)
return false;

return InternalIsHit(probabilityPercentage, getRandomNumber);
}

#endregion Implementation of IProbabilityService

#region Private Method

/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">不需要处理的百分比(介于 0.0 和 1.0 之间的数)。</param>
/// <param name="getRandomNumber">获取随机数的委托(一般为Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
private static bool InternalIsHit(double probabilityPercentage, Func<double> getRandomNumber)
{
//得到一个随机数。
var randomNumber = getRandomNumber();

if (randomNumber < 0 || randomNumber >= 1)
throw new ArgumentException("随机数必须是一个介于 0.0 和 1.0 之间的数。");

//取后15位
const int places = 15;

//精简小数位,提升概率准确性。
randomNumber = GetNumber(randomNumber, places);

return probabilityPercentage > randomNumber;
}

/// <summary>
/// 精简数字的小数位。
/// </summary>
/// <param name="number">数字。</param>
/// <param name="places">小数位。</param>
/// <returns>精简小数位后的数字。</returns>
private static double GetNumber(double number, int places)
{
//精简小数位,提升概率准确性。
return Math.Round(number, places);

//该方法会提高准确性但会影响性能,适用于高精度场景。
/*var str = number.ToString(CultureInfo.InvariantCulture);
if (!str.Contains("."))
return number;
var t = str.Split(‘.‘);
number = double.Parse(t[0] + "." + string.Join("", t[1].Take(places)));
return number;*/
}

#endregion Private Method
}

代码有较详尽的注释这边不再说明了。

有运行Demo吗?


当然,这是我的一贯作风

运行结果

第一行为接口的第一个方法(纯粹的概率计算),第二个行为接口的第二个方法(带简单修正)。

Code

Programinternal class Program
{
private static void Main()
{
var random = new Random();

//每一次执行的测试次数(当前为10w次)。
const int totalCount = 100000;

//概率百分比。
double probability;

#region GetProbability By Console "probability"

var promptMessage = "请输入概率百分比,如30%:";
string probabilityString;
do
{
Console.WriteLine(promptMessage);
promptMessage = "请输入一个正确的概率百分比:";
probabilityString = Console.ReadLine();
if (probabilityString != null && probabilityString.EndsWith("%"))
probabilityString = probabilityString.TrimEnd(‘%‘);
} while (!double.TryParse(probabilityString, out probability));

#endregion GetProbability By Console "probability"

Console.WriteLine("测试次数设定为:{0},概率设定为:{1}%", totalCount, GetPercentage(probability, 100));
Console.WriteLine("==================================================");

IProbabilityService probabilityService = new ProbabilityService();

while (true)
{
RunTest(totalCount, (i, hitCount) => probabilityService.IsHit(probability, random.NextDouble));
RunTest(totalCount, (i, hitCount) => probabilityService.IsHit(probability, hitCount, i - hitCount, random.NextDouble));
Console.ReadLine();
}
}

/// <summary>
/// 执行测试。
/// </summary>
/// <param name="totalCount">测试次数。</param>
/// <param name="hit">是否命中委托。</param>
private static void RunTest(int totalCount, Func<int, int, bool> hit)
{
//总命中次数。
var hitCount = 0;

for (var i = 0; i < totalCount; i++)
{
var isHit = hit(i, hitCount);
if (isHit)
hitCount++;
}

//概率百分比。
var percentage = GetPercentage(hitCount, totalCount);
Console.WriteLine("总次数:{0},命中次数:{1},概率{2}%", totalCount, hitCount, percentage);
}

/// <summary>
/// 获取百分比。
/// </summary>
/// <param name="number1">数字1。</param>
/// <param name="number2">数字2。</param>
/// <returns>百分比。</returns>
private static double GetPercentage(double number1, double number2)
{
return (number1 / number2) * 100;
}
}

Demo下载:http://pan.baidu.com/s/1gdmnH31

写在最后


已经写了一些“散文”做为锻炼,之后准备写一个系列挑战一下,不过最近在做项目,等手上的模块做完之后,开始着手针对 Orchard
Framework

写一个刨析系列,当然中间可能会穿插一些小文章,喜欢Orchard的朋友们可以留个言留个脚印。

时间: 2024-10-12 15:05:02

概率计算(抽奖活动、命中率)的相关文章

javascript抽奖插件+概率计算

写了一个抽奖的jquery插件和计算概率的方法, 结合起来就是一个简单的概率抽奖, 不过实际项目中基本不会把抽奖概率的计算放在前端处理~. 概率计算 function Probability(conf) { this.probArr = conf || []; this.range = [], this.len = this.probArr.length; if (this.len > 0) { this.init(); } } Probability.prototype = { init: f

条件随机场入门(三) 条件随机场的概率计算问题

条件随机场的概率计算问题是给定条件随机场 P(Y|X) ,输入序列 x 和输出序列 y ,计算条件概率 $P(y_i|x)$ , $P(y_{i-1},y_i|x)$ 以及相应的数学期望的问题.为了方便起见,像 HMM 那样,引进前向-后向向量,递归地计算以上概率及期望值.这样的算法称为前向-后向算法. 前向-后向算法 对每个指标 $i = 0,1,-,n+1$ ,定义前向向量 $a_i(x)$ ,对于起始状态 $i=0$: \[a_0(y|x) = \left \{ \begin{aligne

程序员眼中的统计学(3)】概率计算:把握机会

概率计算:把握机会 作者 白宁超 2015年10月13日23:23:13 摘要:程序员眼中的统计学系列是作者和团队共同学习笔记的整理.首先提到统计学,很多人认为是经济学或者数学的专利,与计算机并没有交集.诚然在传统学科中,其在以上学科发挥作用很大.然而随着科学技术的发展和机器智能的普及,统计学在机器智能中的作用越来越重要.本系列统计学的学习基于<深入浅出统计学>一书(偏向代码实现,需要读者有一定基础,可以参见后面PPT学习).正如(吴军)先生在<数学之美>一书中阐述的,基于统计和数

HMM的概率计算问题和预测问题的java实现

HMM(hidden markov model)可以用于模式识别,李开复老师就是采用了HMM完成了语音识别. 一下的例子来自于<统计学习方法> 一个HMM由初始概率分布,状态转移概率分布,观测概率分布确定.并且基于两个假设: 1 假设任意时刻t的状态只依赖于前一个时刻的状态,与其他时刻的状态和观测序列无关 2 假设任意时刻的观测只依赖与该市可的马尔科夫的状态,与其他观测,状态无关. 基于此,HMM有三个基本问题: 1 概率计算问题,给定模型和观测序列,计算在模型下的观测序列出现的概率 2 预测

概率计算函数

//概率计算函数 public function get_rand($proArr) { $result = ''; //概率数组的总概率精度 $proSum = array_sum($proArr); //概率数组循环 foreach ($proArr as $key => $proCur) { $randNum = mt_rand(1, $proSum); if ($randNum <= $proCur) { $result = $key; break; } else { $proSum

算法笔记_155:算法提高 概率计算(Java)

目录 1 问题描述 2 解决方案   1 问题描述 问题描述 生成n个∈[a,b]的随机整数,输出它们的和为x的概率. 输入格式 一行输入四个整数依次为n,a,b,x,用空格分隔. 输出格式 输出一行包含一个小数位和为x的概率,小数点后保留四位小数 样例输入 2 1 3 4 样例输出 0.3333 数据规模和约定 对于50%的数据,n≤5. 对于100%的数据,n≤100,b≤100. 2 解决方案 下面代码在系统中运行评分为90分,第五组数据无法通过,我用同版本的C代码运行(PS:具体参见文末

蓝桥杯 - 概率计算 (概率DP)

题目传送:蓝桥杯 - 概率计算 概率计算 时间限制:1.0s   内存限制:256.0MB 锦囊1 锦囊2 锦囊3 问题描述 生成n个∈[a,b]的随机整数,输出它们的和为x的概率. 输入格式 一行输入四个整数依次为n,a,b,x,用空格分隔. 输出格式 输出一行包含一个小数位和为x的概率,小数点后保留四位小数 样例输入 2 1 3 4 样例输出 0.3333 数据规模和约定 对于50%的数据,n≤5. 对于100%的数据,n≤100,b≤100. 思路:概率DP,好久没做DP题了,居然1A了,

JAVA实现概率计算

程序中经常遇到随机送红包之类的情景,这个随机还得指定概率,比如10%的机率可以得到红包.那么java怎么实现一个简单的概率计算了,见如下例子: int randomInt = RandomUtils.nextInt(1,101); if(randomInt <= 10){ //100里面1个数,小于等于10的概率就是10% //do something } RandomUtils工具类是commons-lang3包里面的 <dependency> <groupId>org.a

隐马尔可夫模型HMM(二)概率计算问题

摘自 1.李航的<统计学习方法> 2.http://www.cnblogs.com/pinard/p/6955871.html 一.概率计算问题 上一篇介绍了概率计算问题是给定了λ(A,B,π),计算一个观测序列O出现的概率,即求P(O|λ). 用三种方法,直接计算法,前向算法,后向算法. 考虑隐马尔可夫模型(一)中的盒子球模型. 假设Q={1,2,3,4}, V = {红,白},在给定λ(A,B,π)的条件下,其中: ,  , 求O=(红,白,红)的概率. 二.直接计算法 说通俗一点,就是暴