既然在前2篇文章中我们已经实现了RandN小于等于49的随机数,上一篇文章(http://blog.csdn.net/xzjxylophone/article/details/6853727)留下的问题:可否用任意Rand7实现任意一个数的随机数
这篇文章就来讨论该如何实现。
注意到我们计算RandN的时候用到34=7*4 + 6,那么计算其他的数,如101,可以想到101=2 * 49 + 3 = 2*pow(7,2) + 0 * pow(7, 1) + 3 * pow(7,0)
这个 让我们想到进制(二进制,十六进制等等)了,基于这个思路我们实现如下的代码:
首先NumAnalysze类:该类的作用是把一个数表示成一个基于我们给定的进制,用一个链表保存其结果
public class NumAnalyse
{
private int _baseNum;//7:类似七进制
private List<int> _list;//203
//注意边界的问题
/// <summary>
/// 构造函数
/// </summary>
/// <param name="baseNum">是几进制的数据</param>
/// <param name="reaNum">实际的十进制数据</param>
public NumAnalyse(int baseNum, int reaNum)
{
//在本例中, _rand7.Next() - 1;
//考虑边界的问题,在这里需要好好的琢磨一下代码
reaNum--;//注意边界的问题
_baseNum = baseNum;
ResetList();
//得到的是倒序
while (reaNum >= _baseNum)
{
int remain = reaNum % baseNum;
reaNum = reaNum / baseNum;
_list.Add(remain);
}
if (reaNum != 0)
{
_list.Add(reaNum);
}
//最后反转过来
_list.Reverse();
}
public NumAnalyse(int baseNum)
{
_baseNum = baseNum;
ResetList();
_list.Add(0);
}
//重新设置
private void ResetList()
{
if(_list != null)
{
_list.Clear();
}
else
{
_list = new List<int>();
}
}
public int GetReaNum
{
get
{
int result = 0;
int i = 0;
for(i = 0; i < _list.Count; i++)
{
result += (_list[i]) * (int)Math.Pow(_baseNum, _list.Count - i - 1);
}
//+1 跟构造函数的类似,需要注意边界的问题
return result + 1;
}
}
public List<int> NumList
{
get
{
return _list;
}
}
}
在看RandSN类的实现:
public class RandSN : Rand7Base
{
private NumAnalyse na;//maxNum对应的伪进制数
private RandSN(int maxNum)
{
UpdateMaxNum(maxNum);
}
public void UpdateMaxNum(int maxNum)
{
_maxNum = maxNum;
na = new NumAnalyse(7, maxNum);
}
public static RandSN GetInstance(int maxNum)
{
if (rand == null || !(rand is RandSN))
{
rand = new RandSN(maxNum);
}
else if (rand is RandSN && rand.MaxNum != maxNum)
{
((RandSN)rand).UpdateMaxNum(maxNum);
}
return (RandSN)rand;
}
override public int Next()
{
int result = 0;
NumAnalyse tempNa = new NumAnalyse(7);
//注意边界问题
int num = _rand7.Next() - 1;
//表示前几位是否是一样的
bool preEqual = true;
for (int i = 0; i < na.NumList.Count; i++)
{
//必须有&& preEqual
//可以想象十进制如何比较2个数的大小的
if (num > na.NumList[i] && preEqual)
{
result = Next();
break;
}
else
{
tempNa.NumList.Add(num);
//如果前面是相等的,就继续判断,否则就不判断了
if(preEqual)
{
preEqual = (num == na.NumList[i]);
}
num = _rand7.Next() - 1;
}
}
//此处的result表示重新计算了
return result != 0 ? result : tempNa.GetReaNum;
}
}
添加测试函数:
static void TestRandSN()
{
RandSN rand = RandSN.GetInstance(101);
TestRand(rand);
rand = RandSN.GetInstance(572);
TestRand(rand);
}
测试结果:
计算Rand101的概率如下
耗时: 24.0733554924083 秒
Average:0.0099010;Max:0.0099738;Min:0.0098305
Max:0.0099738, Max-Average:0.0000728, Bits:0.7353800%
Min:0.0098305, Min-Average:-0.0000705, Bits:-0.7119500%
计算Rand572的概率如下
耗时: 30.9447089193863 秒
Average:0.0017483;Max:0.0017927;Min:0.0017121
Max:0.0017927, Max-Average:0.0000444, Bits:2.5424400%
Min:0.0017121, Min-Average:-0.0000362, Bits:-2.0678800%
关于Rand572的偏差是在2.54%左右,我想主要原因是,对于Rand572来说测试的总次数太小了,有兴趣的朋友可以扩大N倍试一试
或许在测试函数,应该计算方差而不是简单差概率。在此这一点先不管了。
到现在为止,我们一直是以Rand7为例子的,在这种算法中,是用到类似于进制的去解决的。我想应该能写一个更基本的算法,抛弃Rand7,可以用任意一个rand函数,如Rand10,rand20,rand16,rand2等等。