Search code examples
c++randomstl-algorithm

Why do I have to reseed the random generator when using iterators?


My goal is to fill each element of each vector of a vector of vectors with a random value. Please consider to following code:

#include <algorithm>
#include <iostream>
#include <random>
#include <vector>


typedef std::vector<int> IntVect;
typedef std::vector<IntVect> Grid;


void fillRandom(Grid& grid, int lo, int hi);


int main(int argc, const char * argv[]) {

    Grid grid { Grid(5, IntVect (5)) };

    fillRandom(grid, 0, 10);

    for (auto& row : grid) {
        for (auto& i : row) {
            std::cout << i << " ";
        }
        std::cout << "\n";
    }
    return 0;
}
// fillRandom v1
void fillRandom(Grid& grid, int lo, int hi) {

    // Init Random engine
    std::random_device rnd_device;
    std::mt19937 generator { rnd_device() };
    std::uniform_int_distribution<int> distr(lo, hi);
    auto dice = std::bind(distr, generator);

    Grid::iterator i { grid.begin() };
    for (; i != grid.end(); ++i) {
        std::generate(i->begin(), i->end(), dice);
        generator.seed(rnd_device());              // reseed
        dice = std::bind(distr, generator);
    }
    return;
}

In fillRandom I have to reseed the generator for each element of grid. Otherwise the output will be the same for each element of grid, like:

7 5 8 1 9 
7 5 8 1 9 
7 5 8 1 9 
7 5 8 1 9 
7 5 8 1 9

. However, if I change fillRandom to:

// fillRandom v2
void fillRandom(Grid& grid, int lo, int hi) {

    // Init Random engine as above

    for (auto& row : grid)
        for (auto& i : row)
            i = dice();
    return;
}

I get the expected result without reseeding the generator for each vectorof grid.

In the second version of fillRandom dice() is called for each element of each vector, resulting in a grid filled with random values. However, the first version should do exactly the same. But it obviously does not. What's the difference here?

Why do I have to reseed the random generator for every vector when I use std::generate and an iterator? Could you please help me to understand this behaviour?


Solution

  • The line std::generate(i->begin(), i->end(), dice); takes dice by value, creating a copy of it. This also copies the binded arguments. This results in you copying the same generator and distribution each time, without changing the original states. Each iteration begins with the same state.

    In the second example, where you simply call dice in a loop, you are causing the single instances of generator and distribution to produce values, advance their internal state as a consequence, and leading to different values on the next iteration.

    Try it with a lamdba instead.

    // fillRandom v1
    void fillRandom(Grid& grid, int lo, int hi) {
    
        // Init Random engine
        std::random_device rnd_device;
        std::mt19937 generator{ rnd_device() };
        std::uniform_int_distribution<int> distr(lo, hi);
        auto dice = [&generator, &distr]() { return distr(generator); };
    
        Grid::iterator i{ grid.begin() };
        for (; i != grid.end(); ++i) {
            std::generate(i->begin(), i->end(), dice);
        }
        return;
    }