Хобрук: Ваш путь к мастерству в программировании

Представление равномерного распределения по диапазону произвольного типа перечисления

Я использую библиотеку случайных чисел C++ во многих местах. Это может быть не совсем удобно (например, нет базового класса для произвольного дистрибутива), но я научился с этим жить.

Теперь мне нужно равномерно выбирать значения из перечисляемого типа. Я знаю, на SO уже есть вопрос:

генерация случайных перечислений

однако тот:

  1. Предполагается, что все значения перечисления являются смежными, т.е. это не будет работать для

    enum Color { Red = 1, Green = 2, Blue = 4 }
    

    где мы хотим, чтобы каждое из этих трех значений было выбрано с вероятностью 1/3.

  2. Не обеспечивает функциональность std::uniform_distribution<>, т.е. не работает со случайным движком, который вы передаете, и так далее.

Очевидно, я не могу использовать std::uniform_int_distribution<Color> хотя бы по причине 1 выше. Что мне делать вместо этого?

Примечания:

  • Код должен быть универсальным, т. е. тип перечисления будет параметром шаблона.
  • Поскольку, скорее всего, мне понадобится некоторая инструментация только для грубого перечисления, вы можете предположить, что она у меня есть; просто сформулируйте свое предположение явно.
  • В частности, и если это поможет, предположим, что я использую лучшие перечисления, благодаря чему я полностью вооружен всеми колокола и свистки.
  • Если есть какой-то идиоматический способ сделать это без использования каких-либо таких инструментов, это было бы отличным ответом, но я сомневаюсь в этом.
  • Допускаются решения только для C++11/14.
  • Несколько идентификаторов перечисления с одинаковым значением не удваивают частоту, они просто являются псевдонимами друг друга. Если у вас есть простое решение, предполагающее, что их не существует, это также будет актуально, хотя и неоптимально.

  • Идиоматика и использование сторонней рефлексивной библиотеки являются взаимоисключающими. Что насчёт библиотеки доставляет тебе неприятности? Это даже дает вам пример итерации по нему с помощью диапазона for. 15.08.2016
  • @uhohsomebodyneedsapupper: Семантически ты прав. Библиотека вообще не доставляет мне хлопот. 15.08.2016
  • Вы можете удалить эти значения, позволить компилятору установить значения по умолчанию (начиная с 0) и инициализировать вспомогательный map<Color,int> этими значениями, которые затем можно будет использовать вместе с std::uniform_distribution<>. 15.08.2016
  • Если два объявленных перечисления имеют одинаковое значение, хотите ли вы, чтобы распределение соответствующим образом взвешивало их распределение? например enum Color { White, Black, Gray, Grey = Gray }? 15.08.2016
  • Так.. в чем тогда проблема? Вы уже знаете, что в C++ нет отражения, поэтому вы используете библиотеку отражения. И есть масса вопросов по перебору перечислений на SO с использованием стандартного C++. Выбор допустимого случайного значения перечисления в общий способ может помочь. 15.08.2016
  • @ecatmur: верное замечание, см. редактирование. 15.08.2016
  • @uhohsomebodyneedsapupper: Идиоматика и использование сторонней рефлексивной библиотеки исключают друг друга. Чепуха. идиоматика не означает то, что исходит из стандарта С++. Boost изобрел тонны идиом самостоятельно. И они все еще делают. 15.08.2016
  • @NicolBolas Я не понимал, что отражение в C ++ идиоматично. Мои извинения. 15.08.2016
  • @NicolBolas: Что ж, использование сторонней не очень популярной библиотеки enum-reflection само по себе не идиоматично в C ++, поэтому ничто на ее основе не может претендовать на идиоматику. Я мог бы сказать, что ищу условное идиоматическое решение... 15.08.2016
  • @einpoklum: я думаю, что идиоматического решения не существует, создание дистрибутива - это просто тонкая и сложная вещь, и вы должны сделать это явно, именно так, как вы хотите, для максимальной ясности. Я не знаю ни одного языка программирования, который пытался бы определить идиому для проблемы, которую вы поднимаете. 15.08.2016
  • @ChrisBeck: создание равномерного распределения по набору интегральных значений не является ни тонким, ни сложным с математической точки зрения; Я не понимаю, почему это должно быть легко возможно программно. 15.08.2016
  • @einpoklum: Возможно, вам действительно нужно en.cppreference.com/w/cpp /числовое/случайное/дискретное_распределение ? Извините, но я думаю, что любой код, который пытается вывести параметры распределения из перечисления, в лучшем случае будет утилитой для конкретного приложения, а в худшем — дерьмом. 15.08.2016
  • @ChrisBeck: Это интересная идея. Я не использовал std::discrete_distribution, нужно подумать. 15.08.2016

Ответы:


1

Вот три реализации дистрибутива в порядке возрастания сложности:

Во-первых, если мы можем полагаться на то, что значения различны, или если повторяющиеся значения имеют избыточный вес, мы можем просто проиндексировать контейнер _values():

template<class Enum>
struct SimpleEnumDistribution
{
    std::uniform_int_distribution<typename Enum::_integral> dist{0, Enum::_size() - 1};
    template<class Generator> Enum operator()(Generator& g) { return Enum::_values()[dist(g)]; }
};

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

template<class Enum>
struct UniformEnumDistribution
{
    std::uniform_int_distribution<typename Enum::_integral> dist{
        *std::min_element(Enum::_values().begin(), Enum::_values().end()),
        *std::max_element(Enum::_values().begin(), Enum::_values().end())};
    template<class Generator> Enum operator()(Generator& g)
    {
        for (;;)
            if (auto value = Enum::_from_integral_nothrow(dist(g)))
                return *value;
    }
};

Если это будет неэффективно (возможно, значения перечисления разрежены), мы можем вычислить таблицу поиска при инициализации:

template<class Enum>
struct FastUniformEnumDistribution
{
    std::uniform_int_distribution<std::size_t> dist;
    std::array<typename Enum::_integral, Enum::_size()> values;
    FastUniformEnumDistribution()
    {
        std::copy(Enum::_values().begin(), Enum::_values().end(), values.data());
        std::sort(values.begin(), values.end());
        dist.param(std::uniform_int_distribution<std::size_t>::param_type{0u, static_cast<std::size_t>(
            std::distance(values.begin(), std::unique(values.begin(), values.end())) - 1)});
    }
    template<class Generator> Enum operator()(Generator& g)
    {
        return Enum::_from_integral_unchecked(values[dist(g)]);
    }
};

Пример.

15.08.2016
  • Думаю, этого достаточно для меня. Хотя это должно быть усилено, чтобы соответствовать всем требованиям концепции RandomNumberDistribution. 17.08.2016

  • 2

    При использовании улучшенных перечислений эта проблема может быть решена следующим образом:

    template<typename T>
    typename T get_uniform_value(std::default_random_engine& eng)
    {
        std::uniform_int_distribution<int> dist(0, T::_size() - 1);
        return T::_values()[dist(eng)];
    }
    

    Пример использования:

    BETTER_ENUM(Channel, int, Red, Green = 2, Blue) // Enum to generate random values of
    ...
    std::default_random_engine rng(std::random_device{}());
    Channel r = get_uniform_value<Channel>(rng); // Uniformly distributed between 0, 2 and 3
    
    15.08.2016
  • Разве get_uniform_value не следует брать PRNG по ссылке, чтобы он действительно был изменен? 15.08.2016
  • @NathanOliver Да, так и должно быть. Исправлено 15.08.2016

  • 3

    Я бы сказал, что более идиоматично было бы создать массив и выбрать индекс из массива:

     template <typename Rnd>
     Color RandomColor(Rnd& rnd)
     {
         const std::array<Color, 3u> colors {Color::Red, Color::Green, Color::Blue};
    
         std::uniform_int_distribution<int> dist(0, colors.size() - 1);
         return colors[dist(rnd)];
     }
    

    Лучшие перечисления позволяют не создавать массив вручную с помощью Color::_values:

     template <typename BetterEnum, typename Rnd>
     BetterEnum RandomBetterEnum(Rnd& rnd)
     {
         std::uniform_int_distribution<int> dist(0, BetterEnum::_size() - 1);
         return BetterEnum::_values()[dist(rnd)];
     }
    
    15.08.2016
  • Если вы не можете обобщить свою реализацию, чтобы использовать тип Enum в качестве параметра шаблона, то этого недостаточно. 15.08.2016
  • Без дополнительной библиотеки, поскольку у нас нет отражения, вам нужно построить массив. Похоже, что используемая вами библиотека предоставляет эту функциональность, поэтому ее можно обобщить для перечисления, управляемого BetterEnum. 15.08.2016
  • @ Jarod42, конечно, можешь. SFINAE для возвращаемого типа с использованием typename std::enable_if<std::is_enum<BetterEnum>::value, BetterEnum>::type Чтобы быть более точным, вы можете использовать size_t вместо int, но это очень придирчиво. 15.08.2016

  • 4

    В вопросе, с которым вы связались, предполагается, что вы хотите равномерное распределение по перечислителю значения.

    Однако "равномерное распределение по перечислимому типу" может также означать равномерное распределение по диапазону перечисления, что обычно означает все возможные значения базового типа, выбранного реализацией.

    Есть и другие фундаментальные проблемы:

    В случае, который вы показали

    enum Color { Red = 1, Green = 2, Blue = 4 }
    

    Предположительно, равномерное распределение, которое вы хотите, составляет от 0 до 7 (каждый перечислитель возможен с помощью ИЛИ вместе с использованием битовых масок).

    Предположим, что перечисление было:

    enum Color { Red = 1, Green = 2, Blue = 3 }
    

    Тогда, по-видимому, вам нужны только 1, 2, 3 в вашем дистрибутиве.

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

    Короче говоря, я думаю, вы должны делать именно то, что делает вопрос, на который вы ссылаетесь, и генерировать соответствующее распределение по int или чему-то еще, а затем static_cast в перечисление. И не пытайтесь использовать какое-то шаблонное решение, которое пытается прочитать ваши мысли для каждого возможного перечисления.

    15.08.2016
  • В примере с Color неравномерное распределение должно быть 1 w.p. 1/3, 2 изн.п. 1/3, 3 изн.п. 1/3. Я сделаю это яснее. И - я, конечно, могу ожидать, что это будет понято, если я не просто определю свое перечисление таким образом, а использую более сложный механизм - см. примечания. 15.08.2016
  • Новые материалы

    Освоение информационного поиска: создание интеллектуальных поисковых систем (глава 1)
    Глава 1. Поиск по ключевым словам: основы информационного поиска Справочная глава: «Оценка моделей поиска информации: подробное руководство по показателям производительности » Глава 1: «Поиск..

    Фишинг — Упаковано и зашифровано
    Будучи старшим ИТ-специалистом в небольшой фирме, я могу делать много разных вещей. Одна из этих вещей: специалист по кибербезопасности. Мне нравится это делать, потому что в настоящее время я..

    ВЫ РЕГРЕСС ЭТО?
    Чтобы понять, когда использовать регрессионный анализ, мы должны сначала понять, что именно он делает. Вот простой ответ, который появляется, когда вы используете Google: Регрессионный..

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..

    LeetCode Проблема 41. Первый пропущенный положительный результат
    LeetCode Проблема 41. Первый пропущенный положительный результат Учитывая несортированный массив целых чисел, найдите наименьшее пропущенное положительное целое число. Пример 1: Input:..

    Расистский и сексистский робот, обученный в Интернете
    Его ИИ основан на предвзятых данных, которые создают предрассудки. Он словно переходит из одного эпизода в другой из серии Черное зеркало , а вместо этого представляет собой хронику..

    Управление состоянием в микрофронтендах
    Стратегии бесперебойного сотрудничества Микро-фронтенды — это быстро растущая тенденция в сфере фронтенда, гарантирующая, что удовольствие не ограничивается исключительно бэкэнд-системами..