5

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

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

生成随机枚举

但是,那个:

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

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

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

  2. 不提供 的功能std::uniform_distribution<>,即它不适用于您传递给它的随机引擎等等。

std::uniform_int_distribution<Color>显然,如果仅出于上述原因 1,我不能使用。我应该怎么做?

笔记:

  • 代码必须是通用的,即枚举类型是模板参数。
  • 由于我可能需要对粗略枚举进行一些检测,因此您可能会认为我拥有它;只需明确说明您的假设即可。
  • 具体来说,如果它有帮助,假设我使用 Better Enums,让我完全装扮成所有的花里胡哨。
  • 如果有一种不涉及任何此类仪器的惯用方法,那将是一个很好的答案,但我对此表示怀疑。
  • 仅 C++11/14 的解决方案是可以接受的。
  • 具有相同值的多个枚举标识符的频率不会加倍,它们只是彼此的别名。如果您有一个假设这些不存在的简单解决方案,那也将是相关的,尽管不是最理想的。
4

4 回答 4

4

使用Better Enums,可以通过以下方式解决此问题:

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
于 2016-08-15T14:13:59.767 回答
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)]; }
};

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

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

例子

于 2016-08-15T14:30:13.610 回答
1

我会说更惯用的是创建一个数组并从数组中选择索引:

 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似乎允许不手动创建数组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)];
 }
于 2016-08-15T14:45:02.483 回答
0

您链接到的问题中,假设您希望在枚举值上均匀分布。

但是,“枚举类型上的均匀分布”也可能意味着枚举范围内的均匀分布,这通常意味着实现选择的基础类型的所有可能值。

还有其他基本问题:

在你展示的情况下

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

据推测,您想要的均匀分布是从 0 到 7(每个枚举数可能使用位掩码 OR'd 一起)。

假设枚举是:

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

那么大概你只想要 1, 2, 3 在你的分布中。

我认为你不能指望编译器或任何模板代码理解你的意图——任何“枚举->统一分布”代码都需要提示,以便它知道哪些枚举数应该与其他枚举数的组合以及哪些只是选项。

简而言之,我认为您应该完全按照您链接的问题所做的事情,并在int's 或其他内容上生成适当的分布,然后static_cast将其发送到枚举。并且不要尝试使用一些模板解决方案来尝试阅读您对每个可能的枚举的想法。

于 2016-08-15T14:07:56.257 回答