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

Using c++11's <random> header, what is the correct way to get an integer between 0 and n?

本文关键字:整数 之间 是什么 方法 获取 random lt c++11 gt 标头 使用      更新时间:2023-10-16

我刚开始第一次使用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之间的整数时,我调用CCD_ 2。

由于这看起来有点傻,我想实现一个函数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>标头提供的抽象,实现这一简单任务的最佳实践方法是什么?我这么问是因为我以后肯定想做比这更复杂的事情,我想确保我以正确的方式使用这个系统。

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(),生成的结果将在指定的范围内。

现场演示

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


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

来自§26.5.1.6/9[兰特要求dist]

对于采用与分布参数对应的参数的D的每个构造函数,P应具有符合相同要求的相应构造函数,并采用数量、类型和默认值相同的参数。...

因此,我们不需要仅仅为了提取random_up_to_B(eng)0而不必要地构造分布对象。相反,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)

实时演示

回答我自己的问题:通过调整本文档中的一个示例,以下似乎是实现返回0到n-1(含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。

为C++及其不必要的复杂性欢呼!这就结束了一个下午的工作,完成了一个简单的五分钟任务,但至少我对自己现在在做什么有了更好的了解。

根据不同参数生成代码的惯用方法是根据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});

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