表示任意枚举类型范围内的均匀分布
Representing a uniform distribution over the range of an arbitrary enum type
我在很多地方都使用了 C++ 随机数实用程序库。它可能不是很舒服(例如,没有基础 class 用于任意分布),但是 - 我已经学会了忍受它。
现在碰巧需要从一个枚举类型中统一采样值。我知道,SO 上已经有一个问题了:
generating random enums
然而,那个:
假设所有枚举值都是连续的,即它不适用于
enum Color { Red = 1, Green = 2, Blue = 4 }
我们希望以 1/3 的概率对这三个值中的每一个进行采样。
- 不提供
std::uniform_distribution<>
的功能,即它不适用于您传递给它的随机引擎等等。
显然我不能使用 std::uniform_int_distribution<Color>
,如果只是因为上面的原因 1。我应该怎么做?
备注:
- 代码必须是通用的,即枚举类型将是模板参数。
- 由于我很可能只需要对粗略的枚举进行一些检测,您可以假设我有;只需明确说明您的假设即可。
- 具体来说,如果它有帮助,假设我使用 Better Enums,让我完全装扮成所有的花里胡哨。
- 如果有某种不涉及任何此类仪器的惯用方法,那将是一个很好的答案,但我对此表示怀疑。
- 仅 C++11/14 的解决方案是可以接受的。
- 具有相同值的多个枚举标识符不会获得两倍的频率,它们只是彼此的别名。如果你有一个假设这些不存在的简单解决方案,那也将是相关的,尽管不是最优的。
在 the question you linked to 中,假定您希望在 枚举值 .
上均匀分布
但是,"uniform distribution over an enum-type" 也可能意味着在枚举的 范围 上均匀分布,这通常意味着所选基础类型的所有可能值通过实施。
还有其他基本问题:
在你展示的情况下
enum Color { Red = 1, Green = 2, Blue = 4 }
据推测,您想要的均匀分布是从 0 到 7(每个枚举器可能使用位掩码进行“或”运算)。
假设枚举是:
enum Color { Red = 1, Green = 2, Blue = 3 }
那么大概您只需要 1、2、3 在您的分配中。
我认为您不能期望编译器或任何模板代码理解您的意图——任何 "enum -> uniform distribution" 代码都需要提示,以便它知道哪些枚举器应该与其他枚举器组合哪些只是选项。
所以简而言之,我认为你应该完全按照你链接到的问题做,并在 int
或其他任何东西上生成适当的分布,然后 static_cast
它到枚举.并且不要尝试使用某些模板解决方案来尝试了解您对每个可能的枚举的想法。
使用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
这是分布的三个实现,按照复杂性升序排列:
首先,如果我们可以依赖不同的值或者可以接受超重的重复值,我们可以只索引 _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;
}
};
如果这样做效率低下(可能枚举值稀疏),我们可以在初始化时计算查找 table:
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 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)];
}
我在很多地方都使用了 C++ 随机数实用程序库。它可能不是很舒服(例如,没有基础 class 用于任意分布),但是 - 我已经学会了忍受它。
现在碰巧需要从一个枚举类型中统一采样值。我知道,SO 上已经有一个问题了:
generating random enums
然而,那个:
假设所有枚举值都是连续的,即它不适用于
enum Color { Red = 1, Green = 2, Blue = 4 }
我们希望以 1/3 的概率对这三个值中的每一个进行采样。
- 不提供
std::uniform_distribution<>
的功能,即它不适用于您传递给它的随机引擎等等。
显然我不能使用 std::uniform_int_distribution<Color>
,如果只是因为上面的原因 1。我应该怎么做?
备注:
- 代码必须是通用的,即枚举类型将是模板参数。
- 由于我很可能只需要对粗略的枚举进行一些检测,您可以假设我有;只需明确说明您的假设即可。
- 具体来说,如果它有帮助,假设我使用 Better Enums,让我完全装扮成所有的花里胡哨。
- 如果有某种不涉及任何此类仪器的惯用方法,那将是一个很好的答案,但我对此表示怀疑。
- 仅 C++11/14 的解决方案是可以接受的。
- 具有相同值的多个枚举标识符不会获得两倍的频率,它们只是彼此的别名。如果你有一个假设这些不存在的简单解决方案,那也将是相关的,尽管不是最优的。
在 the question you linked to 中,假定您希望在 枚举值 .
上均匀分布但是,"uniform distribution over an enum-type" 也可能意味着在枚举的 范围 上均匀分布,这通常意味着所选基础类型的所有可能值通过实施。
还有其他基本问题:
在你展示的情况下
enum Color { Red = 1, Green = 2, Blue = 4 }
据推测,您想要的均匀分布是从 0 到 7(每个枚举器可能使用位掩码进行“或”运算)。
假设枚举是:
enum Color { Red = 1, Green = 2, Blue = 3 }
那么大概您只需要 1、2、3 在您的分配中。
我认为您不能期望编译器或任何模板代码理解您的意图——任何 "enum -> uniform distribution" 代码都需要提示,以便它知道哪些枚举器应该与其他枚举器组合哪些只是选项。
所以简而言之,我认为你应该完全按照你链接到的问题做,并在 int
或其他任何东西上生成适当的分布,然后 static_cast
它到枚举.并且不要尝试使用某些模板解决方案来尝试了解您对每个可能的枚举的想法。
使用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
这是分布的三个实现,按照复杂性升序排列:
首先,如果我们可以依赖不同的值或者可以接受超重的重复值,我们可以只索引 _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;
}
};
如果这样做效率低下(可能枚举值稀疏),我们可以在初始化时计算查找 table:
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 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)];
}