Reputation: 361
I'm currently learning C++ and have a question about the random_device.
I'm trying to simulate the monty hall problem. For this I need random integers to pick random doors. Therefore I tried making a function that returns random integers for this purpose.
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);
However when I try to get random integers in main() I keep getting same numbers.
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.
However when I put the random device in the main loop and do the same there I manage to get random integers.
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
Why is that? I'm simply trying to encapsulate the random device. I thought it would generate new random number each time the function is called similiar to Pythons random.randint but I reckon there is something else going on in C++.
Thanks in advance.
Upvotes: 3
Views: 3492
Reputation: 490108
While it's not guaranteed to give identical results every time, the way you're trying to do things is definitely sub-optimal (bordering on just plain wrong). In particular, at least as you've shown the code, you're re-seeding the PRNG every time you ask for another number. What you want to do is seed it once, then use successive numbers without re-seeding.
You can fix this by marking them as static
in the function, or (usually better) use a class, with those actions carried out in the ctor, and generating a number in a member function:
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);
}
};
This is a little extra code, but not massively so--and it can have some benefits1.
Using this is fairly simple:
RandomIndex d(0, 2);
for (int i=0; i<10; i++)
std::cout << d();
One result:
0 2 1 2 1 2 0 2 1 0
One minor side-note: although this particular run doesn't show repetitions of a single number, it does include the sequence 2 1
more than most people tend to expect. In fact, tests have shown that people generally expect a random sequence to contain substantially less repetition (of single numbers or of sequences like shown above) than will happen randomly. For example, most people would glance at a sequence like 0 0 1 1 2 2
and judge it as something that should never (or nearly never) happen randomly. In fact, it's just as likely as any other sequence with the same parameters, so if you prevent it from happening, you're reducing randomness.
1. For example, a function with statics may include some code to check whether initialization has been done, and do it if and only if it hasn't been done previously.
Upvotes: 3
Reputation: 93264
You're creating a new std::random_device
and std::mt19937
generator every time you call guessGetRandomIndex
. Mark them as static
or pass them to the function via reference to avoid re-instantiating them:
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);
}
In order to generate pseudorandom numbers, the generators provided by the standard library contain an internal status that varies every time they generate a value. If you keep re-instantiating a generator, the internal state will always match its initial one, thus always generating the same number.
Upvotes: 9