表示在任意枚举类型范围内的均匀分布

ein*_*ica 5 c++ random enums c++11 uniform-distribution

我在很多地方使用C++随机数实用程序库.它可能不是很舒服(例如,没有任意分布的基类),但是 - 我已经学会了忍受它.

现在我碰巧需要从枚举类型中统一采样值.我知道,关于SO的问题已经存在:

生成随机枚举

然而,那一个:

  1. 假设所有枚举值都是连续的,即它不起作用

    enum Color { Red = 1, Green = 2, Blue = 4 }
    
    Run Code Online (Sandbox Code Playgroud)

    我们希望以1/3的概率对这三个值中的每一个进行采样.

  2. 不提供功能std::uniform_distribution<>,即它不能与您传递它的随机引擎一起使用等等.

显然我不能使用std::uniform_int_distribution<Color>,如果只是因为上面的原因1.我该怎么做呢?

笔记:

  • 代码必须是通用的,即枚举类型将是模板参数.
  • 因为我可能需要一些仪器而不仅仅是粗糙的枚举,你可能会认为我拥有它; 只是明确陈述你的假设.
  • 具体来说,如果它有所帮助,假设我使用了 更好的Enums,让我充满了所有的花里胡哨.
  • 如果某种惯用的方式不做任何这样的仪器,那将会有一个很好的答案,但我对此表示怀疑.
  • 仅C++ 11/14解决方案是可以接受的.
  • 具有相同值的多个枚举标识符不会使频率加倍,它们只是彼此的别名.如果你有一个简单的解决方案,假设这些不存在,这也是相关的,虽然不是最理想的.

eca*_*mur 2

以下是分布的三种实现,按复杂性升序排列:

首先,如果我们可以依赖不同的值或者可以接受重复值超重,我们可以只索引容器_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)]; }
};
Run Code Online (Sandbox Code Playgroud)

否则,我们可以使用拒绝采样,预先计算枚举值范围的最小值和最大值:

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

如果这效率低下(也许枚举值稀疏),我们可以在初始化时计算一个查找表:

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)]);
    }
};
Run Code Online (Sandbox Code Playgroud)

例子