簡體   English   中英

使用c ++ 11 <random> header,獲取0到n之間的整數的正確方法是什么?

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

我剛剛開始首次使用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和BI之間的整數時,調用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>標題提供的抽象,完成這個簡單任務的預期最佳實踐方法是什么? 我問,因為我一定要做比以后更復雜的事情,我想確保我以正確的方式使用這個系統。

uniform_int_distribution構造成本不應太高,因此每次使用新的限制創建一個應該沒問題。 但是,有一種方法可以使用具有新限制的相同對象,但這很麻煩。

uniform_int_distribution::operator()有一個重載,它接受一個uniform_int_distribution::param_type對象,該對象可以指定要使用的新限制,但是param_type本身是一個opaque類型,除了從現有的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_distributionparam_type使用上面的函數創建並保存param_type多個實例,並在調用operator()時使用它們。


上面的答案是不准確的,因為標准確實指定param_type可以使用與相應分布類型的構造函數使用的相同的分布參數構造。 感謝@TC 指出這一點

來自§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)

現場演示

回答我自己的問題:通過調整本文檔中的示例,以下似乎是實現函數返回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 ++及其不必要的復雜性! 這結束了下午的工作完成了一個簡單的五分鍾任務,但至少我對我現在正在做的事情有了更好的了解。

根據不同參數生成代碼的慣用方法是根據需要創建分布對象,每個均勻的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});

如果這看起來很復雜,請考慮到大多數情況下,您將根據具有相同參數的相同分布生成隨機數(因此分布構造函數采用參數); 通過改變參數,您已進入高級領域。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM