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 vector
of 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?
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;
}