Mercurial > thymian
diff common/random.h @ 0:a4671277546c tip
created the repository for the thymian project
| author | ferencd |
|---|---|
| date | Tue, 17 Aug 2021 11:19:54 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/random.h Tue Aug 17 11:19:54 2021 +0200 @@ -0,0 +1,1178 @@ +#ifndef EFFOLKRONIUM_RANDOM_HPP +#define EFFOLKRONIUM_RANDOM_HPP + +#include <random> +#include <chrono> // timed seed +#include <type_traits> +#include <cassert> +#include <initializer_list> +#include <utility> // std::forward, std::declval +#include <algorithm> // std::shuffle, std::next, std::distance +#include <iterator> // std::begin, std::end, std::iterator_traits +#include <limits> // std::numeric_limits +#include <ostream> +#include <istream> + +namespace effolkronium { + + namespace details { + /// Key type for getting common type numbers or objects + struct common{ }; + + /// True if type T is applicable by a std::uniform_int_distribution + template<typename T> + struct is_uniform_int { + static constexpr bool value = + std::is_same<T, short>::value + || std::is_same<T, int>::value + || std::is_same<T, long>::value + || std::is_same<T, long long>::value + || std::is_same<T, unsigned short>::value + || std::is_same<T, unsigned int>::value + || std::is_same<T, unsigned long>::value + || std::is_same<T, unsigned long long>::value; + }; + + /// True if type T is applicable by a std::uniform_real_distribution + template<typename T> + struct is_uniform_real { + static constexpr bool value = + std::is_same<T, float>::value + || std::is_same<T, double>::value + || std::is_same<T, long double>::value; + }; + + /// True if type T is plain byte + template<typename T> + struct is_byte { + static constexpr bool value = + std::is_same<T, signed char>::value + || std::is_same<T, unsigned char>::value; + }; + + /// True if type T is plain number type + template<typename T> + struct is_supported_number { + static constexpr bool value = + is_byte <T>::value + || is_uniform_real<T>::value + || is_uniform_int <T>::value; + }; + + /// True if type T is iterator + template<typename T> + struct is_iterator { + private: + static char test( ... ); + + template <typename U, + typename = typename std::iterator_traits<U>::difference_type, + typename = typename std::iterator_traits<U>::pointer, + typename = typename std::iterator_traits<U>::reference, + typename = typename std::iterator_traits<U>::value_type, + typename = typename std::iterator_traits<U>::iterator_category + > static long test( U&& ); + public: + static constexpr bool value = std::is_same< + decltype( test( std::declval<T>( ) ) ), long>::value; + }; + + } // namespace details + + /// Default seeder for 'random' classes + struct seeder_default { + /// return seed sequence + std::seed_seq& operator() ( ) { + // MinGW issue, std::random_device returns constant value + // Use std::seed_seq with additional seed from C++ chrono + return seed_seq; + } + private: + std::seed_seq seed_seq{ { + static_cast<std::uintmax_t>( std::random_device{ }( ) ), + static_cast<std::uintmax_t>( std::chrono::steady_clock::now( ) + .time_since_epoch( ).count( ) ), + } }; + }; + + /** + * \brief Base template class for random + * with static API and static internal member storage + * \note it is NOT thread safe but more efficient then + * basic_random_thread_local + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template<typename> class IntegerDist = std::uniform_int_distribution, + template<typename> class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_static { + public: + basic_random_static( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template<typename T> + using integer_dist_t = IntegerDist<T>; + + /// Type of used real distribution + template<typename T> + using real_dist_t = RealDist<T>; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine.discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine.seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template<typename Sseq> + static void seed( Sseq& seq ) { + engine.seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template<typename CharT, typename Traits> + static void serialize( std::basic_ostream<CharT, Traits>& ost ) { + ost << engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template<typename CharT, typename Traits> + static void deserialize( std::basic_istream<CharT, Traits>& ist ) { + ist >> engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + static typename std::enable_if<details::is_uniform_int<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist<T>{ from, to }( engine ); + return IntegerDist<T>{ to, from }( engine ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + static typename std::enable_if<details::is_uniform_real<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist<T>{ from, to }( engine ); + return RealDist<T>{ to, from }( engine ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + static typename std::enable_if<details::is_byte<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional<std::is_signed<T>::value, + short, unsigned short>::type; + + return static_cast<T>( get<short_t>( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type<Unsigned, Signed> chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type<A, B>::type + > + static typename std::enable_if< + std::is_same<Key, common>::value + && details::is_supported_number<A>::value + && details::is_supported_number<B>::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed<A>::value != std::is_unsigned<B>::value + , C>::type get( A from = std::numeric_limits<A>::min( ), + B to = std::numeric_limits<B>::max( ) ) { + return get( static_cast<C>( from ), static_cast<C>( to ) ); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template<typename T> + static typename std::enable_if<std::is_same<T, bool>::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template<typename T> + static T get( std::initializer_list<T> init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template<typename InputIt> + static typename std::enable_if<details::is_iterator<InputIt>::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0u == size ) return last; + using diff_t = typename std::iterator_traits<InputIt>::difference_type; + return std::next( first, get<diff_t>( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template<typename Container> + static typename std::enable_if<details::is_iterator< + decltype( std::begin( std::declval<Container>( ) ) )>::value + , decltype( std::begin( std::declval<Container>( ) ) ) + >::type get( Container& container ) { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template<typename Dist, typename... Args> + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward<Args>( args )... }( engine ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template<typename Dist> + static typename Dist::result_type get( Dist& dist ) { + return dist( engine ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template<typename RandomIt> + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template<typename Container> + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine; + } + protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine( ) { + // Make seeder instance for seed return by reference like std::seed_seq + Seeder seeder; + return Engine{ seeder( ) }; + } + protected: + /// The random number engine + static Engine engine; + }; + + /// Seed random number engine by Seeder + template< + typename Engine, + typename Seeder, + template<typename> class IntegerDist, + template<typename> class RealDist, + typename BoolDist + > + Engine basic_random_static<Engine, Seeder, IntegerDist, RealDist, BoolDist + // VS2017 issue, can't init by Seeder from lambda + >::engine( make_seeded_engine( ) ); + + /** + * \brief Base template class for random + * with thread_local API and thread_local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template<typename> class IntegerDist = std::uniform_int_distribution, + template<typename> class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_thread_local { + public: + basic_random_thread_local( ) = delete; + + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template<typename T> + using integer_dist_t = IntegerDist<T>; + + /// Type of used real distribution + template<typename T> + using real_dist_t = RealDist<T>; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + static void discard( const unsigned long long z ) { + engine.discard( z ); + } + + /// Reseed by Seeder + static void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + static void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine.seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template<typename Sseq> + static void seed( Sseq& seq ) { + engine.seed( seq ); + } + + /// return random number from engine in [min(), max()] range + static typename Engine::result_type get( ) { + return engine( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + static bool is_equal( const Engine& other ) { + return engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template<typename CharT, typename Traits> + static void serialize( std::basic_ostream<CharT, Traits>& ost ) { + ost << engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template<typename CharT, typename Traits> + static void deserialize( std::basic_istream<CharT, Traits>& ist ) { + ist >> engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + static typename std::enable_if<details::is_uniform_int<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist<T>{ from, to }( engine ); + return IntegerDist<T>{ to, from }( engine ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + static typename std::enable_if<details::is_uniform_real<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist<T>{ from, to }( engine ); + return RealDist<T>{ to, from }( engine ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + static typename std::enable_if<details::is_byte<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional<std::is_signed<T>::value, + short, unsigned short>::type; + + return static_cast<T>( get<short_t>( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type<Unsigned, Signed> chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type<A, B>::type + > + static typename std::enable_if< + std::is_same<Key, common>::value + && details::is_supported_number<A>::value + && details::is_supported_number<B>::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed<A>::value != std::is_unsigned<B>::value + , C>::type get( A from = std::numeric_limits<A>::min( ), + B to = std::numeric_limits<B>::max( ) ) { + return get( static_cast<C>( from ), static_cast<C>( to ) ); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template<typename T> + static typename std::enable_if<std::is_same<T, bool>::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template<typename T> + static T get( std::initializer_list<T> init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template<typename InputIt> + static typename std::enable_if<details::is_iterator<InputIt>::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0u == size ) return last; + using diff_t = typename std::iterator_traits<InputIt>::difference_type; + return std::next( first, get<diff_t>( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template<typename Container> + static typename std::enable_if<details::is_iterator< + decltype( std::begin( std::declval<Container>( ) ) )>::value + , decltype( std::begin( std::declval<Container>( ) ) ) + >::type get( Container& container ) { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template<typename Dist, typename... Args> + static typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward<Args>( args )... }( engine ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template<typename Dist> + static typename Dist::result_type get( Dist& dist ) { + return dist( engine ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template<typename RandomIt> + static void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template<typename Container> + static void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + static Engine get_engine( ) { + return engine; + } + protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine( ) { + // Make seeder instance for seed return by reference like std::seed_seq + Seeder seeder; + return Engine{ seeder( ) }; + } + protected: + /// The random number engine + static thread_local Engine engine; + }; + + /// Seed random number engine by Seeder + template< + typename Engine, + typename Seeder, + template<typename> class IntegerDist, + template<typename> class RealDist, + typename BoolDist + > + thread_local Engine basic_random_thread_local<Engine, Seeder, IntegerDist, RealDist, BoolDist + // VS2017 issue, can't init by Seeder from lambda + >::engine( make_seeded_engine( ) ); + + /** + * \brief Base template class for random + * with local API and local internal member storage + * \note it IS thread safe but less efficient then + * basic_random_static + * \param Engine A random engine with interface like in the std::mt19937 + * \param Seeder A seeder type which return seed for internal engine + * through operator() + */ + template< + typename Engine, + typename Seeder = seeder_default, + template<typename> class IntegerDist = std::uniform_int_distribution, + template<typename> class RealDist = std::uniform_real_distribution, + typename BoolDist = std::bernoulli_distribution + > + class basic_random_local { + public: + /// Type of used random number engine + using engine_type = Engine; + + /// Type of used random number seeder + using seeder_type = Seeder; + + /// Type of used integer distribution + template<typename T> + using integer_dist_t = IntegerDist<T>; + + /// Type of used real distribution + template<typename T> + using real_dist_t = RealDist<T>; + + /// Type of used bool distribution + using bool_dist_t = BoolDist; + + /// Key type for getting common type numbers or objects + using common = details::common; + + /** + * \return The minimum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type min( ) { + return Engine::min( ); + } + + /** + * \return The maximum value + * potentially generated by the random-number engine + */ + static constexpr typename Engine::result_type max( ) { + return Engine::max( ); + } + + /// Advances the internal state by z times + void discard( const unsigned long long z ) { + engine.discard( z ); + } + + /// Reseed by Seeder + void reseed( ) { + Seeder seeder; + seed( seeder( ) ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param value The seed value to use + * in the initialization of the internal state + */ + void seed( const typename Engine::result_type value = + Engine::default_seed ) { + engine.seed( value ); + } + + /** + * \brief Reinitializes the internal state + * of the random-number engine using new seed value + * \param seq The seed sequence + * to use in the initialization of the internal state + */ + template<typename Sseq> + void seed( Sseq& seq ) { + engine.seed( seq ); + } + + /// return random number from engine in [min(), max()] range + typename Engine::result_type get( ) { + return engine( ); + } + + /** + * \brief Compares internal pseudo-random number engine + * with 'other' pseudo-random number engine. + * Two engines are equal, if their internal states + * are equivalent, that is, if they would generate + * equivalent values for any number of calls of operator() + * \param other The engine, with which the internal engine will be compared + * \return true, if other and internal engine are equal + */ + bool is_equal( const Engine& other ) { + return engine == other; + } + + /** + * \brief Serializes the internal state of the + * internal pseudo-random number engine as a sequence + * of decimal numbers separated by one or more spaces, + * and inserts it to the stream ost. The fill character + * and the formatting flags of the stream are + * ignored and unaffected. + * \param ost The output stream to insert the data to + */ + template<typename CharT, typename Traits> + void serialize( std::basic_ostream<CharT, Traits>& ost ) { + ost << engine; + } + + /** + * \brief Restores the internal state of the + * internal pseudo-random number engine from + * the serialized representation, which + * was created by an earlier call to 'serialize' + * using a stream with the same imbued locale and + * the same CharT and Traits. + * If the input cannot be deserialized, + * internal engine is left unchanged and failbit is raised on ist + * \param ost The input stream to extract the data from + */ + template<typename CharT, typename Traits> + void deserialize( std::basic_istream<CharT, Traits>& ist ) { + ist >> engine; + } + + /** + * \brief Generate a random integer number in a [from; to] range + * by std::uniform_int_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random integer number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + typename std::enable_if<details::is_uniform_int<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + if( from < to ) // Allow range from higher to lower + return IntegerDist<T>{ from, to }( engine ); + return IntegerDist<T>{ to, from }( engine ); + } + + /** + * \brief Generate a random real number in a [from; to] range + * by std::uniform_real_distribution + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random real number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + typename std::enable_if<details::is_uniform_real<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + if( from < to ) // Allow range from higher to lower + return RealDist<T>{ from, to }( engine ); + return RealDist<T>{ to, from }( engine ); + } + + /** + * \brief Generate a random byte number in a [from; to] range + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random byte number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Prevent implicit type conversion + */ + template<typename T> + typename std::enable_if<details::is_byte<T>::value + , T>::type get( T from = std::numeric_limits<T>::min( ), + T to = std::numeric_limits<T>::max( ) ) { + // Choose between short and unsigned short for byte conversion + using short_t = typename std::conditional<std::is_signed<T>::value, + short, unsigned short>::type; + + return static_cast<T>( get<short_t>( from, to ) ); + } + + /** + * \brief Generate a random common_type number in a [from; to] range + * \param Key The Key type for this version of 'get' method + * Type should be '(THIS_TYPE)::common' struct + * \param from The first limit number of a random range + * \param to The second limit number of a random range + * \return A random common_type number in a [from; to] range + * \note Allow both: 'from' <= 'to' and 'from' >= 'to' + * \note Allow implicit type conversion + * \note Prevent implicit type conversion from singed to unsigned types + * Why? std::common_type<Unsigned, Signed> chooses unsigned value, + * then Signed value will be converted to Unsigned value + * which gives us a wrong range for random values. + * https://stackoverflow.com/a/5416498/5734836 + */ + template< + typename Key, + typename A, + typename B, + typename C = typename std::common_type<A, B>::type + > + typename std::enable_if< + std::is_same<Key, common>::value + && details::is_supported_number<A>::value + && details::is_supported_number<B>::value + // Prevent implicit type conversion from singed to unsigned types + && std::is_signed<A>::value != std::is_unsigned<B>::value + , C>::type get( A from = std::numeric_limits<A>::min( ), + B to = std::numeric_limits<B>::max( ) ) { + return get( static_cast<C>( from ), static_cast<C>( to ) ); + } + + /** + * \brief Generate a bool value with specific probability + * by std::bernoulli_distribution + * \param probability The probability of generating true in [0; 1] range + * 0 means always false, 1 means always true + * \return 'true' with 'probability' probability ('false' otherwise) + */ + template<typename T> + typename std::enable_if<std::is_same<T, bool>::value + , bool>::type get( const double probability = 0.5 ) { + assert( 0 <= probability && 1 >= probability ); // out of [0; 1] range + return BoolDist{ probability }( engine ); + } + + /** + * \brief Return random value from initilizer_list + * \param init_list initilizer_list with values + * \return Random value from initilizer_list + * \note Should be 1 or more elements in initilizer_list + * \note Warning! Elements in initilizer_list can't be moved: + * https://stackoverflow.com/a/8193157/5734836 + */ + template<typename T> + T get( std::initializer_list<T> init_list ) { + assert( 0u != init_list.size( ) ); + return *get( init_list.begin( ), init_list.end( ) ); + } + + /** + * \brief Return random iterator from iterator range + * \param first, last - the range of elements + * \return Random iterator from [first, last) range + * \note If first == last, return last + */ + template<typename InputIt> + typename std::enable_if<details::is_iterator<InputIt>::value + , InputIt>::type get( InputIt first, InputIt last ) { + const auto size = std::distance( first, last ); + if( 0u == size ) return last; + using diff_t = typename std::iterator_traits<InputIt>::difference_type; + return std::next( first, get<diff_t>( 0, size - 1 ) ); + } + + /** + * \brief Return random iterator from Container + * \param container The container with elements + * \return Random iterator from container + * \note If container is empty return std::end( container ) iterator + */ + template<typename Container> + typename std::enable_if<details::is_iterator< + decltype( std::begin( std::declval<Container>( ) ) )>::value + , decltype( std::begin( std::declval<Container>( ) ) ) + >::type get( Container& container ) { + return get( std::begin( container ), std::end( container ) ); + } + + /** + * \brief Return value from custom Dist distribution + * seeded by internal random engine + * \param Dist The type of custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom distribution + */ + template<typename Dist, typename... Args> + typename Dist::result_type get( Args&&... args ) { + return Dist{ std::forward<Args>( args )... }( engine ); + } + + /** + * \brief Return value from custom 'dist' distribution + * seeded by internal random engine + * \param dist The custom distribution with next concept: + * http://en.cppreference.com/w/cpp/concept/RandomNumberDistribution + * \param args The arguments which will be forwarded to Dist constructor + * \return Value from custom 'dist' distribution + */ + template<typename Dist> + typename Dist::result_type get( Dist& dist ) { + return dist( engine ); + } + + /** + * \brief Reorders the elements in the given range [first, last) + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param first, last - the range of elements to shuffle randomly + */ + template<typename RandomIt> + void shuffle( RandomIt first, RandomIt last ) { + std::shuffle( first, last, engine ); + } + + /** + * \brief Reorders the elements in the given container + * such that each possible permutation of those elements + * has equal probability of appearance. + * \param container - the container with elements to shuffle randomly + */ + template<typename Container> + void shuffle( Container& container ) { + shuffle( std::begin( container ), std::end( container ) ); + } + + /// return internal engine by copy + Engine get_engine( ) const { + return engine; + } + protected: + /// return engine seeded by Seeder + static Engine make_seeded_engine( ) { + // Make seeder instance for seed return by reference like std::seed_seq + Seeder seeder; + return Engine{ seeder( ) }; + } + protected: + /// The random number engine + Engine engine{ make_seeded_engine( ) }; + }; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with static storage + * \note Not thread safe but more prefomance + */ + using random_static = basic_random_static<std::mt19937>; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses static methods API and data with thread_local storage + * \note Thread safe but less perfomance + */ + using random_thread_local = basic_random_thread_local<std::mt19937>; + + /** + * \brief The basic static random alias based on a std::mt19937 + * \note It uses non static methods API and data with auto storage + * \note Not thread safe. Should construct on the stack at local scope + */ + using random_local = basic_random_local<std::mt19937>; + +} // namespace effolkronium + +#endif // #ifndef EFFOLKRONIUM_RANDOM_HPP
