使用c ++ 11的<random>标头,获取0到n之间的整数的正确方法是什么?</random>

时间:2014-07-04 04:08:35

标签: c++ c++11 random

我第一次开始使用C ++ 11的<random>标题,但仍有一些事情看起来有些神秘。这个问题是关于完成一项非常简单的任务的预期的,惯用的,最佳实践的方法。

目前,在我的代码的一部分中,我有类似的内容:

std::default_random_engine eng {std::random_device{}()};
std::uniform_int_distribution<> random_up_to_A {0, A};
std::uniform_int_distribution<> random_up_to_B {0, B};
std::uniform_int_distribution<> random_up_to_some_other_constant {0, some_other_constant};

然后当我想要一个介于0和B之间的整数时,我会调用random_up_to_B(eng)

由于这开始看起来有点傻,我想实现一个函数rnd,使rnd(n, eng)返回0到n之间的随机整数。

以下内容应该起作用

template <class URNG>
int rnd(int n, URNG &eng) {
    std::uniform_int_distribution<> dist {0, n};
    return dist(eng);
}

但这涉及每次创建一个新的分发对象,并且我得到的印象不是你应该这样做的。

所以我的问题是,使用<random>标题提供的抽象来完成这个简单任务的预期最佳实践方法是什么?我问,因为我以后想要做比以后更复杂的事情,我想确保我以正确的方式使用这个系统。

3 个答案:

答案 0 :(得分:7)

uniform_int_distribution构建起来不应该是昂贵的,因此每次使用新限制创建一个应该没问题。但是,有一种方法可以使用具有新限制的同一对象,但这很麻烦。

uniform_int_distribution::operator()有一个重载,它带有一个uniform_int_distribution::param_type对象,可以指定要使用的新限制,但param_type本身是一个不透明的类型,并且没有可移植的方法来构造一个除了从现有uniform_int_distribution实例中提取它。例如,以下函数可用于构造uniform_int_distribution::param_type

std::uniform_int_distribution<>::param_type
    make_param_type(int min, int max)
{
    return std::uniform_int_distribution<>(min, max).param();
}

将这些传递给operator(),生成的结果将在指定的范围内。

Live demo

因此,如果您真的想重复使用相同的uniform_int_distribution,请使用上述函数创建并保存param_type的多个实例,并在调用operator()时使用这些实例。


上面的答案是不准确的,因为标准确实指定param_type可以使用与相应分布类型的构造函数使用的相同的分布参数构造。感谢@ T.C. pointing this out

来自§26.5.1.6/ 9 [rand.req.dist]

  

对于D的每个构造函数,它们采用与分布参数相对应的参数,P应具有相应的构造函数,这些构造函数具有相同的要求并且在数量,类型和默认值方面具有相同的参数。 ...

因此,我们不需要不必要地构造分布对象来提取param_type。相反,make_param_type功能可以修改为

template <typename Distribution, typename... Args>
typename Distribution::param_type make_param_type(Args&&... args)
{
  return typename Distribution::param_type(std::forward<Args>(args)...);
}

可以用作

make_param_type<std::uniform_int_distribution<>>(0, 10)

Live demo

答案 1 :(得分:7)

回答我自己的问题:通过调整this document中的示例,以下似乎是实现函数返回0到n-1之间的随机整数的正确方法:

template<class URNG>
int rnd(int n, URNG &engine) {
    using dist_t = std::uniform_int_distribution<>;
    using param_t = dist_t::param_type;

    static dist_t dist;
    param_t params{0,n-1};

    return dist(engine, params);
}

为了使其成为线程安全的,必须避免static声明。一种可能性是按照这些方式制作一个便利课程,这就是我在自己的代码中使用的:

template<class URNG>
class Random {
public:        
    Random(): engine(std::random_device{}()) {}
    Random(typename std::result_of<URNG()>::type seed): engine(seed) {}

    int integer(int n) {
        std::uniform_int_distribution<>::param_type params {0, n-1};
        return int_dist(engine, params);
    }

private:
    URNG engine;
    std::uniform_int_distribution<> int_dist;
};

这是用(例如)Random<std::default_random_engine> rnd实例化的,然后可以用rnd.integer(n)获得随机整数。从其他分布中采样的方法可以很容易地添加到这个类中。

为了重复我在评论中所说的,重复使用分布对象可能不需要统一采样整数的特定任务,但对于其他分布,我认为这将比每次创建它更有效,因为有一些算法从一些可以通过同时生成多个值来节省CPU周期的分布中进行采样。 (原则上,即使uniform_int_distribution也可以通过SIMD矢量化来实现这一目标。)如果您不能通过保留分发对象来提高效率,那么很难想象为什么他们会以这种方式设计API

Hooray for C ++及其不必要的复杂性!下午结束了一个下午的工作,完成了一个简单的五分钟任务,但至少我对我现在所做的事情有了更好的了解。

答案 2 :(得分:2)

根据不同参数生成代码的惯用方法是根据需要创建分发对象,Vary range of uniform_int_distribution

std::random_device rd;
std::default_random_engine eng{rd()};
int n = std::uniform_int_distribution<>{0, A}(eng);

如果您担心未能充分利用分发的内部状态可能会影响性能,您可以使用单个分发并每次传递不同的参数:

std::random_device rd;
std::default_random_engine eng{rd()};
std::uniform_int_distribution<> dist;
int n = dist(eng, decltype(dist)::param_type{0, A});

如果这看起来很复杂,请考虑到大多数情况下,您将根据具有相同参数的相同分布生成随机数(因此分布构造函数采用参数);通过改变参数,您已进入高级领域。