Я использую библиотеку утилит случайных чисел C ++ во многих местах. Это может быть не совсем удобно (например, нет базового класса для произвольного распределения), но — я научился жить с этим.
Теперь мне нужно равномерно выбирать значения из перечислимого типа. Я знаю, что на SO уже есть вопрос:
однако, этот:
Предполагается, что все значения перечисления являются смежными, то есть это не будет работать для
enum Color { Red = 1, Green = 2, Blue = 4 }
где мы хотим, чтобы каждое из этих трех значений было выбрано с вероятностью 1/3.
std::uniform_distribution<>
то есть он не работает со случайным движком, вы его пропускаете и так далее.Очевидно, я не могу использовать std::uniform_int_distribution<Color>
Если только по причине 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)]; }
};
В противном случае мы можем использовать выборку отклонения, предварительно рассчитав минимальное и максимальное значения диапазона значений 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)]);
}
};
С использованием Лучшие перечисления, эта проблема может быть решена следующим образом:
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
Я бы сказал, что более идиоматичным будет создание массива и выбор индекса из массива:
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)];
}
В вопрос, с которым вы связаны, предполагается, что вы хотите равномерное распределение по значения перечислителя.
Однако «равномерное распределение по типу перечисления» может также означать, что равномерное распределение по спектр перечисления, что обычно означает все возможные значения базового типа, который был выбран реализацией.
Есть и другие фундаментальные проблемы:
В том случае, если вы показали
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
это к перечислению. И не пытайтесь использовать какое-либо шаблонное решение, которое пытается прочитать ваши мысли для каждого возможного перечисления.