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

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

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

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

однако, этот:

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

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

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

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

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

Заметки:

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

5

Решение

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

Во-первых, если мы можем полагаться на то, что значения различны или все в порядке с переоценкой значений повтора, мы можем просто проиндексировать _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)]; }
};

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

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)]);
}
};

пример.

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
4

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

 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)];
}
1

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

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

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

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

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 это к перечислению. И не пытайтесь использовать какое-либо шаблонное решение, которое пытается прочитать ваши мысли для каждого возможного перечисления.

0
По вопросам рекламы [email protected]