Написание фабричного метода для генераторов случайных чисел STL

Я пытаюсь предоставить интерфейс — через файл конфигурации — для того, чтобы мои пользователи могли выбирать дистрибутив для некоторых параметров, которые они используют. Для этой цели я хотел бы использовать алгоритмы генератора случайных чисел STL.

Предположим, что моя программа читает JSON из командной строки. Для приведенного ниже JSON программа должна понимать, что она должна генерировать случайное число из нормального распределения с заданным средним значением и стандартной вариацией. (Я использую те же имена параметров, что и библиотека STL для очистки.)

{
    "dist": "normal_distribution",
    "mean": 0.1,
    "stddev": 0.5
}

Пока что я могу легко разобрать JSON и использовать param_type каждого дистрибутива для инициализации дистрибутива. Я использую имя, чтобы решить, какой дистрибутив выбрать param_type и дистрибутив.

Чего я не знаю, так это как это красиво реализовать. Я знаю, что я должен предоставить какой-то фабричный метод для этого, передать JSON и выплюнуть функцию или класс. Если я хочу вернуть экземпляр класса, скажем, unique_ptr генератора, мне нужно определить абстрактный класс, например, RandDist, и написать какой-то адаптер для включения моего ввода, .... Обычно я этого не делаю. нужно много от класса, достаточно только метода gen().

Мне интересно, есть ли у кого мысли по этому поводу. Или, если кто-нибудь знает библиотеку, которая может это сделать.

P.S. Входные данные не обязательно должны быть объектом JSON, любая хэш-таблица будет работать сама по себе.

Мне кажется, вы довольно ясно представляете, в каком направлении вам следует двигаться. В качестве пищи для размышлений интерфейс с одной виртуальной функцией можно рассматривать как функтор, поэтому здесь может пригодиться функция стирания типов std::function.

Quentin 29.05.2019 13:56

@Quentin Я думаю, что мне не очень нравится мой подход, так это количество адаптеров, которые мне нужно написать для всего алгоритма. Я почему-то надеюсь, что есть более приятный способ добиться этого.

Amir 29.05.2019 14:00
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
192
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы описали довольно стандартный способ справиться с этой ситуацией — иметь абстрактный RandomGenerator класс всего с одним виртуальным методом gen().

Затем у него будут реализации, такие как NormalDistributionGenerator, UniformDistributionGenerator и т. д., с конструкторами, принимающими соответствующий набор параметров распределения и инициализирующими элементы STL в качестве членов. Эти конкретные классы будут использоваться непосредственно только в процедуре создания генератора и использоваться в других местах как абстрактные RandomGenerator.

Итак, процедура создания будет выглядеть примерно так

std::unique_ptr<RandomGenerator> CreateRandomGenerator(const Info& info) {
    switch (info.type) {
    case Type::Normal:
        return std::make_unique<NormalDistributionGenerator>(info.mean(), info.stddev());
    case Type::Uniform:
        return std::make_unique<UniformDistributionGenerator>(info.a(), info.b());
    // ...
    }
}

Info - это класс, который содержит информацию о распространении (некоторая оболочка JSON, map/hash_table - что лучше работает в вашем случае).

Таким образом, вам определенно нужно будет написать шаблонный код немного, чтобы он заработал, но это сделает использование вашего RandomGenerator простым и понятным, а добавление новых типов дистрибутивов будет достаточно простым и потребует модификации кода только в одном месте - фабричный метод.

Ответ принят как подходящий

Я постарался свести шаблон к минимуму. Предположения:

  • Вы заранее знаете тип вашего генератора (это легко переключается, если вам нужно, чтобы ваши генераторы также были динамическими)

  • Все дистрибутивы генерируют doubles (это в значительной степени встроено, поскольку API должен возвращать бетон что-то, чтобы его можно было прилично использовать)

  • Все дистрибутивы создаются из параметров double (это также можно настроить с помощью прокси-объекта, но в зависимости от вашей фактической библиотеки JSON работа может быть уже выполнена там)

  • Я использовал расширение препроцессора GCC для обработки случая с нулевым параметром, но макрос, безусловно, можно переписать, чтобы он не нуждался в нем.

using Generator = std::mt19937;
using Distribution = std::function<double(Generator &)>;
using Json = std::map<std::string, std::string>;

template <class DistributionType, class... Parameters>
Distribution make_distribution_impl(Json const &json, Parameters... parameters) {
    return DistributionType{std::stod(json.at(parameters))...};
}

Distribution make_distribution(Json const &json) {
    auto const &distributionName = json.at("dist");

    #define generate_distribution_factory(name_, ...) \
        if (distributionName == #name_) \
            return make_distribution_impl<std::name_<double>>(json, ## __VA_ARGS__)

    generate_distribution_factory(uniform_real_distribution, "a", "b");
    generate_distribution_factory(normal_distribution, "mean", "stddev");
    // ...

    #undef generate_distribution_factory

    throw std::runtime_error{"Unknown distribution " + distributionName};
}

Смотрите в прямом эфире на Wandbox

Это выглядит потрясающе! Я думаю, что это то, что я искал, но я должен признать, что у меня есть проблема с пониманием этого. Я думаю, что по большей части я понимаю логику этого, но у меня есть вопрос. Некоторые дистрибутивы, например std::piecewise_linear_distribution, используют контейнер в качестве параметров, интересно, как вы справляетесь с этим в своем подходе. Я использую nlohmann-json, чтобы справиться с этим. Моя идея состояла в том, чтобы написать парсер для param_type каждого дистрибутива и прочитать параметры, а затем передать их. Но я не понимаю, как это сделать здесь. Спасибо :-)

Amir 29.05.2019 14:57

Я думаю, что удаление std::stod подойдет, так как json-библиотека nlohmann использует тот же синтаксис и выполняет преобразование в типы stl, ... но я спрашиваю, так как я не полностью понимаю код для части generate_dis.....

Amir 29.05.2019 15:04

@ Амир ... черт возьми! То, что вы используете nlohmann-json, — это отличная новость, так как в нем настроена автоматическая конвертация — вы можете просто удалить std::stod() из make_distribution_impl, и в простых случаях сработают волшебные преобразования. std::piecewise_linear_distribution это своего рода рутинная работа, так как для этого нужны итераторы... Самое простое решение — просто не объявлять его через макрос, а добавить отдельную ветку для его обработки внутри make_distribution.

Quentin 29.05.2019 15:04

Я слежу, но не совсем честно. Я понимаю, что делает макрос, и понимаю, как его расширить, но я не знаю, что мне нужно для поддержки внешнего std::piecewise_linear_distribution :\

Amir 29.05.2019 15:08

Должно быть не более if (distributionName == "piecewise_linear_distribution ") { /* read in values, handle errors */ return std::piecewise_linear_distribution<double>{ /* what you just parsed */ }; }

Quentin 29.05.2019 15:09

Я попробую посмотреть, смогу ли я заставить его работать :-) Думаю, я вижу, что мне следует делать. Большое спасибо...

Amir 29.05.2019 15:11

Я думаю, что понял, но не буду врать, у меня голова болит после прочтения и понимания вашего кода. Для меня это был другой уровень :D Но теперь у меня есть причина изучать эти вещи больше.

Amir 29.05.2019 15:37

Мне удалось получить все раздачи, кроме bernoulli_distribution и discrete_distribution! Я изменил макрос на этот, #define generate_distribution_factory(name_, type_, ...)\ if (distributionName == #name_) return make_distribution_impl<std::name_<type_>>(j, ## __VA_ARGS__); чтобы я мог указать тип и быть более гибким. Однако по какой-то причине это не помогает bool! Интересно, есть ли у вас какие-либо представления об этом?

Amir 31.05.2019 14:12

Ах, bernoulli_distribution - единственная раздача, которая не является шаблонной... Что за тур! Я заставил это работать, подняв пару <> в аргумент макроса: #define generate_distribution_factory(name_, suffix_, ...) if (distributionName == #name_) return make_distribution_impl<std::name_ suffix_>(json, ## __VA_ARGS__), называемый generate_distribution_factory(bernoulli_distribution, /* empty */, "p");; но вы также можете просто указать это в особом случае. Я не уверен, как вы застряли с discrete_distribution и о каком bool вы говорите.

Quentin 31.05.2019 15:50

Другие вопросы по теме