在进行开发的时候,你难免会用到随机数。还有更多的时候,我们需要随机数来生成一系列的数字串备用。不过好在各种编程语言里都提供了一个简单好用的伪随机数生成器供你使用,比如C#里的 Random。
不过,C#的 Random 默认是以系统时钟为种子的——这种方法简单粗暴,可惜一旦遇到短时间生成大量随机数的情况就捉襟见肘了——一堆相同的随机数就蹦出来了,不过正是因为随机,所以才会有相同的数字出现,但是对于我们人类的感官来讲,“随机”其实指的是“随机且不重复”。
所以,你需要一个去重的算法。简单来讲,使用合集类型是最好的解决办法,比如我熟悉的 Swift 里有个 Set 类型,它自动过滤掉了重复的元素……不过我不熟悉 C# 里的类似类型,所以我也是使用了网上比较流行的方法。
网上比较流行的方法一种是递归的方式去重,另一种是用 Hashtable 。还有一种我忘记了,我使用的是后者,这个容易理解也好实现,逻辑简单。
生成随机数
大概的思路就是我们得到了随机数,就把它添加进 Hashtable ,由于这个是字典类型,那么我们把 key 和 value 都设置为相同的值——即要储存的字串即可。接下来每个要储存的字串都先判断一下是否已存在就好了,而 Hashtable 有这个自带方法 .ContainsValue() 。
那么,代码就应该是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public Hashtable genRandom(int Number) { Random ran = new Random(); Hashtable myRandom = new Hashtable(); while(myRandom.Count < Number) { int nValue = ran.Next(); if (!myRandom.ContainsValue(nValue)) { myRandom.Add(nValue, nValue); } } return myRandom; } |
这样,我们只要传入需要的随机数数量,则 genRandom(int) 就会返回带有对应数量的不重复随机数序列了,你可以方便地把 Hashtable 转换为数组。
不过,这依旧不太ok,这样返回的随机数长度不同,从大到小都有,如果我们需要的是一段相同的长度数据呢?随机数的产生从零到几万几十万,显然事后再慢慢挑选是个坏主意。
设定范围
比如具体到我自己的case,我需要一段相同位数的随机号码——好吧,我们还需要一个筛选器。什么是筛选器呢?其实就是个限制,我们不需要自己实现算法,因为 .Next() 方法已经给出了重载,使用 ran.Next(minValue,maxValue)即可,这样就可以实现让随机数“指哪打哪”了,好,现在我们来迭代上边的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public Hashtable genRandom(int Number,int length) { Random ran = new Random(); Hashtable myRandom = new Hashtable(); int lowLine = 1; for (int i = 1;i < length;++i) { lowLine *= 10; } int hightLine = lowLine * 10 - 1; while(myRandom.Count < Number) { int nValue = ran.Next(lowLine,hightLine); if (!myRandom.ContainsValue(nValue)) { myRandom.Add(nValue, nValue); } } return myRandom; } |
首先请原谅我这个中二的属性命名法,总之,我们可以给 genRandom(int,int) 传入需要的随机数数量和每个随机数长度(也就是位数)了。这样,我们就得到了给定数量、给定长度的随机数列。
不过,比如说还是我的case,这里我需求几千个结果——甚至说几万个?总之,这里要说的是虽然我们有了去重的机制,但出现重复需要判断是不可避免的,如果一旦出现重复,就需要再次循环,这样的话还是会消耗一部分资源——虽然说顶了天了也就几秒钟的事情(其实就最慢也就一两秒),但你想,用户点击“生成随机数”按钮,然后你的应用界面假死2秒——这不叫用户体验差——这叫bug。
所以,这时候你就需要一个更高级的随机数实现——当然了,比起这个,我想你更需要的应该是一个更高级的种子。
提高随机度
我们就地取材用系统的时钟,在每次循环的时候作为种子传给 Random 实例,这样就大大降低了重复的概率,避免了循环多次却得不到有效的数字。
1 2 |
long tick = DateTime.Now.Ticks; Random ran = new Random((int)(tick & 0xffffffffL) | (int)(tick >> 32)); |
把这段代码替换到上边的代码段当中就可以了。
其实还有另外的调用一些加密方法里的随机数种子生成器来生成,但我倒是不太喜欢,那样又要调用一个方法来生成一个随机的种子?反而不如就地取材来的实在。
One more thing
这里我再提一句,我是这样使用上边的方法的,利用构造器将返回的 Hashtable 转换为 ArrayList :
1 |
ArrayList myRandomNumbers = new ArrayList(genRandom(5000,13).Values); |
本文由 落格博客 原创撰写:落格博客 » C# 里的 随机数
转载请保留出处和原文链接:https://www.logcg.com/archives/1347.html