关于c#:每次运行应用程序时生成相同数字的随机数生成器

Random number generator generating same numbers each time application is ran

本问题已经有最佳答案,请猛点这里访问。

我知道这个问题有好几次被提出来,但这些解决方案都不适合我。

首先,我在名为RandomNumGenerator(items)的方法中做了这个。

1
2
3
4
5
List<int> randNum = new List<int>();
foreach (var item in items)
{
    randNum.Add(new Random(1000).Next());
}

这总是给我相同的号码,然后在看了这个答案之后,我做了如下的事情:

1
2
3
4
5
Random rnd = new Random(1000);
foreach (var item in items)
{
    randNum.Add(rnd.Next());
}

这给了我下面的数字

1
2
3
4
5
325467165
506683626  
1623525913  
2344573    
1485571032

现在,虽然这对于循环的每次迭代都是好的,但这里的问题是,当我停止并重新运行应用程序时,我会得到与以前相同的数字。

1
2
3
4
5
325467165
506683626  
1623525913  
2344573    
1485571032

这种行为是只在调试期间发生的,还是每次调用RandomNumGenerator时都会出现同样的问题?


您在这里使用相同的种子1000来播种Random实例:

1
Random rnd = new Random(1000);

由于当前时间用作种子,因此不会执行此操作:

1
Random rnd = new Random();

看看采用int的构造函数。

Providing an identical seed value to different Random objects causes
each instance to produce identical sequences of random numbers.


按MSDN计算。

1
2
3
public Random(
    int Seed
)

Seed

A number used to calculate a starting value for the pseudo-random number sequence. If a negative number is specified, the absolute value of the number is used.

大多数初学者犯的涉及RNG(随机数生成器)的错误的原因是缺乏对"种子"是什么和它做什么的理解。

那么什么是"种子"?

Random类是一个生成伪随机数的类,或看似随机的数。它们通常是一个数学函数,使用一个参数"seed"来生成一个看起来是随机的数字序列。

new Random(1000)的情况下,前5个非负随机整数是

325467165
506683626
1623525913
2344573
1485571032

在第一个代码中,每次需要一个随机数时,都会用相同的种子创建一个新的伪随机数序列,因此很明显,数组中填充了相同的数字:325467165,这恰好是new Random(1000)生成的第一个非负整数。

这也解释了为什么每次启动应用程序时,第二个代码总是生成相同的伪随机数序列。

为了确保应用程序总是生成不同的伪随机序列,每次都需要使用不同的种子。到目前为止,确保这一点的最简单方法是慢慢来,字面上。

1
2
Random rnd = new Random(DateTime.UtcNow.Millisecond);
// Taking the millisecond component, because it changes quickly

幸运的是,您不必键入这么多,因为Random类的默认构造函数已经做了类似的事情。

1
Random rnd = new Random(); // Much simpler, isn't it?

请记住,Random类不是线程安全的;如果多个线程试图同时访问同一个Random对象,则RNG在其剩余寿命内只返回0。

另一件需要注意的事情是,创建多个Random对象,一个接一个,即使使用时间作为种子,也可能导致相同的伪随机数序列。

1
2
3
4
Random r1 = new Random();
Random r2 = new Random();
Random r3 = new Random();
Random r4 = new Random();

在上面的代码中,r1r2r3r4生成相同序列的可能性很高。

这怎么可能?好吧,(联合国)幸运的是,CPU正在飞速发展。一个1GHz的CPU每秒可以执行大约10亿条指令(给出或接受);这是每1纳秒1条指令,或者每一百万分之一秒1条指令。创建一个新的Random对象可能需要相当多的指令,但绝大多数都不到一百万条。

那么,如果使用时钟的当前毫秒计数是我们"所有人"想要的,并且已经是默认值,那么为什么我们需要手动定义一个种子呢?

因为它对于保持多个终端的同步非常有用。

想象一个游戏,其中重要的现象随机出现,如天气变化,可能完全颠覆游戏。你不会只想让一方遭受雾害,而其他方仍然从晴朗的天气中获利,对吧?

当然,您可以让服务器或主机生成随机的天气变化并通知玩家;或者您可以在游戏开始前定义种子,并使用该种子来确保整个游戏中所有玩家的"随机性"。

编码不是很有趣吗?


您需要更改:

1
Random rnd = new Random(1000);

1
Random rnd = new Random();

从随机构造函数文档中:

The default seed value is derived from the system clock and has finite
resolution. As a result, different Random objects that are created in
close succession by a call to the default constructor will have
identical default seed values and, therefore, will produce identical
sets of random numbers. This problem can be avoided by using a single
Random object to generate all random numbers. You can also work around
it by modifying the seed value returned by the system clock and then
explicitly providing this new seed value to the Random(Int32)
constructor. For more information, see the Random(Int32) constructor.


关键概念是随机种子——随机种子从中衍生出其他一切的初始数据。如果种子是相同的,那么"随机"序列将是相同的。

默认情况下,seed设置为零,这显然会导致程序运行之间的重复序列。

为了避免这种情况,您可以这样构造您的随机对象:

1
Random rnd = new Random();

…引擎盖下面是:

1
Random rnd = new Random(Environment.TickCount);

这将从操作系统启动开始以毫秒数初始化随机对象。每次程序启动时,这都会有所不同,因此每次都会得到不同的随机序列。


random.next()方法生成伪随机数。您应该声明并初始化一个随机对象,而不是每次创建一个新对象。也不需要使用任何晶体造影术。:)


你应该使用类级随机变量。如果在方法级别使用一个新的随机数作为局部变量,则时间相关的种子将重复自身,生成相同的随机数序列。

1
2
3
4
5
6
7
8
class Program
{
 static Random _r = new Random();
 static void Main()
 {
// use _r variable to generate random number
 }
}