从函数中生成的随机整数总是返回相同的数字 - 为什么?

3

我目前正在学习C++,有一个关于random_device的问题。

我正在尝试模拟蒙提霍尔问题。为此,我需要随机整数来选择随机门。因此,我尝试编写一个函数来返回随机整数以实现这个目的。

int guessGetRandomIndex() {
    std::random_device rd; // obtain a random number from hardware
    std::mt19937 eng(rd()); // seed the generator
    std::uniform_int_distribution<> distr(0, 2);
    return distr(eng);

然而,当我在main()中尝试获取随机整数时,我一直得到相同的数字。
    vector<int> v = {0, 1, 2, 3, 4, 5};
for (auto & j : v){
    int random = guessGetRandomIndex();
    std::cout << random << ' ';
}    
 Output: 1 1 1 1 1 etc.

然而,当我将随机设备放入主循环并在那里执行相同的操作时,我成功地获得了随机整数。
    std::random_device rd; // obtain a random number from hardware
std::mt19937 eng(rd()); // seed the generator
std::uniform_int_distribution<> distr(0, 2); // define the range
vector<int> v = {0, 1, 2, 3, 4, 5};
for (auto & j : v){

    std::cout << distr(eng) << ' ';
}   

 Output: 1 2 1 2 0 1

为什么会这样呢?我只是想封装随机设备。我认为每次调用函数时它会生成新的随机数,就像Python中的random.randint一样,但我想C++中可能还有其他事情发生。

提前感谢您的帮助。


你的函数不是封装了一个随机设备;而是封装了N个随机设备和伪随机数生成器,其中N是被调用执行的次数。将这两个(random_devicemt19937)都设置为静态 - WhozCraig
2个回答

9
您每次调用guessGetRandomIndex都会创建一个新的std::random_devicestd::mt19937生成器。将它们标记为static或通过引用将它们传递到函数中,以避免重新实例化它们。
int guessGetRandomIndex() {
    static std::random_device rd; // obtain a random number from hardware
    static std::mt19937 eng(rd()); // seed the generator
    std::uniform_int_distribution<> distr(0, 2);
    return distr(eng);
}

为了生成伪随机数,标准库提供的生成器包含一个内部状态,每次生成一个值时该状态会发生变化。如果您不断重新实例化一个生成器,则其内部状态将始终匹配其初始状态,因此始终生成相同的数字。

如果您能解释一下为什么每次实例化随机数生成器会导致问题,那将非常有帮助。 - ForceBru
6
rand()与标准库<random>模块无关 - 它是一个具有不同种子生成规则的C函数。请查看[其文档](http://en.cppreference.com/w/cpp/numeric/random/rand)。由于它会产生低质量的伪随机数并且在现代C++中不符合惯用法,因此不建议使用它。 - Vittorio Romeo
关于 rand() - 你应该看看这个:https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful - Jesper Juhl
很遗憾,Jonas。std::random_device可以被实现成每次都得到相同的随机数序列。cppreference上的警告如下:“如果实现中没有可用的非确定性源(例如硬件设备),则std::random_device可能以实现定义的伪随机数引擎为基础实现。在这种情况下,每个std::random_device对象可能生成相同的数字序列。” - user4581301
1
@Jonas 这就是一个实现可能会做的事情。如果你有一个高可靠性的 RNG,请使用它,但标准必须保持开放的可能性,即实现可能在裸机上运行或没有受信任的第三方支持。在标准的最新草案中从 [rand.device] 读取,“如果实现限制防止生成非确定性随机数,则实现可以使用随机数引擎。”这就导致了如何种子化种子的问题? - user4581301
显示剩余3条评论

3
虽然不能保证每次都得到相同的结果,但你尝试的方式明显是次优的(接近于错误)。特别是,至少根据你展示的代码,每次请求另一个数字时,你都会重新生成 PRNG 种子。你想要做的是仅在最开始生成种子,然后连续使用数字而不重新生成种子。
你可以通过将它们标记为函数内的“static”来修复这个问题,或者(通常更好的方法)使用一个类,在构造函数中执行这些操作,并在成员函数中生成一个数字:
class RandomIndex {
    std::mt19937 eng;
    std::uniform_int_distribution<> distr;
public:
    guessGetRandomIndex(int lower, int upper) 
        : eng(std::random_device()())
        , distr(lower, upper)
    {}

    int operator()() { 
         return distr(eng);
    }
};

这是一些额外的代码,但不是非常复杂--而且它可能会带来一些好处1

使用这个相当简单:

RandomIndex d(0, 2);

for (int i=0; i<10; i++)
    std::cout << d();

一个结果:

0 2 1 2 1 2 0 2 1 0

一个小的侧面说明:尽管该运行不显示单个数字的重复,但它包含了序列 2 1 ,比大多数人预期的要多。实际上,测试表明,人们通常认为随机序列中包含的重复(单个数字或像上面展示的序列)要远少于随机发生的次数。例如,大多数人会看一眼像 0 0 1 1 2 2 这样的序列,并判断它是不应该(或几乎不可能)随机发生的。事实上,它和具有相同参数的任何其他序列一样可能发生。因此,如果你防止它发生,你就会减少随机性。

1. 例如,包含静态变量的函数可以包括一些代码来检查是否已经完成了初始化,仅在之前没有完成初始化时才执行初始化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接