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