В C++ Генерация Случайных Чисел


Я делаю несколько классов обертка для стандартной библиотеки C++, и было интересно, если следующий код является хорошей практикой.

#pragma once

#include <random>

namespace LibraryName
{
    class RandomBase
    {
    protected:
        RandomBase()
            : m_RandomGenerator(m_RandomDevice()) {}

        std::random_device m_RandomDevice;
        std::mt19937 m_RandomGenerator;
    };

    template<typename T>
    class IntRandom : public RandomBase
    {
    public:
        IntRandom(T minValue, T maxValue)
            : m_Distribution(minValue, maxValue), m_MinValue(minValue), m_MaxValue(maxValue) {}

        T Next()
        {
            return m_Distribution(m_RandomGenerator);
        }

        inline T GetMinValue() const { return m_MinValue; }
        inline T GetMaxValue() const { return m_MaxValue; }

    private:
        std::uniform_int_distribution<T> m_Distribution;
        T m_MinValue;
        T m_MaxValue;
    };

    template<typename T>
    class FloatRandom : public RandomBase
    {
    public:
        FloatRandom(T minValue, T maxValue)
            : m_Distribution(minValue, maxValue), m_MinValue(minValue), m_MaxValue(maxValue) {}

        T Next()
        {
            return m_Distribution(m_RandomGenerator);
        }

        inline T GetMinValue() const { return m_MinValue; }
        inline T GetMaxValue() const { return m_MaxValue; }

    private:
        std::uniform_real_distribution<T> m_Distribution;
        T m_MinValue;
        T m_MaxValue;
    };

    class BoolRandom : public RandomBase
    {
    public:
        BoolRandom() : BoolRandom(0.5f) {}
        BoolRandom(float trueChance)
            : m_Distribution(trueChance), m_TrueChance(trueChance) {}

        bool Next()
        {
            return m_Distribution(m_RandomGenerator);
        }

        inline bool GetTrueChance() const { return m_TrueChance; }

    private:
        std::bernoulli_distribution m_Distribution;
        float m_TrueChance;
    };
}


358
5
задан 8 марта 2018 в 09:03 Источник Поделиться
Комментарии
2 ответа

Это кажется много кода очень мало. Это позволит писать

IntRandom<int> r(17, 42);
int i = r.Next();

вместо нет-хелперов версия,

std::mt19937 g(std::random_device{}());
std::uniform_int_distribution<int> dist(17, 42);
int i = dist(g);

Некоторые люди говорят, что не стоит 77 строк кода.


Два мелких проблем с вашим кодом, связанная с использованием random_device: во-первых, вы используете только 32 бита энтропии на семя весь вихрь мерсена; было бы лучше, чтобы использовать столько битов, как mt19937 бит состояния. Однако, делать это правильно это излишне сложно в C++17, Так вы получите пропуск на это сейчас.

Во-вторых, ваш RandomBase класс содержит член типа random_device. Это может быть проблематичным, потому что random_device по сути дескриптор открытого файла в /dev/urandom. Если вы создаете много RandomBase объектов одновременно, вы могли бы найти себя заканчиваются файловые дескрипторы.

Ну, и третья проблема заключается в том, что random_device не копировать и не-движимости; так что ваш RandomBase также не-движимости. Что на самом деле может быть довольно плохо, в зависимости от того, как вы хотите его использовать.


Вы могли бы исправить random_device вопросы по удаление random_device данные-члены, и вместо этого предоставляет способ такой:

void RandomBase::reseed() {
std::random_device rd;
m_RandomGenerator.seed(rd()); // TODO: better seeding
}

Это держит random_device жив (а /dev/urandom открыть) только так долго, как это необходимо; и он удаляет данные-члены класса, так что класс становится подвижной и даже копировать.


Вы могли бы также сократить дублирование с помощью шаблонов и тип-псевдонимы, что-то вроде этого:

template<class Distribution>
class dist_and_gen {
std::mt19937 m_g;
Distribution m_dist;

template<class... Args>
explicit constexpr dist_and_gen(Args&&...) :
m_dist(std::forward<Args>(args)...) {}

auto next() { return m_dist(m_g); }
auto params() const { return m_dist.params(); }
};

template<class T> using IntRandom =
dist_and_gen<std::uniform_int_distribution<T>>;
template<class T> using FloatRandom =
dist_and_gen<std::uniform_float_distribution<T>>;
template<class T> using BoolRandom =
dist_and_gen<std::bernoulli_distribution<T>>;

Эта версия теряет некоторые детали, такие как пользовательские именами аксессоров параметров; но он сохраняет многие детали "случайно". Например, я не пыталась сохранить BoolRandom::BoolRandom() конструктор по умолчанию; но оказывается, что он просто работает в любом случае, благодаря конструктор по умолчанию для стандартной bernoulli_distribution.

11
ответ дан 8 марта 2018 в 10:03 Источник Поделиться

Я в принципе написал то же вспомогательные классы для работы. Преимуществом их является двоякой


  1. Это позволяет воспроизвести шумные моделирования путем определения семян

  2. Обычно генераторы случайных чисел весьма дорогостоящим в их инициализации, но дешево в возвращается следующее значение. Так что по поводу @Quuxplusone существует большая разница в производительности между

    IntRandom<int> r(17, 42);
    for (size_t i=0; i < something large; ++i)
    int i = r.Next();

    По сравнению с

    for (size_t i=0; i < something large; ++i){
    std::mt19937 g(std::random_device{}());
    std::uniform_int_distribution<int> dist(17, 42);
    int i = dist(g);
    }

    Также встраивания их в свой класс гораздо проще с помощью данного интерфейса.


Теперь для фактического обзора. Я думаю, что ваш базовый класс не хватает одной ключевой особенностью, и это прохождение семян. Также выполнения random_device не нужен, т. к. @Quuxplusone указывают, поэтому свести это к:

class RandomBase
{
protected:
RandomBase()
: RandomDevice(std::mt19937(std::random_device()))
{}

RandomBase(const double seed)
: RandomDevice(std::mt19937(seed))
{}
std::mt19937 m_RandomGenerator;
};

Относительно вашего IntDistribution я бы сказал, что это плохое имя и переусложненным. С одной стороны, есть функция-член std::uniform_int_distribution::max что делает ваш GetMaxValue делает. Кроме того, я действительно не вижу выгоды в его неоспоримым класс шаблона, так что я бы сократить его до:

class RandomUniformInt : public RandomBase {
public:
RandomUniformInt (int lower_bound, int upper_bound)
: dist(lower_bound, upper_bound)
{}
RandomUniformInt(int lower_bound, int upper_bound, double seed)
: RandomBase(seed)
, dist(lower_bound, upper_bound)
{}

int operator ()() { return dist(m_RandomGenerator); }
private:
std::uniform_int_distribution<> dist;
};

Что говорит, учитывая минимальное влияние RadomBase я бы на самом деле подставлять ее.

1
ответ дан 9 марта 2018 в 12:03 Источник Поделиться