Representation of uniform distribution over a range of arbitrary type enum

I use the C ++ random number utility library in several places. It may not be very convenient (for example, there is no base class for arbitrary distribution), but - I learned to live with it.

Now I need to evenly select values ​​from the listed type. I know there is another question about what on SO already:

random listing generation

however, that one:

  • Assumes all enumeration values ​​are contiguous, i.e. will not work for

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

    where we want each of these three values ​​to be selected with a probability of 1/3.

  • It std::uniform_distribution<> not provide the function std::uniform_distribution<> , that is, it does not work with the random engine that you pass to it, etc.

Obviously, I cannot use std::uniform_int_distribution<Color> , unless for reason 1 above. What should I do instead?

Notes:

  • The code must be shared, i.e. the enumeration type will be the template parameter.
  • Since, most likely, I will need some kind of equipment for the whole rough listing, you can assume that I have it; just state your assumption explicitly.
  • In particular, and if that helps, suppose I use Better Enums , which makes me completely decorated with all the bells and whistles.
  • If there is any idiomatic way to do this without involving any such tools, this may serve as an excellent answer, but I doubt it.
  • C ++ 11/14 solutions are acceptable.
  • Several enumeration identifiers with the same value do not receive double the frequency, they are simply aliases of each other. If you have a simple solution that assumes that they do not exist, this will also be relevant, although not optimal.
+5
source share
4 answers

Here are three implementations of the distribution in increasing order of complexity:

First, if we can rely on values ​​that are different from each other, or everything is fine when the repetition values ​​are overweight, we can simply index the _values() container:

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

Otherwise, we can use the culling selection, pre-calculating the min and max range of the enumeration values:

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

If this is ineffective (perhaps the enumeration values ​​are sparse), we can calculate the lookup table during initialization:

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

An example .

+2
source

Using Better Enums , this problem can be solved as follows:

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

Usage example:

 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
source

I would say that creating an array and selecting an index from an array would be more idiomatic:

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

Better Enums doesn't seem to allow you to create an array manually using 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
source

The question that you linked to is assumed that you want an even distribution over the values ​​of the enumerator.

However, "uniform distribution over the enum type" can also mean a uniform distribution over the enumeration range, which usually means all possible values ​​of the base type that were chosen by the implementation.

There are other fundamental problems:

In the event that you showed

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

Presumably, the uniform distribution you want is between 0 and 7 (each enumerator is possible OR'd along with using a bitmask).

Assume the enumeration:

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

Then, presumably, you only want 1, 2, 3 in your distribution.

I think you cannot expect the compiler or any code template to understand your intention - any "enum β†’ uniform distribution" code will require hints so that it knows which counters should be with d + and which ones are just options.

In short, I think you should do exactly what your question is related to and create the appropriate distribution by int or something else, and then static_cast to list. And do not try to use any template solution that your mind tries to read for all possible enumerations.

0
source

All Articles