如何在构造函数中构造预定义的随机数生成器和种子序列?

时间:2017-12-28 13:35:28

标签: c++ random c++17

背景

我正在构建一个离散选择器,例如给定一个序列,它根据给定的权重随机选取一个元素。您可以将其视为一对PredefinedRandomGeneratorstd::discrete_distribution。我希望选择器是可移植的,并提供合理的熵。它将用于加密上下文,我只想要更好的随机性(抱歉,如果我在这里弄乱了术语)。

问题

Windows上的MinGW上的

std:random_device总是产生相同的数字。作为解决方法,我使用

//assume using namespace std::chrono
std::seed_seq seq{std::random_device{}(), 
                  duration_cast<nanoseconds>(system_clock::now().time_since_epoch()).count(),
                  42};

问题是PredefinedRandomGenerator的构造函数需要lvalue-reference,所以我不能在成员初始化列表中初始化它,如下所示:

template <typename InputIterator>
discrete_selector(InputIterator weights_first,
                  InputIterator weights_last):
        generator(std::seed_seq{/*...*/}),
        distribution(weights_first, weights_last)
{}

所以现在至少有一个或多或少随机数的来源。

我尝试了什么

//somewhere in namespace details
std::seed_seq& get_new_sequence()
{
    static std::unique_ptr<std::seed_seq> sequence;
    sequence = std::make_unique<std::seed_seq>(std::random_device{}(), 
                                            duration_cast<nanoseconds>(system_clock::now().time_since_epoch()).count(),
                                            42);
    return *sequence;
}

上述应该有效,但我相信应该有更好的方法。

附带问题

为什么构造函数通过左值引用获取std::seed_seq?它至少可以使用rvalue-reference。

2 个答案:

答案 0 :(得分:2)

我通常做的是使用lambda。

generator([]() -> auto& {
    static std::seed_seq seq(std::random_device()(), 
              duration_cast<nanoseconds>(system_clock::now().time_since_epoch()).count(),
              42);
    return seq;
}())

如果你愿意,你可以随时将lambda放在另一个私有函数中,这样做也适用于所有意图和目的。

或者,就像tree with eyes所说的那样,你可以在构造函数中初始化种子。 :)

答案 1 :(得分:0)

是否可以添加声明?

你目前有这个:

template <typename InputIterator>
discrete_selector(InputIterator weights_first,
                  InputIterator weights_last):
        generator(std::seed_seq{/*...*/}),
        distribution(weights_first, weights_last)
{}

考虑std::seed_seq不可复制的事实 - 不可移动 而不是重写现有的构造函数,你可以重载它:

#include <isostream>
#include <vector>
#include <map>
#include <random>

template<class IntType = int>
class discrete_selector {
private:
    std::mt19937 generator;
    std::discrete_distribution<IntType> distribution;

public:
    template<class InputIt>
    discrete_selector( InputIt wf, InputIt wl ) :
        generator( std::seed_seq{} ),
        distribution( wf, wl ) 
    {}

    // Constructor that takes an initializer list; you can not initialize
    // std::seed_seq by constructor in initializer list then pass it to a 
    // random generator or engine. seed_seq is non copy non moveable and     
    // besides that; the engines will not be able to convert the parameter 
    // type even if you tried. We have to create an instance of seed_seq in 
    // the constructor's body and we can then pass to it the initializer 
    // list from the constructor's parameter list. After we construct the
    // seed_seq then we can use our engine's or generator's `seed` function
    // and pass to it whatever type of seed we are using. In this case seed_seq
    template<class InputIt, class Type>
    discrete_selector( InputIt wf, InputIt wl, std::initializer_list<Type> list ) :
        distribution( wf, wl ) 
    {
        std::seed_seq seq( list );
        generator.seed( seq );
    }

    std::mt19937 getGenerator() {
        return generator;
    }

    IntType getDistributionMin() {
        return distribution.min();
    }

    IntType getDistributionMax() {
        return distribution.max();
    }

    // Copy?
    std::discrete_distribution<IntType> getDistribution()  {
        return distribution;
    }

    // Ref?
    void getDistribution( std::discrete_distribution<IntType>&& dd )  {
        dd = distribution;
    }

    std::vector<double> getDistriubtionProbabilities() {
        return distribution.probabilities();
    }

    int generate() {
        return distribution( generator );
    }

    int generate( int& val ) {
        val = distribution( generator );
    }
};

// main program demo example    
int main() {
    std::initializer_list<unsigned> list{ 1,2,3,4,5 };
    discrete_selector<> ds( 100, 10000, list );
    int val = ds.generate();

    std::vector<double> probs{ ds.getDistriubtionProbabilities() };
    for( auto p : probs ) {
        std::cout << p << " ";
    }
    std::cout << std::endl << std::endl;

    //std::discrete_distribution<> dd = ds.getDistribution();
    //std::mt19937 gen = ds.getGenerator();
    std::map<unsigned int, unsigned int> m;
    for( int n = 0; n < 10000; ++n ) {
        ++m[ds.generate()]; // use class built in function
    }

    for( auto p : m ) {
        std::cout << p.first << " generated " << p.second << " times\n";
    }

    return 0; 
}