Tạo số ngẫu nhiên bằng thư viện ngẫu nhiên C ++ 11


135

Như tiêu đề cho thấy, tôi đang cố gắng tìm ra cách tạo số ngẫu nhiên bằng <random>thư viện C ++ 11 mới . Tôi đã thử nó với mã này:

std::default_random_engine generator;
std::uniform_real_distribution<double> uniform_distance(1, 10.001);

Vấn đề với mã tôi có là mỗi lần tôi biên dịch và chạy nó, nó luôn tạo ra các số giống nhau. Vì vậy, câu hỏi của tôi là những chức năng khác trong thư viện ngẫu nhiên có thể thực hiện điều này trong khi thực sự ngẫu nhiên?

Đối với trường hợp sử dụng cụ thể của tôi, tôi đã cố gắng để có được một giá trị trong phạm vi [1, 10]


3
Câu hỏi này giáp nguy hiểm với "chủ yếu dựa trên ý kiến." Nếu bạn có thể thoát khỏi sự xúi giục để lấy ý kiến, tôi có thể thấy câu hỏi này rất hữu ích (nếu nó chưa được hỏi).
John Dibling

4
Tôi đề nghị sử dụng std::mt19937như một động cơ trừ khi bạn có lý do chính đáng để không. Và phân phối là một khoảng kín trên cả hai đầu.
chris


2
@chris phân phối không bị đóng ở cả hai đầu, hãy kiểm tra liên kết này hoặc liên kết
memo1288

1
@ memo1288, Cảm ơn bạn, tôi nghĩ OP đang sử dụng a std::uniform_int_distribution, được đóng ở cả hai đầu.
chris

Câu trả lời:


191

Stephan T. Lavavej (stl) từ Microsoft đã nói chuyện tại Đi bản địa về cách sử dụng các chức năng ngẫu nhiên mới của C ++ 11 và tại sao không sử dụng rand(). Trong đó, anh ấy bao gồm một slide về cơ bản giải quyết câu hỏi của bạn. Tôi đã sao chép mã từ slide đó bên dưới.

Bạn có thể xem toàn bộ bài nói chuyện của anh ấy tại đây: http://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful

#include <random>
#include <iostream>

int main() {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_real_distribution<double> dist(1.0, 10.0);

    for (int i=0; i<16; ++i)
        std::cout << dist(mt) << "\n";
}

Chúng tôi sử dụng random_devicemột lần để gieo hạt tạo số ngẫu nhiên có tên mt. random_device()chậm hơn mt19937, nhưng không cần phải gieo hạt vì nó yêu cầu dữ liệu ngẫu nhiên từ hệ điều hành của bạn (sẽ lấy nguồn từ nhiều vị trí khác nhau, ví dụ như RdRand ).


Nhìn vào câu hỏi / câu trả lời này , có vẻ như uniform_real_distributiontrả về một số trong phạm vi [a, b), nơi bạn muốn [a, b]. Để làm điều đó, chúng ta uniform_real_distibutionnên thực sự trông giống như:

std::uniform_real_distribution<double> dist(1, std::nextafter(10, DBL_MAX));

3
Vì câu hỏi là yêu cầu cách tổng quát nhất để tạo số ngẫu nhiên mà bạn có thể muốn sử dụng default_random_engine, theo c ++ primer, đây là cách mà việc triển khai được coi là hữu ích nhất
aaronman

2
@aaronman: Tôi đang nói về STL, nơi anh ấy rõ ràng không thích điều đó default_random_enginetồn tại.
Bill Lynch

5
@chris chúng ta đều biết sự khác biệt giữa vectơ và bản đồ, không phải ai cũng biết sự khác biệt giữa mt19937 và ranlux24, nếu ai đó quản lý để trở thành lập trình viên mà không biết vectơ và từ điển có lẽ họ nên có std::default_container, hy vọng là không có những người tự coi mình là lập trình viên không biết sự khác biệt, rất nhiều ngôn ngữ kịch bản có cấu trúc kiểu bản đồ mặc định, có thể được thực hiện theo nhiều cách mà người dùng có thể không biết
aaronman

21
Cuộc nextaftergọi này là quá mức cần thiết cho hầu hết các ứng dụng. Cơ hội doublehạ cánh ngẫu nhiên chính xác trên điểm cuối là rất nhỏ đến nỗi không có sự khác biệt thực tế giữa bao gồm và loại trừ nó.
Đánh dấu tiền chuộc

3
@ Chris Không liên quan (nhưng bạn mở cửa), bạn std::vectortương tự không làm việc ở đây vì std::vector thật sự một mặc định tốt do CPU bộ nhớ đệm. Nó thậm chí còn vượt trội hơn std::listkhi chèn ở giữa. Điều đó đúng ngay cả khi bạn hiểu tất cả các container và có thể đưa ra quyết định sáng suốt dựa trên độ phức tạp thuật toán.
void.pulum

24

Thư viện 'ngẫu nhiên' của tôi cung cấp trình bao bọc tiện lợi cao xung quanh các lớp ngẫu nhiên C ++ 11. Bạn có thể làm hầu hết mọi thứ với phương pháp 'nhận' đơn giản.

Ví dụ:

  1. Số ngẫu nhiên trong một phạm vi

    auto val = Random::get(-10, 10); // Integer
    auto val = Random::get(10.f, -10.f); // Float point
  2. Boolean ngẫu nhiên

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
  3. Giá trị ngẫu nhiên từ một std :: initilizer_list

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
  4. Trình lặp ngẫu nhiên từ phạm vi lặp hoặc tất cả vùng chứa

    auto it = Random::get( vec.begin(), vec.end() ); // it = random iterator
    auto it = Random::get( vec ); // return random iterator

Và nhiều thứ khác nữa! Kiểm tra trang github:

https://github.com/effolkronium/random


4

Tôi tô đỏ tất cả những thứ ở trên, khoảng 40 trang khác có c ++ như thế này và xem video từ Stephan T. Lavavej "STL" và vẫn không chắc chắn số lượng ngẫu nhiên hoạt động như thế nào trong Praxis vì vậy tôi đã dành trọn một ngày chủ nhật để tìm ra tất cả những gì về nó và làm thế nào nó hoạt động và có thể được sử dụng.

Theo tôi, STL đã đúng về việc "không sử dụng srand nữa" và anh ấy đã giải thích điều đó rất tốt trong video 2 . Ông cũng đề nghị sử dụng:

a) void random_device_uniform()- để tạo mã hóa nhưng chậm hơn (từ ví dụ của tôi)

b) các ví dụ với mt19937- nhanh hơn, khả năng tạo hạt giống, không được mã hóa


Tôi đã rút ra tất cả các cuốn sách c ++ 11 mà tôi có quyền truy cập và nhận thấy rằng các tác giả người Đức như Breymann (2015) vẫn sử dụng bản sao của

srand( time( 0 ) );
srand( static_cast<unsigned int>(time(nullptr))); or
srand( static_cast<unsigned int>(time(NULL))); or

chỉ với <random>thay vì <time> and <cstdlib>#includings - vì vậy hãy cẩn thận chỉ học từ một cuốn sách :).

Ý nghĩa - không nên được sử dụng kể từ c ++ 11 vì:

Các chương trình thường cần một nguồn số ngẫu nhiên. Trước tiêu chuẩn mới, cả C và C ++ đều dựa vào chức năng thư viện C đơn giản có tên rand. Hàm đó tạo ra các số nguyên giả được phân phối đồng đều trong phạm vi từ 0 đến giá trị tối đa phụ thuộc hệ thống ít nhất là 32767. Hàm rand có một số vấn đề: Nhiều, nếu không, hầu hết, các chương trình cần số ngẫu nhiên trong một phạm vi khác với một được sản xuất bởi rand. Một số ứng dụng yêu cầu số dấu phẩy động ngẫu nhiên. Một số chương trình cần số phản ánh phân phối không dạng. Các lập trình viên thường đưa ra sự không hợp lý khi họ cố gắng chuyển đổi phạm vi, loại hoặc phân phối các số được tạo bởi rand. (trích dẫn từ Lippmans C ++ primer phiên bản thứ năm 2012)


Cuối cùng tôi đã tìm thấy một lời giải thích tốt nhất trong số 20 cuốn sách trong Bjarne Stroustrups những cuốn mới hơn - và anh ấy nên biết nội dung của mình - trong "Chuyến tham quan C ++ 2019", "Nguyên tắc lập trình và thực hành sử dụng C ++ 2016" và "Ngôn ngữ lập trình C ++ phiên bản thứ 4 2014 "và một số ví dụ trong" Lippmans C ++ primer phiên bản thứ năm 2012 ":

Và nó thực sự đơn giản vì một trình tạo số ngẫu nhiên bao gồm hai phần: (1) một công cụ tạo ra một chuỗi các giá trị ngẫu nhiên hoặc giả ngẫu nhiên. (2) phân phối ánh xạ các giá trị đó thành phân phối toán học trong một phạm vi.


Bất chấp ý kiến ​​của anh chàng microsofts STL, Bjarne Stroustrups viết:

Trong, thư viện tiêu chuẩn cung cấp các công cụ và phân phối số ngẫu nhiên (§24.7). Theo mặc định, sử dụng default_random_engine, được chọn cho khả năng ứng dụng rộng rãi và chi phí thấp.

Các void die_roll()ví dụ là từ Bjarne Stroustrups - ý tưởng tốt động cơ máy phát điện và phân phối với using (thêm cơn rằng đây) .


Để có thể sử dụng thực tế các trình tạo số ngẫu nhiên được cung cấp bởi thư viện tiêu chuẩn ở <random> đây, một số mã thực thi với các ví dụ khác nhau được giảm đến mức cần thiết nhất, hy vọng thời gian và tiền bạc an toàn cho các bạn:

    #include <random>     //random engine, random distribution
    #include <iostream>   //cout
    #include <functional> //to use bind

    using namespace std;


    void space() //for visibility reasons if you execute the stuff
    {
       cout << "\n" << endl;
       for (int i = 0; i < 20; ++i)
       cout << "###";
       cout << "\n" << endl;
    }

    void uniform_default()
    {
    // uniformly distributed from 0 to 6 inclusive
        uniform_int_distribution<size_t> u (0, 6);
        default_random_engine e;  // generates unsigned random integers

    for (size_t i = 0; i < 10; ++i)
        // u uses e as a source of numbers
        // each call returns a uniformly distributed value in the specified range
        cout << u(e) << " ";
    }

    void random_device_uniform()
    {
         space();
         cout << "random device & uniform_int_distribution" << endl;

         random_device engn;
         uniform_int_distribution<size_t> dist(1, 6);

         for (int i=0; i<10; ++i)
         cout << dist(engn) << ' ';
    }

    void die_roll()
    {
        space();
        cout << "default_random_engine and Uniform_int_distribution" << endl;

    using my_engine = default_random_engine;
    using my_distribution = uniform_int_distribution<size_t>;

        my_engine rd {};
        my_distribution one_to_six {1, 6};

        auto die = bind(one_to_six,rd); // the default engine    for (int i = 0; i<10; ++i)

        for (int i = 0; i <10; ++i)
        cout << die() << ' ';

    }


    void uniform_default_int()
    {
       space();
       cout << "uniform default int" << endl;

       default_random_engine engn;
       uniform_int_distribution<size_t> dist(1, 6);

        for (int i = 0; i<10; ++i)
        cout << dist(engn) << ' ';
    }

    void mersenne_twister_engine_seed()
    {
        space();
        cout << "mersenne twister engine with seed 1234" << endl;

        //mt19937 dist (1234);  //for 32 bit systems
        mt19937_64 dist (1234); //for 64 bit systems

        for (int i = 0; i<10; ++i)
        cout << dist() << ' ';
    }


    void random_seed_mt19937_2()
    {
        space();
        cout << "mersenne twister split up in two with seed 1234" << endl;

        mt19937 dist(1234);
        mt19937 engn(dist);

        for (int i = 0; i < 10; ++i)
        cout << dist() << ' ';

        cout << endl;

        for (int j = 0; j < 10; ++j)
        cout << engn() << ' ';
    }



    int main()
    {
            uniform_default(); 
            random_device_uniform();
            die_roll();
            random_device_uniform();
            mersenne_twister_engine_seed();
            random_seed_mt19937_2();
        return 0;
    }

Tôi nghĩ rằng nó đã bổ sung tất cả và như tôi đã nói, tôi đã mất rất nhiều thời gian để đọc nó và ví dụ như vậy - nếu bạn có thêm thông tin về thế hệ số, tôi rất vui khi biết về điều đó qua chiều hoặc trong phần bình luận và sẽ thêm nó nếu cần thiết hoặc chỉnh sửa bài đăng này. Bool


0

Đây là một cái gì đó mà tôi vừa viết dọc theo những dòng đó ::

#include <random>
#include <chrono>
#include <thread>

using namespace std;

//==============================================================
// RANDOM BACKOFF TIME
//==============================================================
class backoff_time_t {
  public:
    random_device                      rd;
    mt19937                            mt;
    uniform_real_distribution<double>  dist;

    backoff_time_t() : rd{}, mt{rd()}, dist{0.5, 1.5} {}

    double rand() {
      return dist(mt);
    }
};

thread_local backoff_time_t backoff_time;


int main(int argc, char** argv) {
   double x1 = backoff_time.rand();
   double x2 = backoff_time.rand();
   double x3 = backoff_time.rand();
   double x4 = backoff_time.rand();
   return 0;
}

~


0

Đây là một số tài nguyên bạn có thể đọc về trình tạo số giả ngẫu nhiên.

https://en.wikipedia.org/wiki/Pseudorandom_number_generator

Về cơ bản, số ngẫu nhiên trong máy tính cần một hạt giống (số này có thể là thời gian hệ thống hiện tại).

Thay thế

std::default_random_engine generator;

Bởi

std::default_random_engine generator(<some seed number>);

-3

Bạn đã có hai tình huống phổ biến. Đầu tiên là bạn muốn số ngẫu nhiên và không quá bận tâm về chất lượng hoặc tốc độ thực hiện. Trong trường hợp đó, sử dụng macro sau

#define uniform() (rand()/(RAND_MAX + 1.0))

cung cấp cho bạn p trong phạm vi 0 đến 1 - epsilon (trừ khi RAND_MAX lớn hơn độ chính xác của một cú đúp, nhưng lo lắng về điều đó khi bạn đến với nó).

int x = (int) (thống nhất () * N);

Bây giờ cho một số nguyên ngẫu nhiên trên 0 đến N -1.

Nếu bạn cần phân phối khác, bạn phải chuyển đổi p. Hoặc đôi khi dễ dàng hơn để gọi đồng phục () nhiều lần.

Nếu bạn muốn hành vi lặp lại, hãy tạo hạt giống với hằng số, nếu không thì hãy gọi bằng lệnh gọi time ().

Bây giờ nếu bạn bận tâm về chất lượng hoặc hiệu suất thời gian chạy, hãy viết lại thống nhất (). Nhưng nếu không đừng chạm vào mã. Luôn giữ đồng phục () trên 0 đến 1 trừ epsilon. Bây giờ bạn có thể bọc thư viện số ngẫu nhiên C ++ để tạo đồng phục () tốt hơn, nhưng đó là một loại tùy chọn cấp trung bình. Nếu bạn bận tâm về các đặc điểm của RNG, thì cũng đáng đầu tư một chút thời gian để hiểu các phương thức cơ bản hoạt động như thế nào, sau đó cung cấp một phương thức. Vì vậy, bạn đã kiểm soát hoàn toàn mã và bạn có thể đảm bảo rằng với cùng một hạt giống, chuỗi sẽ luôn giống hệt nhau, bất kể nền tảng hay phiên bản C ++ nào bạn đang liên kết.


3
Ngoại trừ đó không phải là thống nhất (0 đến N-1). Lý do rất dễ, giả sử N = 100 và RAND_MAX = 32758. Không có cách nào để ánh xạ đồng nhất 32758 phần tử (RAND_MAX) thành 100 đầu vào. Cách duy nhất được đặt ràng buộc trên 32000 và thực hiện lại rand () nếu vượt ra khỏi giới hạn
amchacon

1
Nếu N là 100 thì RNG của bạn phải cực kỳ tốt để có thể phát hiện sai lệch so với phân phối phẳng.
Malcolm McLean
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.