สร้างตัวเลขสุ่มโดยใช้ไลบรารีสุ่ม C ++ 11


143

ตามที่ชื่อแนะนำฉันกำลังพยายามหาวิธีสร้างตัวเลขสุ่มโดยใช้<random>ไลบรารีC ++ 11 ใหม่ ฉันได้ลองใช้รหัสนี้แล้ว:

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

ปัญหาเกี่ยวกับรหัสที่ฉันมีคือทุกครั้งที่ฉันรวบรวมและเรียกใช้มันจะสร้างตัวเลขเดียวกันเสมอ ดังนั้นคำถามของฉันคือฟังก์ชั่นอื่น ๆ ในไลบรารีสุ่มสามารถทำสิ่งนี้ได้ในขณะที่สุ่มอย่างแท้จริง?

สำหรับกรณีการใช้งานโดยเฉพาะของฉันฉันพยายามหาค่าภายในช่วง [1, 10]


3
คำถามนี้มีพรมแดนติดกับ "ตามความคิดเห็นเป็นหลัก" หากคุณสามารถกำจัดการชักชวนเพื่อขอความคิดเห็นได้ฉันเห็นว่าคำถามนี้มีประโยชน์มาก (หากยังไม่ได้รับการถาม)
John Dibling

4
ฉันขอแนะนำให้ใช้ a std::mt19937เป็นเครื่องยนต์เว้นแต่คุณจะมีเหตุผลที่ดีที่จะไม่ทำ และการกระจายเป็นช่วงปิดทั้งสองด้าน
chris


2
@chris ไม่ได้ปิดการจำหน่ายทั้งสองด้านตรวจสอบที่ลิงค์นี้หรือลิงค์
บันทึก 1288

1
@ memo1288 ขอขอบคุณผมคิดว่า OP ถูกใช้std::uniform_int_distributionซึ่งจะปิดให้บริการในปลายทั้งสอง
chris

คำตอบ:


202

สเตฟานตัน Lavavej (STL) จากไมโครซอฟท์ไม่ได้พูดคุยที่จะพื้นเมืองเกี่ยวกับวิธีการใช้ C ++ ใหม่ 11 rand()ฟังก์ชั่นแบบสุ่มและทำไมไม่ใช้ ในนั้นเขามีสไลด์ที่ช่วยไขข้อสงสัยของคุณ ฉันคัดลอกโค้ดจากสไลด์ด้านล่างนี้แล้ว

คุณสามารถดูการพูดคุยทั้งหมดของเขาได้ที่นี่: http://channel9.msdn.com/Events/GoingNative/2013/rand-Cons ถือว่า-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";
}

เราใช้ครั้งเดียวเพื่อเมล็ดเครื่องกำเนิดไฟฟ้าจำนวนสุ่มชื่อrandom_device ช้ากว่าแต่ไม่จำเป็นต้องทำการ seed เนื่องจากขอข้อมูลแบบสุ่มจากระบบปฏิบัติการของคุณ (ซึ่งจะมาจากตำแหน่งต่างๆเช่นRdRandเป็นต้น)mtrandom_device()mt19937


มองไปที่คำถามนี้ / คำตอบก็ปรากฏว่าuniform_real_distributionผลตอบแทนที่ตัวเลขในช่วงที่คุณต้องการ[a, b) [a, b]ในการทำเช่นนั้นของเราuniform_real_distibutionควรมีลักษณะดังนี้:

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

3
เนื่องจากคำถามกำลังถามหาวิธีทั่วไปในการสร้างตัวเลขสุ่มที่คุณอาจต้องการใช้default_random_engineตามไพรเมอร์ c ++ จึงเป็นวิธีที่การใช้งานถือว่ามีประโยชน์มากที่สุด
aaronman

3
@aaronman: ฉันจะพูดตามคำพูดของ STL ซึ่งเขาไม่ชอบที่default_random_engineมีอยู่อย่างชัดเจน
Bill Lynch

5
@chris เราทุกคนรู้ถึงความแตกต่างระหว่างเวกเตอร์และแผนที่ไม่ใช่ทุกคนที่รู้ความแตกต่างระหว่าง mt19937 และ ranlux24 หากมีคนจัดการให้เป็นโปรแกรมเมอร์โดยไม่รู้ว่าเวกเตอร์และพจนานุกรมคืออะไรพวกเขาควรมี a std::default_containerหวังว่าจะไม่มี คนที่คิดว่าตัวเองเป็นโปรแกรมเมอร์ที่ไม่รู้ความแตกต่างภาษาสคริปต์จำนวนมากมีโครงสร้างประเภทแผนที่เริ่มต้นซึ่งสามารถนำไปใช้งานได้หลากหลายวิธีที่ผู้ใช้อาจไม่รู้
aaronman

21
การnextafterโทรเกินความจำเป็นสำหรับแอปพลิเคชันส่วนใหญ่ โอกาสของการdoubleลงจอดแบบสุ่มที่จุดสิ้นสุดนั้นค่อนข้างน้อยมากจนไม่มีความแตกต่างในทางปฏิบัติระหว่างการรวมและการไม่รวมมัน
Mark Ransom

4
@chris ไม่เกี่ยวข้อง (แต่คุณเปิดประตู) การstd::vectorเปรียบเทียบของคุณใช้ไม่ได้ที่นี่เพราะstd::vector เป็นค่าเริ่มต้นที่ดีเนื่องจากการแคช CPU มันทำได้ดีกว่าstd::listการแทรกตรงกลางด้วยซ้ำ นั่นเป็นเรื่องจริงแม้ว่าคุณจะเข้าใจคอนเทนเนอร์ทั้งหมดและสามารถตัดสินใจได้อย่างชาญฉลาดตามความซับซ้อนของอัลกอริทึม
void.pointer

25

ไลบรารี 'สุ่ม' ของฉันมี wrapper ที่สะดวกสบายสูงรอบ ๆ คลาสสุ่ม C ++ 11 คุณสามารถทำเกือบทุกอย่างด้วยวิธี 'get' ง่ายๆ

ตัวอย่าง:

  1. หมายเลขสุ่มในช่วง

    auto val = Random::get(-10, 10); // Integer
    auto val = Random::get(10.f, -10.f); // Float point
    
  2. บูลีนแบบสุ่ม

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
    
  3. ค่าสุ่มจาก std :: initilizer_list

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
    
  4. ตัววนซ้ำแบบสุ่มจากช่วงตัววนซ้ำหรือคอนเทนเนอร์ทั้งหมด

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

และสิ่งอื่น ๆ อีกมากมาย! ตรวจสอบหน้า github:

https://github.com/effolkronium/random


7

ผมสีแดงทุกสิ่งข้างต้นประมาณ 40 หน้าอื่น ๆ ด้วย C ++ ในนั้นเช่นนี้และดูวิดีโอจากสเตฟานตัน Lavavej "STL" และยังคงไม่แน่ใจว่าสุ่มตัวเลขงานใน Praxis ดังนั้นฉันจึงเต็มอาทิตย์ที่จะคิดออก มันเกี่ยวกับอะไรและทำงานอย่างไรและสามารถใช้งานได้

ในความคิดของฉัน STL ที่ถูกต้องเกี่ยวกับ "ไม่ได้ใช้ srand อีกต่อไป" และเขาอธิบายได้ดีในวิดีโอ2 เขายังแนะนำให้ใช้:

ก) void random_device_uniform()- สำหรับการสร้างที่เข้ารหัส แต่ช้ากว่า (จากตัวอย่างของฉัน)

b) ตัวอย่างที่มีmt19937- เร็วกว่า, ความสามารถในการสร้างเมล็ดพันธุ์, ไม่เข้ารหัส


ฉันดึงหนังสือ c ++ 11 ที่อ้างสิทธิ์ทั้งหมดที่ฉันเข้าถึงได้และพบว่าผู้เขียนชาวเยอรมันเช่น Breymann (2015) ยังคงใช้โคลนของ

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

เพียงแค่<random>ใช้<time> and <cstdlib>#includings - ดังนั้นโปรดใช้ความระมัดระวังในการเรียนรู้จากหนังสือเล่มเดียว :)

ความหมาย - ที่ไม่ควรใช้ตั้งแต่ c ++ 11 เนื่องจาก:

โปรแกรมมักต้องการแหล่งที่มาของตัวเลขสุ่ม ก่อนที่จะมีมาตรฐานใหม่ทั้ง C และ C ++ อาศัยฟังก์ชันไลบรารี C แบบง่ายที่ชื่อ rand ฟังก์ชันนั้นจะสร้างจำนวนเต็มเทียมที่มีการกระจายอย่างสม่ำเสมอในช่วงตั้งแต่ 0 ถึงค่าสูงสุดที่ขึ้นกับระบบซึ่งมีค่าอย่างน้อย 32767 ฟังก์ชัน Rand มีปัญหาหลายประการ: หลายโปรแกรมต้องการตัวเลขสุ่มในช่วงที่แตกต่างจาก หนึ่งผลิตโดยแรนด์ บางแอปพลิเคชันต้องการตัวเลขทศนิยมแบบสุ่ม บางโปรแกรมต้องการตัวเลขที่แสดงถึงการแจกแจงแบบไม่สม่ำเสมอ โปรแกรมเมอร์มักจะแนะนำความไม่เป็นเงื่อนไขเมื่อพวกเขาพยายามที่จะเปลี่ยนช่วงประเภทหรือการกระจายของตัวเลขที่สร้างโดยแรนด์ (อ้างจาก Lippmans C ++ primer 5th edition 2012)


ในที่สุดฉันก็พบคำอธิบายที่ดีที่สุดจากหนังสือ 20 เล่มใน Bjarne Stroustrups ซึ่งเป็นหนังสือที่ใหม่กว่า - และเขาควรรู้เนื้อหาของเขาใน "A tour of C ++ 2019" "Programming Principles and Practice Using C ++ 2016" และ "The C ++ Programming Language 4th edition 2014 "และตัวอย่างบางส่วนใน" Lippmans C ++ primer 5th edition 2012 ":

และมันง่ายมากเพราะตัวสร้างตัวเลขสุ่มประกอบด้วยสองส่วน: (1) เอ็นจิ้นที่สร้างลำดับของค่าสุ่มหรือสุ่มหลอก (2) การแจกแจงที่จับคู่ค่าเหล่านั้นเป็นการแจกแจงทางคณิตศาสตร์ในช่วง


แม้จะมีความเห็นของ Microsofts STL Guy Bjarne Stroustrups เขียนว่า:

ในไลบรารีมาตรฐานมีเครื่องมือและการแจกแจงตัวเลขแบบสุ่ม (§24.7) โดยค่าเริ่มต้นให้ใช้ default_random_engine ซึ่งถูกเลือกสำหรับการใช้งานที่หลากหลายและต้นทุนต่ำ

void die_roll()ตัวอย่างจาก Bjarne Stroustrups - เครื่องมือสร้างความคิดที่ดีและการกระจายรายได้ด้วย(เพิ่มเติมการแข่งขันที่นี่)using


เพื่อให้สามารถใช้ประโยชน์จากเครื่องกำเนิดตัวเลขสุ่มที่จัดทำโดยไลบรารีมาตรฐานได้ใน <random> โค้ดปฏิบัติการบางตัวพร้อมตัวอย่างที่แตกต่างกันซึ่งลดความจำเป็นให้เหลือน้อยที่สุดเพื่อหวังว่าจะปลอดภัยเวลาและเงินสำหรับพวกคุณ:

    #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;
    }

ฉันคิดว่านั่นเป็นการเพิ่มทั้งหมดและอย่างที่ฉันพูดฉันต้องใช้เวลาอ่านและเวลามากมายในการกำหนดตัวอย่างนั้น - หากคุณมีข้อมูลเพิ่มเติมเกี่ยวกับการสร้างตัวเลขฉันยินดีที่จะได้ยินเกี่ยวกับเรื่องนั้นทาง pm หรือในส่วนความคิดเห็น และจะเพิ่มหากจำเป็นหรือแก้ไขโพสต์นี้ บูล


0

นี่คือสิ่งที่ฉันเพิ่งเขียนตามบรรทัดเหล่านั้น ::

#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

นี่คือแหล่งข้อมูลบางส่วนที่คุณสามารถอ่านเกี่ยวกับตัวสร้างตัวเลขสุ่มหลอก

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

โดยทั่วไปแล้วตัวเลขสุ่มในคอมพิวเตอร์จำเป็นต้องมีเมล็ดพันธุ์ (ตัวเลขนี้อาจเป็นเวลาของระบบปัจจุบัน)

แทนที่

std::default_random_engine generator;

โดย

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

-3

คุณมีสถานการณ์ทั่วไปสองสถานการณ์ อย่างแรกคือคุณต้องการตัวเลขแบบสุ่มและไม่ต้องกังวลกับคุณภาพหรือความเร็วในการดำเนินการมากเกินไป ในกรณีนั้นให้ใช้มาโครต่อไปนี้

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

ที่ให้คุณอยู่ในช่วง 0 ถึง 1 - epsilon (เว้นแต่ว่า RAND_MAX จะใหญ่กว่าความแม่นยำของคู่ แต่ต้องกังวลเมื่อคุณมาถึงมัน)

int x = (int) (เครื่องแบบ () * N);

ตอนนี้ให้จำนวนเต็มสุ่มที่ 0 ถึง N -1

ถ้าคุณต้องการการแจกแจงแบบอื่นคุณต้องแปลง p หรือบางครั้งก็ง่ายกว่าที่จะเรียก uniform () หลายครั้ง

หากคุณต้องการพฤติกรรมที่ทำซ้ำได้ให้เริ่มต้นด้วยค่าคงที่มิฉะนั้นเมล็ดด้วยการเรียกเวลา ()

ตอนนี้หากคุณกังวลเกี่ยวกับคุณภาพหรือประสิทธิภาพของเวลาทำงานให้เขียนเครื่องแบบใหม่ () แต่อย่างอื่นอย่าแตะต้องรหัส รักษาความสม่ำเสมอ () ไว้ที่ 0 ถึง 1 ลบ epsilon ตอนนี้คุณสามารถรวมไลบรารีตัวเลขสุ่ม C ++ เพื่อสร้างเครื่องแบบที่ดีกว่า () ได้ แต่นั่นคือตัวเลือกระดับกลาง หากคุณกังวลเกี่ยวกับลักษณะเฉพาะของ RNG คุณควรลงทุนเวลาสักหน่อยเพื่อทำความเข้าใจว่าวิธีการทำงานพื้นฐานนั้นทำงานอย่างไรจากนั้นระบุวิธีการหนึ่ง ดังนั้นคุณจึงสามารถควบคุมโค้ดได้อย่างสมบูรณ์และคุณสามารถรับประกันได้ว่าด้วยเมล็ดพันธุ์เดียวกันลำดับจะเหมือนกันทุกประการไม่ว่าคุณจะเชื่อมโยง C ++ กับแพลตฟอร์มใดหรือเวอร์ชันใดก็ตาม


3
ยกเว้นไม่สม่ำเสมอ (0 ถึง N-1) เหตุผลนั้นง่ายมากสมมติว่า N = 100 และ RAND_MAX = 32758 ไม่มีวิธีที่จะแมป 32758 องค์ประกอบ (RAND_MAX) ให้สม่ำเสมอกับอินพุต 100 วิธีที่ไม่ซ้ำกันถูกกำหนดไว้ที่ 32000 และเรียกใช้ Rand () ใหม่หากอยู่นอกขอบเขต
amchacon

1
ถ้า N เท่ากับ 100 ดังนั้น RNG ของคุณจะต้องดีมากจึงจะสามารถตรวจจับความเบี่ยงเบนจากการกระจายแบบแบนได้
Malcolm McLean
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.