1

I am trying to initialise an Eigen vector of integers with random numbers from a specific range. My approach so far has been to create a functor and calling it, it is however problematic in the sense that I need to initialise the random generation in a way that it wont be re-initialised each time.

What would be "the best" way of doing this? Speed-wise.

My code for the functor so far is:

std::mt19937 rd(time(0));
std::default_random_engine gen(rd());

template<typename Scalar>
struct RandomRange {
    RandomRange(const Scalar& low, const Scalar& high) : m_low(low), m_high(high) {}
    const Scalar operator()(const Scalar& high) const {
        std::uniform_int_distribution<> dis(m_low, m_high);
        return dis(gen); }
    Scalar m_low, m_high;
};

And i call it using:

VectorXi testVec = VectorXi(10).unaryExpr(RandomRange<int>(5,100));

A side question is where it would be proper to put such a definition? I am rather new to c++ in general, but I imagine it should be in a header file, but am unsure how it would work with the initialisation of the random number generator.

Best regards!

Jon Lachmann
  • 381
  • 1
  • 3
  • 10
  • 1
    Make the `dis` a (mutable, or remove `const` from `operator()`) member of `RandomRange`? – Max Langhof Mar 20 '19 at 17:33
  • @MaxLanghof `operator()` needs to be `const` for Eigen. But making `dis` mutable is one possible solution. – chtz Mar 20 '19 at 22:51

1 Answers1

3

First of all, initializing (seeding) a default_random_engine with one number of a Mersenne-twister does not really make sense. If the simple random engine is good enough, directly seed it with time(0) or whatever you prefer. If you need longer sequences of truly independent pseudo-random numbers, directly pass the mt19937 object to your distribution.

Also, you are not using the high argument of your operator(), so you should actually use a NullaryExpr. Also, you can make dis a member variable, and probably better store a reference to the generator instead of making it a global variable:

template<typename Scalar>
struct RandomRange {
    RandomRange(const Scalar& low, const Scalar& high, 
                std::default_random_engine &gen) : dis(low, high), gen(gen) {}
    const Scalar operator()() const { return dis(gen); }
    mutable std::uniform_int_distribution<> dis;
    std::default_random_engine &gen;
};

And call it like:

std::default_random_engine gen(time(0));
Eigen::VectorXi testVec = Eigen::VectorXi::NullaryExpr(10,RandomRange<int>(5,100, gen));

or

std::default_random_engine gen(time(0));
RandomRange<int> uniform(5,100, gen)
Eigen::VectorXi testVec = Eigen::VectorXi::NullaryExpr(10, uniform);

With C++11 you can also just define your distribution locally and call it using a lambda expression:

std::default_random_engine gen(time(0));
std::uniform_int_distribution<> dis(5,100);
Eigen::VectorXi testVec = Eigen::VectorXi::NullaryExpr(10,[&](){ return dis(gen); });

Or

std::default_random_engine gen(time(0));
std::uniform_int_distribution<> dis(5,100);
auto uni = [&](){ return dis(gen); };
Eigen::VectorXi testVec = Eigen::VectorXi::NullaryExpr(10,uni);

Or

std::default_random_engine gen(time(0));
Eigen::VectorXi testVec = Eigen::VectorXi::NullaryExpr(10,[&gen](){
    std::uniform_int_distribution<> dis(5,100);
    return dis(gen);
});

Some godbolt compilation examples: https://godbolt.org/z/uG0j__

Unless you find that this is a bottle-neck, I would not care too early about what variant is most performant (with proper optimization they should all be equivalent), but use whatever variant is most easy to read and maintain inside your code base.

chtz
  • 17,329
  • 4
  • 26
  • 56