sockevalley
sockevalley

Reputation: 361

Random integers from a function always return the same number - why?

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

Answers (2)

Jerry Coffin
Jerry Coffin

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

Vittorio Romeo
Vittorio Romeo

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

Related Questions