一道面试题引发的有关随机数的思考(2)

上一篇文章(http://blog.csdn.net/xzjxylophone/article/details/6832211)我们已经按照面试题的要求完成了Rand10的功能。

在文章的结尾处,猜测了有可能有其他的方法去实现Rand10.

那么这篇文章就来说说如何去实现10=5*2(5个集合,每个集合有2个元素)和12=2*6(2个集合,每个集合有6个元素)

既然我们知道有代码重构这个概念,我想有必要代码重构下!Let's Go

首先一个所有随机数的抽象类

Rand

//抽象类
//随即数范围是[1, MaxNum]
//注:[a,b],表示包括a也包括b
//(a,b],表示不包括a但是包括b
//数学中闭合的关系
public abstract class Rand
{
	//获取下一个随机数
	public abstract int Next();
	//随机数类的实例
	protected static Rand rand;
	//指明的最大数
	protected int _maxNum;
	public int MaxNum
	{
		get
		{
			return _maxNum;
		}
	}
}
Rand7:

//1~7的随机数产生类  
public class Rand7 : Rand
{
	private readonly Random _random = new Random();
	private Rand7()
	{
		_maxNum = 7;
	}
	public static Rand7 GetInstance()
	{
		if (rand == null || !(rand is Rand7))
		{
			rand = new Rand7();
		}
		return (Rand7)rand;
	}
	//获得随机数   
	override public int Next()
	{
		return _random.Next(1, 8);
	}   
}


以Rand7为基础计算其他数的随机数的一个基类:

Rand7Base:

//该类的作用表示其他随机数是以Rand7为基础
public abstract class Rand7Base : Rand
{
	protected Rand7 _rand7 = Rand7.GetInstance();
}

把上一篇文章Rand10和Rand12类重构如下:(注意基类)

//1~10的随机数产生类
public class Rand10 : Rand7Base
{
	private Rand10()
	{
		_maxNum = 10;
	}
	public static Rand10 GetInstance()
	{
		if (rand == null || !(rand is Rand10))
		{
			rand = new Rand10();
		}
		return (Rand10)rand;
	}
	//2个集合,每个集合5个元素
	//(1,3,5,7,9)(2,4,6,8,10)
	override public int Next()
	{
		int num;
		//均匀产生1、 2 、3、4、5   
		while (true)
		{
			num = _rand7.Next();
			if (num <= 5)
				break;
		}

		while (true)
		{
			int n = _rand7.Next();
			if (n == 1)
				continue;
			//n大于4的数字有5、6、7,因为是由Rand7产生的,所以概率均匀   
			if (n > 4)
				//因为num只可取值1、2、3、4、5并且取值概率均匀,num*2可得2、4、6、8、10也概率均匀   
				num *= 2;
			//n小于4的数字有1、2、3,因为是由Rand7产生的,所以概率均匀   
			else
				//因为num只可取值1、2、3、4、5并且取值概率均匀,num*2-1可得1、3、5、7、9也概率均匀   
				num = num * 2 - 1;
			break;
		}
		return num;
	}   
}

public class Rand12 : Rand7Base
{
	private Rand12()
	{
		_maxNum = 12;
	}
	public static Rand12 GetInstance()
	{
		if (rand == null || !(rand is Rand12))
		{
			rand = new Rand12();
		}
		return (Rand12)rand;
	}
	//获得随机数   
	//3个集合,每个集合有4个数据
	//(3,6,9,12)(2,5,8,11)(1,4,7,10)
	override public int Next()
	{
		//3*4
		int num;  
		while (true)
		{
			num = _rand7.Next();
			if (num <= 4)
				break;
		}

		while (true)
		{
			int n = _rand7.Next();
			if (n == 1)
				continue;  
			if (n > 5)
				num *= 3;
			else if (n > 3)
				num = num * 3 - 1;
			else 
				num = num * 3 - 2;
			break;
		}
		return num;
	}   
}

测试类中:

[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
static extern bool QueryPerformanceCounter(ref long count);
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
static extern bool QueryPerformanceFrequency(ref long count); 
//测试随即数产生的概率
static void SubTestRand(Rand rand)
{
	long total = 10000000;

	long count = 0;
	long count1 = 0;
	long freq = 0;
	double result = 0;
	QueryPerformanceFrequency(ref freq);
	QueryPerformanceCounter(ref count);   

	int[] numArray = new int[rand.MaxNum];
	double[] doubleResult = new double[rand.MaxNum];
	for (long i = 0; i < total; i++)
	{
		int randomNumber = rand.Next();
		numArray[randomNumber - 1]++;
	}

	//打印产生各数的概率
	for (int i = 0; i < numArray.Length; i++)
	{
		double res = (Double)numArray[i] / total;
		doubleResult[i] = res;
		string eachResult = string.Format("产生{0}的概率是:{1:0.0000000}", i + 1, res);
		Console.WriteLine(eachResult);
	}

	QueryPerformanceCounter(ref count1);
	count = count1 - count;
	result = (double)(count) / (double)freq;
	string spendTime = string.Format("耗时: {0} 秒", result);
	Console.WriteLine(spendTime);

	double average = (double)1 / doubleResult.Length;
	double max = doubleResult[0];
	double min = doubleResult[0];
	for (int i = 0; i < doubleResult.Length; i++)
	{
		max = max > doubleResult[i] ? max : doubleResult[i];
		min = min < doubleResult[i] ? min : doubleResult[i];
	}
	string totalMes = string.Format("Average:{0:0.0000000};Max:{1:0.0000000};Min:{2:0.0000000}", average, max, min);
	string maxMes = string.Format("Max:{0:0.0000000}, Max-Average:{1:0.0000000}, Bits:{2:0.0000000}%",
		max, max - average, (max - average) / average * 100);
	string minMes = string.Format("Min:{0:0.0000000}, Min-Average:{1:0.0000000}, Bits:{2:0.0000000}%",
		min, min - average, (min - average) / average * 100);

	Console.WriteLine(totalMes);
	Console.WriteLine(maxMes);
	Console.WriteLine(minMes);
}
static void TestRand(Rand rand)
{
	Console.WriteLine(string.Format("计算Rand{0}的概率如下", rand.MaxNum));
	SubTestRand(rand);
}

如果要测试Rand10和Rand12可以添加如下函数:

static void TestRand10()
{
	Rand10 rand = Rand10.GetInstance();
	TestRand(rand);
}
static void TestRand12()
{
	Rand12 rand = Rand12.GetInstance();
	TestRand(rand);
}

在主函数中可以调用:

static void Main(string[] args)
{
	TestRand10();
	TestRand10();
	Console.ReadLine();   
}

可以通过注释主函数中的代码来测试任何一个。

以上这么多内容只是说了重构,现在我们来实现10=5*2(5个集合,每个集合有2个元素)和12=2*6(2个集合,每个集合有6个元素)。

实现10=5*2, 假设类名为:Rand10_2(表示Rand10的第二种实现方案)

public class Rand10_2:Rand7Base
{
	private Rand10_2()
	{
		_maxNum = 10;
	}
	public static Rand10_2 GetInstance()
	{
		if (rand == null || !(rand is Rand10_2))
		{
			rand = new Rand10_2();
		}
		return (Rand10_2)rand;
	}
	//5个集合,每个集合2个元素
	//(1,6)(2,7)(3,8)(4,9)(5,10)
	override public int Next()
	{
		int num;
		//均匀产生1、 2 、3、4、5   
		while (true)
		{
			num = _rand7.Next();
			if (num <= 2)
				break;
		}

		while (true)
		{
			int n = _rand7.Next();
			if (n > 5)
				continue;
			//n是从1到5
			//num刚开始只是1或者2
			//所以要-1
			num = 5 * num - n + 1;
			break;
		}
		return num;
	}   
}
在测试类添加如下函数:

//此函数的作用是表示随机数的其他方法
static void TestRand(Rand rand, string message)
{
	Console.WriteLine(string.Format("计算{0}的概率如下", message));
	SubTestRand(rand);
}
static void TestRand10_2()
{
	Rand10_2 rand = Rand10_2.GetInstance();
	TestRand(rand, "Rand10_2");
}

同样的实现: 12=2*6(2个集合,每个集合有6个元素),Rand12_2

public class Rand12_2 : Rand7Base
{
	private Rand12_2()
	{
		_maxNum = 12;
	}
	public static Rand12_2 GetInstance()
	{
		if (rand == null || !(rand is Rand12_2))
		{
			rand = new Rand12_2();
		}
		return (Rand12_2)rand;
	}
	//2个集合,每个集合有6个数据
	//(2,4,6,8,10,12)(1,3,5,7,9,11)
	override public int Next()
	{
		int num;
		while (true)
		{
			num = _rand7.Next();
			if (num <= 6)
				break;
		}

		while (true)
		{
			int n = _rand7.Next();
			if (n == 1)
				continue;
			if (n > 4)
				num *= 2;
			else
				num = num * 2 - 1;
			break;
		}
		return num;
	}   
}

在测试类添加测试代码:

static void TestRand12_2()
{
	Rand12_2 rand = Rand12_2.GetInstance();
	TestRand(rand, "Rand12_2");
}

更新测试类的主函数

static void Main(string[] args)
{
	TestRand10();
	TestRand10_2();
	TestRand12();
	TestRand12_2();
	Console.ReadLine();   
}

运行,得到的结果是:

计算Rand10的概率如下
产生1的概率是:0.0999950
产生2的概率是:0.1000683
产生3的概率是:0.1000904
产生4的概率是:0.0998266
产生5的概率是:0.1000829
产生6的概率是:0.0999189
产生7的概率是:0.1000285
产生8的概率是:0.0999484
产生9的概率是:0.0999287
产生10的概率是:0.1001123
耗时: 2.23924715284627 秒
Average:0.1000000;Max:0.1001123;Min:0.0998266
Max:0.1001123, Max-Average:0.0001123, Bits:0.1123000%
Min:0.0998266, Min-Average:-0.0001734, Bits:-0.1734000%
计算Rand10_2的概率如下
产生1的概率是:0.1001656
产生2的概率是:0.0998927
产生3的概率是:0.1000315
产生4的概率是:0.1000447
产生5的概率是:0.1000213
产生6的概率是:0.0998959
产生7的概率是:0.0999171
产生8的概率是:0.0998640
产生9的概率是:0.1000109
产生10的概率是:0.1001563
耗时: 4.63440941163983 秒
Average:0.1000000;Max:0.1001656;Min:0.0998640
Max:0.1001656, Max-Average:0.0001656, Bits:0.1656000%
Min:0.0998640, Min-Average:-0.0001360, Bits:-0.1360000%
计算Rand12的概率如下
产生1的概率是:0.0832689
产生2的概率是:0.0833657
产生3的概率是:0.0832885
产生4的概率是:0.0833306
产生5的概率是:0.0833956
产生6的概率是:0.0832436
产生7的概率是:0.0833872
产生8的概率是:0.0833305
产生9的概率是:0.0832966
产生10的概率是:0.0834083
产生11的概率是:0.0834503
产生12的概率是:0.0832342
耗时: 2.61100502823934 秒
Average:0.0833333;Max:0.0834503;Min:0.0832342
Max:0.0834503, Max-Average:0.0001170, Bits:0.1403600%
Min:0.0832342, Min-Average:-0.0000991, Bits:-0.1189600%
计算Rand12_2的概率如下
产生1的概率是:0.0834729
产生2的概率是:0.0832840
产生3的概率是:0.0833406
产生4的概率是:0.0834923
产生5的概率是:0.0833294
产生6的概率是:0.0830786
产生7的概率是:0.0834936
产生8的概率是:0.0833099
产生9的概率是:0.0834100
产生10的概率是:0.0832989
产生11的概率是:0.0832233
产生12的概率是:0.0832665
耗时: 2.46493227776721 秒
Average:0.0833333;Max:0.0834936;Min:0.0830786
Max:0.0834936, Max-Average:0.0001603, Bits:0.1923200%
Min:0.0830786, Min-Average:-0.0002547, Bits:-0.3056800%

以上可以看出误差范围都是在-0.2%到0.2%之间。

我想这个随机数是可以接受了。

既然10和12都可以通过7来获得,那么11该如何通过7来获得了? 下一篇文章我们来介绍如何通过Rand7来得到Rand11.


你可能感兴趣的:(一道面试题引发的有关随机数的思考(2))