Ответ 1
Интересный вопрос.
Поэтому мне было интересно, не мешать ли постоянно перезагружая его (т.е. воссоздавая распределение на каждом вызов get_int_from_range) Я получаю правильно распределенные результаты.
Я написал код, чтобы проверить это с помощью uniform_int_distribution
и poisson_distribution
. Достаточно легко расширить это, чтобы проверить другой дистрибутив, если хотите. Ответ кажется да.
Код котельной:
#include <random>
#include <memory>
#include <chrono>
#include <utility>
typedef std::mt19937_64 engine_type;
inline size_t get_seed()
{ return std::chrono::system_clock::now().time_since_epoch().count(); }
engine_type& engine_singleton()
{
static std::unique_ptr<engine_type> ptr;
if ( !ptr )
ptr.reset( new engine_type(get_seed()) );
return *ptr;
}
// ------------------------------------------------------------------------
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>
void plot_distribution( const std::vector<double>& D, size_t mass = 200 )
{
const size_t n = D.size();
for ( size_t i = 0; i < n; ++i )
{
printf("%02ld: %s\n", i,
std::string(static_cast<size_t>(D[i]*mass),'*').c_str() );
}
}
double maximum_difference( const std::vector<double>& x, const std::vector<double>& y )
{
const size_t n = x.size();
double m = 0.0;
for ( size_t i = 0; i < n; ++i )
m = std::max( m, std::abs(x[i]-y[i]) );
return m;
}
Код для фактических тестов:
#include <iostream>
#include <vector>
#include <cstdio>
#include <random>
#include <string>
#include <cmath>
void compare_uniform_distributions( int lo, int hi )
{
const size_t sample_size = 1e5;
// Initialize histograms
std::vector<double> H1( hi-lo+1, 0.0 ), H2( hi-lo+1, 0.0 );
// Initialize distribution
auto U = std::uniform_int_distribution<int>(lo,hi);
// Count!
for ( size_t i = 0; i < sample_size; ++i )
{
engine_type E(get_seed());
H1[ U(engine_singleton())-lo ] += 1.0;
H2[ U(E)-lo ] += 1.0;
}
// Normalize histograms to obtain "densities"
for ( size_t i = 0; i < H1.size(); ++i )
{
H1[i] /= sample_size;
H2[i] /= sample_size;
}
printf("Engine singleton:\n"); plot_distribution(H1);
printf("Engine creation :\n"); plot_distribution(H2);
printf("Maximum difference: %.3f\n", maximum_difference(H1,H2) );
std::cout<< std::string(50,'-') << std::endl << std::endl;
}
void compare_poisson_distributions( double mean )
{
const size_t sample_size = 1e5;
const size_t nbins = static_cast<size_t>(std::ceil(2*mean));
// Initialize histograms
std::vector<double> H1( nbins, 0.0 ), H2( nbins, 0.0 );
// Initialize distribution
auto U = std::poisson_distribution<int>(mean);
// Count!
for ( size_t i = 0; i < sample_size; ++i )
{
engine_type E(get_seed());
int u1 = U(engine_singleton());
int u2 = U(E);
if (u1 < nbins) H1[u1] += 1.0;
if (u2 < nbins) H2[u2] += 1.0;
}
// Normalize histograms to obtain "densities"
for ( size_t i = 0; i < H1.size(); ++i )
{
H1[i] /= sample_size;
H2[i] /= sample_size;
}
printf("Engine singleton:\n"); plot_distribution(H1);
printf("Engine creation :\n"); plot_distribution(H2);
printf("Maximum difference: %.3f\n", maximum_difference(H1,H2) );
std::cout<< std::string(50,'-') << std::endl << std::endl;
}
// ------------------------------------------------------------------------
int main()
{
compare_uniform_distributions( 0, 25 );
compare_poisson_distributions( 12 );
}
Запустите здесь.
Предоставляет ли стандарт С++ какие-либо гарантии в отношении этой темы?
Не то, чтобы я знал. Однако я бы сказал, что стандарт делает неявную рекомендацию не воссоздавать двигатель каждый раз; для любого распределения Distrib
прототип Distrib::operator()
принимает ссылку URNG&
, а не константу ссылки. Это, по-видимому, требуется, потому что двигателю, возможно, потребуется обновить его внутреннее состояние, но также подразумевает, что код выглядит так:
auto U = std::uniform_int_distribution(0,10);
for ( <something here> ) U(engine_type());
не компилируется, что для меня является явным стимулом не писать такой код.
Я уверен, что есть много советов о том, как правильно использовать случайную библиотеку. Это усложняется, если вам приходится обрабатывать возможность использования random_device
и разрешать детерминированный посев для целей тестирования, но я подумал, что было бы полезно также бросить мою собственную рекомендацию:
#include <random>
#include <chrono>
#include <utility>
#include <functional>
inline size_t get_seed()
{ return std::chrono::system_clock::now().time_since_epoch().count(); }
template <class Distrib>
using generator_type = std::function< typename Distrib::result_type () >;
template <class Distrib, class Engine = std::mt19937_64, class... Args>
inline generator_type<Distrib> get_generator( Args&&... args )
{
return std::bind( Distrib( std::forward<Args>(args)... ), Engine(get_seed()) );
}
// ------------------------------------------------------------------------
#include <iostream>
int main()
{
auto U = get_generator<std::uniform_int_distribution<int>>(0,10);
std::cout<< U() << std::endl;
}
Запустите здесь. Надеюсь, это поможет!
EDIT Моя первая рекомендация была ошибкой, и я прошу прощения за это; мы не можем использовать одноэлементный движок, как в вышеприведенных тестах, потому что это будет означать, что два равномерных распределения int будут производить одну и ту же случайную последовательность. Вместо этого я полагаюсь на то, что std::bind
копирует недавно созданный движок локально в std::function
со своим собственным семенем, и это дает ожидаемое поведение; разные генераторы с одинаковым распределением создают разные случайные последовательности.