จะสร้างตัวเลขสุ่มใน C ++ ได้อย่างไร


150

ฉันพยายามสร้างเกมด้วยลูกเต๋าและฉันต้องมีตัวเลขสุ่มในนั้น (เพื่อจำลองด้านข้างของผู้ตายฉันรู้วิธีสร้างมันระหว่าง 1 ถึง 6) การใช้

#include <cstdlib> 
#include <ctime> 
#include <iostream>

using namespace std;

int main() 
{ 
    srand((unsigned)time(0)); 
    int i;
    i = (rand()%6)+1; 
    cout << i << "\n"; 
}

ทำงานได้ไม่ดีนักเพราะเมื่อฉันรันโปรแกรมสองสามครั้งนี่คือผลลัพธ์ที่ฉันได้รับ:

6
1
1
1
1
1
2
2
2
2
5
2

ดังนั้นฉันต้องการคำสั่งที่จะสร้างตัวเลขสุ่มที่แตกต่างกันในแต่ละครั้งไม่เหมือนกัน 5 ครั้งในแถว มีคำสั่งที่จะทำเช่นนี้หรือไม่?


56
การแจกแจงกันโปรดทราบว่าการใช้ตัวเลขสุ่มมีความเป็นไปได้ที่จะได้ผลลัพธ์เดียวกันหลาย ๆ ครั้งติดต่อกัน หากคุณรับประกันว่าจะไม่ได้รับหมายเลขเดียวกันสองครั้งติดต่อกันผลลัพธ์จะไม่ถูกสุ่ม
cdhowie

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

2
อ่านeternallyconfuzzled.com/arts/jsw_art_rand.aspx ด้วยว่าเหตุใดการใช้ตัวดำเนินการโมดูลัสจึงไม่ใช่ความคิดที่ดี
Benjamin Bannier

4
คุณเข้าใจผิดมากกว่าหนึ่งคนสามารถแสดงความคิดเห็นหรือแม้แต่คำตอบได้ คุณต้องเรียนรู้อย่างอิสระเกี่ยวกับเครื่องกำเนิดเลขสุ่มหลอกเกี่ยวกับเมล็ดเกี่ยวกับความสำคัญของการเลือกเมล็ดสุ่มอย่างแท้จริงและเกี่ยวกับการแจกแจงแบบสม่ำเสมอ
Kerrek SB

20
เมื่อคุณเพาะเมล็ดด้วยเวลา ซึ่งหมายความว่าหากคุณรันโปรแกรมมากกว่าหนึ่งวินาทีคุณจะได้รับหมายเลขเดียวกัน
Martin York

คำตอบ:


79

ปัญหาพื้นฐานที่สุดของแอปพลิเคชันทดสอบของคุณคือคุณโทรsrandหนึ่งครั้งแล้วโทรrandหนึ่งครั้งและออก

จุดทั้งหมดของsrandฟังก์ชั่นคือการเริ่มต้นลำดับของตัวเลขสุ่มหลอกด้วยเมล็ดสุ่ม

หมายความว่าหากคุณส่งค่าเดียวกันไปยังsrandในสองแอปพลิเคชันที่แตกต่างกัน (ที่มีsrand/ randการใช้งานเดียวกัน) จากนั้นคุณจะได้รับลำดับที่เหมือนกันของrand()ค่าที่อ่านหลังจากนั้นในทั้งสองแอปพลิเคชัน

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

เห็นได้ชัดว่าเมื่อคุณเรียกใช้แอปพลิเคชันในวินาทีเดียวกัน - คุณใช้ค่าเมล็ดเดียวกัน - ดังนั้นผลลัพธ์ของคุณจึงเป็นแบบเดียวกัน

ที่จริงแล้วคุณควรโทรsrand(seed)หนึ่งครั้งแล้วโทรrand() หลายครั้งและวิเคราะห์ลำดับนั้น - มันควรดูสุ่ม

แก้ไข:

อ้อ! ฉันเข้าใจแล้ว. เห็นได้ชัดว่าคำอธิบายด้วยวาจาไม่เพียงพอ (อาจเป็นอุปสรรคด้านภาษาหรืออะไรบางอย่าง ... :))

ตกลง. ตัวอย่างรหัส C แบบเก่าที่ใช้srand()/rand()/time()ฟังก์ชั่นเดียวกันกับที่ใช้ในคำถาม:

#include <stdlib.h>
#include <time.h>
#include <stdio.h>

int main(void)
{
    unsigned long j;
    srand( (unsigned)time(NULL) );

    for( j = 0; j < 100500; ++j )
    {
        int n;

        /* skip rand() readings that would make n%6 non-uniformly distributed
          (assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
        while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
        { /* bad value retrieved so get next one */ }

        printf( "%d,\t%d\n", n, n % 6 + 1 );
    }

    return 0;
}

^^^ ที่ลำดับจากการทำงานครั้งเดียวของโปรแกรมที่ควรจะมีลักษณะแบบสุ่ม

EDIT2:

เมื่อใช้ไลบรารีมาตรฐาน C หรือ C ++ สิ่งสำคัญคือต้องเข้าใจว่า ณ ตอนนี้ไม่มีฟังก์ชั่นมาตรฐานเดียวหรือคลาสที่สร้างข้อมูลสุ่มแบบสุ่มแน่นอน (รับประกันโดยมาตรฐาน) เครื่องมือมาตรฐานเดียวที่เข้าถึงปัญหานี้คือstd :: random_deviceแต่น่าเสียดายที่ยังไม่ได้รับประกันการสุ่มจริง

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

อย่างไรก็ตามตัวเลขสุ่มระดับความปลอดภัยเป็นอุตสาหกรรมที่แยกต่างหากที่คุ้มค่าบทความแยกต่างหาก

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

คำถามดั้งเดิมและคำถามซ้ำ ๆ มากมายที่เหมือนกัน / คล้ายคลึงกัน (และแม้แต่คำตอบที่ "เข้าใจผิด" หลายข้อ) บ่งชี้ว่าสิ่งแรกและสำคัญที่สุดคือการแยกแยะตัวเลขสุ่มจากตัวเลขสุ่มหลอกและเพื่อให้เข้าใจว่าอะไรคือตัวเลขสุ่มหลอก สถานที่แรกและที่จะตระหนักว่ากำเนิดตัวเลขสุ่มหลอกไม่ได้ใช้วิธีเดียวกับที่คุณสามารถใช้กำเนิดตัวเลขสุ่มจริง

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

^^^ ความคาดหวังแบบสัญชาตญาณแบบนั้นผิดมากและเป็นอันตรายในทุกกรณีที่เกี่ยวข้องกับเครื่องกำเนิดตัวเลขแบบหลอก - แม้จะมีเหตุผลสำหรับตัวเลขสุ่มจริง

ในขณะที่ความคิดที่มีความหมายของ "หมายเลขสุ่ม" มีอยู่ - ไม่มีสิ่งเช่น "หมายเลขสุ่มหลอก" Pseudo-Random Number Generatorจริงผลิตจำนวนสุ่มหลอกลำดับ

เมื่อผู้เชี่ยวชาญพูดถึงคุณภาพของ PRNG พวกเขาจะพูดถึงคุณสมบัติทางสถิติของลำดับที่สร้างขึ้น (และลำดับย่อยที่น่าทึ่ง) ตัวอย่างเช่นหากคุณรวม PRNG คุณภาพสูงสองรายการโดยใช้ทั้งสองอย่างในทางกลับกัน - คุณอาจสร้างลำดับผลลัพธ์ที่ไม่ดี - แม้ว่าพวกเขาจะสร้างลำดับที่ดีแยกจากกัน (ทั้งสองลำดับที่ดีเหล่านั้นอาจสัมพันธ์กัน

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

เฉพาะrand()/ srand(s)คู่ของฟังก์ชั่นให้เอกพจน์ต่อกระบวนการที่ไม่ปลอดภัยเธรด (!) ลำดับเลขสุ่มหลอกที่สร้างขึ้นด้วยอัลกอริทึมที่กำหนดใช้งาน ฟังก์ชั่นการผลิตค่าในช่วงrand()[0, RAND_MAX]

อ้างอิงจากมาตรฐาน C11:

ฟังก์ชั่นใช้อาร์กิวเมนต์เป็นเมล็ดพันธุ์สำหรับลำดับใหม่ของตัวเลขสุ่มหลอกที่จะส่งกลับโดยโทรตามมาsrand randหาก srandมีการเรียกด้วยค่าเมล็ดเดียวกันลำดับของตัวเลขสุ่มหลอกจะต้องทำซ้ำ หากrandถูกเรียกก่อนที่จะทำการเรียกใด ๆsrandลำดับเดียวกันจะถูกสร้างขึ้นเช่นเดียวกับเมื่อsrandถูกเรียกครั้งแรกด้วยค่าเมล็ด 1

หลายคนคาดหวังอย่างสมเหตุสมผลว่า rand()จะผลิตลำดับของกึ่งอิสระกระจายสม่ำเสมอตัวเลขในช่วงที่จะ0 RAND_MAXดีมันแน่นอนที่สุดควร (มิฉะนั้นจะไร้ประโยชน์) แต่น่าเสียดายที่ไม่ได้เป็นเพียงมาตรฐานไม่จำเป็นต้องให้ - มีแม้กระทั่งข้อจำกัดความรับผิดชอบอย่างชัดเจนว่ารัฐ"มีการค้ำประกันไม่เป็นคุณภาพของลำดับสุ่มผลิต" ในบางกรณีในอดีตrand/ srandการใช้งานมีคุณภาพไม่ดีอย่างแน่นอน แม้ว่าในการติดตั้งใช้งานที่ทันสมัยมันก็น่าจะดีพอ - แต่ความเชื่อใจนั้นขาดและไม่สามารถกู้คืนได้ นอกเหนือจากลักษณะที่ไม่ปลอดภัยของเธรดทำให้การใช้งานที่ปลอดภัยในแอพพลิเคชั่นแบบมัลติเธรดนั้นมีความยุ่งยากและ จำกัด (ยังเป็นไปได้ - คุณสามารถใช้งานได้จากเธรดเฉพาะหนึ่งเธรด)

เทมเพลตคลาสใหม่ std :: mersenne_twister_engine <> (และความสะดวกในการพิมพ์ - std::mt19937/ std::mt19937_64พร้อมกับแม่แบบพารามิเตอร์ที่ดี) ให้ตัวสร้างตัวเลขแบบหลอกต่อวัตถุที่กำหนดไว้ในมาตรฐาน C ++ 11 ด้วยพารามิเตอร์แม่แบบเดียวกันและพารามิเตอร์การเริ่มต้นเดียวกันวัตถุที่แตกต่างกันจะสร้างลำดับผลลัพธ์ต่อวัตถุเดียวกันบนคอมพิวเตอร์ทุกเครื่องในแอปพลิเคชันใด ๆ ที่สร้างด้วยไลบรารีมาตรฐานที่สอดคล้องกับ C ++ 11 ข้อได้เปรียบของคลาสนี้คือคุณภาพเอาต์พุตที่คาดการณ์ได้สูงและมีความสม่ำเสมอในการใช้งาน

นอกจากนี้ยังมีเอ็นจิน PRNG เพิ่มเติมที่กำหนดไว้ในมาตรฐาน C ++ 11 - มาตรฐาน :: linear_congruential_engine <> (อดีตเคยเป็นที่มีคุณภาพเป็นธรรมsrand/randอัลกอริทึมในบาง C การใช้งานห้องสมุดมาตรฐาน) และมาตรฐาน :: subtract_with_carry_engine <> พวกเขายังสร้างลำดับขึ้นอยู่กับพารามิเตอร์ที่กำหนดขึ้นอย่างเต็มที่ต่อลำดับวัตถุ

Modern day C ++ 11 ตัวอย่างการแทนที่รหัส C ที่ล้าสมัยด้านบน:

#include <iostream>
#include <chrono>
#include <random>

int main()
{
    std::random_device rd;
    // seed value is designed specifically to make initialization
    // parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
    // different across executions of application
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);

    for( unsigned long j = 0; j < 100500; ++j )
    /* ^^^Yes. Generating single pseudo-random number makes no sense
       even if you use std::mersenne_twister_engine instead of rand()
       and even when your seed quality is much better than time(NULL) */    
    {
        std::mt19937::result_type n;
        // reject readings that would make n%6 non-uniformly distributed
        while( ( n = gen() ) > std::mt19937::max() -
                                    ( std::mt19937::max() - 5 )%6 )
        { /* bad value retrieved so get next one */ }

        std::cout << n << '\t' << n % 6 + 1 << '\n';
    }

    return 0;
}

รุ่นของรหัสก่อนหน้าที่ใช้std :: uniform_int_distribution <>

#include <iostream>
#include <chrono>
#include <random>

int main()
{
    std::random_device rd;
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);
    std::uniform_int_distribution<unsigned> distrib(1, 6);

    for( unsigned long j = 0; j < 100500; ++j )
    {
        std::cout << distrib(gen) << ' ';
    }

    std::cout << '\n';
    return 0;
}

ฉันได้ถามคำถามที่คล้ายกันในลิงค์นี้แต่ก็ยังหาคำตอบไม่ได้เลย คุณช่วยกรุณาสาธิต "ที่จริงแล้วคุณควรเรียก srand (เมล็ด) หนึ่งครั้งแล้วโทร rand ()" ด้วยรหัสเพราะฉันทำสิ่งที่คุณพูดไปแล้ว แต่มันทำงานไม่ถูกต้อง
bashburak

2
@bashburak ดูเหมือนว่าคุณพลาดจุดตอบทั้งหมดไปแล้ว ทำไมคุณถึงตัดคำพูดของฉัน? ฉันพูดในคำตอบของฉันอย่างแท้จริง "จริงๆแล้วคุณควรเรียก srand (เมล็ด) หนึ่งครั้งแล้วโทร rand () หลายครั้งแล้ววิเคราะห์ลำดับนั้น - มันควรจะดูสุ่ม" คุณสังเกตเห็นว่าคุณควรโทรหา rand () หลายครั้งหลังจากโทร srand (... ) ครั้งเดียว คำถามของคุณในลิงก์ของคุณซ้ำซ้อนกับคำถามนี้โดยมีความเข้าใจผิดเหมือนกันทุกประการ
Serge Dundich

นี่เป็นคำตอบเก่า แต่จะปรากฏขึ้นเมื่อคุณ google "การสร้างหมายเลขสุ่ม C ++" มันเป็นคำแนะนำที่ดีสำหรับการเขียนโปรแกรมภาษา C ++ เพราะมันให้คำแนะนำแก่คุณใช้และrand() srand()คุณสามารถอัปเดตได้หรือไม่
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont มันไม่จริงแนะนำให้ใช้และrand() srand()ในความเป็นจริงมันเพียงตอบคำถามพร้อมคำอธิบายที่ให้ เห็นได้ชัดจากคำอธิบาย (ที่ใช้rand/ srand) ว่าแนวคิดพื้นฐานของการสร้างตัวเลขสุ่มหลอกควรอธิบาย - เช่นความหมายของลำดับหลอกสุ่มและเมล็ดของมัน ฉันพยายามที่จะทำตรงนั้นและใช้ง่ายที่สุดและมีความคุ้นเคยrand/ srandรวมกัน สิ่งที่ตลกคือคำตอบอื่น ๆ - แม้จะมีเรทติ้งสูงมาก แต่ก็ต้องทนทุกข์กับความเข้าใจผิดเช่นเดียวกับผู้เขียนคำถาม
Serge Dundich

@ Yakk-AdamNevraumont ฉันได้รับคำแนะนำของคุณและแก้ไขคำตอบของฉันพร้อมข้อมูลเกี่ยวกับการเพิ่ม C ++ ใหม่ล่าสุด แต่ผมคิดว่านี่เป็นบิตปิดหัวข้อ - แต่คำแนะนำของคุณเช่นเดียวกับคำตอบอื่น ๆ บางอย่างบ่งชี้ว่าทั้งเก่าดีstd::rand/std::srandคุณสมบัติ C ++ และห้องสมุดใหม่เช่นstd::random_device<>, มาตรฐาน :: mersenne_twister_engine <>และความหลากหลายของการกระจายสุ่มจำเป็นต้องมีคำอธิบายบางอย่าง
Serge Dundich

216

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

ลองใช้คุณสมบัติ C ++ 11 เพื่อการกระจายที่ดีขึ้น:

#include <random>
#include <iostream>

int main()
{
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist6(1,6); // distribution in range [1, 6]

    std::cout << dist6(rng) << std::endl;
}

ดูคำถาม / คำตอบนี้สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวเลขสุ่ม C ++ 11 ข้างต้นไม่ใช่วิธีเดียวที่จะทำเช่นนี้ แต่เป็นวิธีหนึ่ง


7
จำนวนอคติที่แนะนำโดยใช้%6มีน้อย อาจสำคัญหากคุณกำลังเขียนเกม craps เพื่อใช้ในลาสเวกัส แต่ไม่มีผลใด ๆ ในบริบทอื่น ๆ
Hot Licks

9
HotLicks: เห็นด้วย แต่ถ้าคุณใช้รุ่น C ++ ที่รองรับrandom_deviceและmt19937มีอยู่แล้วไม่มีเหตุผลที่จะไม่ออกไปข้างนอกและใช้มาตรฐานuniform_int_distributionเช่นกัน
Quuxplusone

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

3
rng สำหรับ "range" หรือไม่
Christoffer

4
@ ChristofferHjärtström: สำหรับr andom n umber g enerator
Cornstalks

11

หากคุณใช้Boost libs คุณสามารถรับตัวสร้างแบบสุ่มด้วยวิธีนี้:

#include <iostream>
#include <string>

// Used in randomization
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>

using namespace std;
using namespace boost;

int current_time_nanoseconds(){
    struct timespec tm;
    clock_gettime(CLOCK_REALTIME, &tm);
    return tm.tv_nsec;
}

int main (int argc, char* argv[]) {
    unsigned int dice_rolls = 12;
    random::mt19937 rng(current_time_nanoseconds());
    random::uniform_int_distribution<> six(1,6);

    for(unsigned int i=0; i<dice_rolls; i++){
        cout << six(rng) << endl;
    }
}

โดยที่ฟังก์ชันcurrent_time_nanoseconds()ให้เวลาปัจจุบันเป็นนาโนวินาทีซึ่งใช้เป็นเมล็ด


นี่คือคลาสทั่วไปเพิ่มเติมเพื่อรับจำนวนเต็มและวันที่แบบสุ่มในช่วง:

#include <iostream>
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/date_time/gregorian/gregorian.hpp"


using namespace std;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;


class Randomizer {
private:
    static const bool debug_mode = false;
    random::mt19937 rng_;

    // The private constructor so that the user can not directly instantiate
    Randomizer() {
        if(debug_mode==true){
            this->rng_ = random::mt19937();
        }else{
            this->rng_ = random::mt19937(current_time_nanoseconds());
        }
    };

    int current_time_nanoseconds(){
        struct timespec tm;
        clock_gettime(CLOCK_REALTIME, &tm);
        return tm.tv_nsec;
    }

    // C++ 03
    // ========
    // Dont forget to declare these two. You want to make sure they
    // are unacceptable otherwise you may accidentally get copies of
    // your singleton appearing.
    Randomizer(Randomizer const&);     // Don't Implement
    void operator=(Randomizer const&); // Don't implement

public:
    static Randomizer& get_instance(){
        // The only instance of the class is created at the first call get_instance ()
        // and will be destroyed only when the program exits
        static Randomizer instance;
        return instance;
    }
    bool method() { return true; };

    int rand(unsigned int floor, unsigned int ceil){
        random::uniform_int_distribution<> rand_ = random::uniform_int_distribution<> (floor,ceil);
        return (rand_(rng_));
    }

    // Is not considering the millisecons
    time_duration rand_time_duration(){
        boost::posix_time::time_duration floor(0, 0, 0, 0);
        boost::posix_time::time_duration ceil(23, 59, 59, 0);
        unsigned int rand_seconds = rand(floor.total_seconds(), ceil.total_seconds());
        return seconds(rand_seconds);
    }


    date rand_date_from_epoch_to_now(){
        date now = second_clock::local_time().date();
        return rand_date_from_epoch_to_ceil(now);
    }

    date rand_date_from_epoch_to_ceil(date ceil_date){
        date epoch = ptime(date(1970,1,1)).date();
        return rand_date_in_interval(epoch, ceil_date);
    }

    date rand_date_in_interval(date floor_date, date ceil_date){
        return rand_ptime_in_interval(ptime(floor_date), ptime(ceil_date)).date();
    }

    ptime rand_ptime_from_epoch_to_now(){
        ptime now = second_clock::local_time();
        return rand_ptime_from_epoch_to_ceil(now);
    }

    ptime rand_ptime_from_epoch_to_ceil(ptime ceil_date){
        ptime epoch = ptime(date(1970,1,1));
        return rand_ptime_in_interval(epoch, ceil_date);
    }

    ptime rand_ptime_in_interval(ptime floor_date, ptime ceil_date){
        time_duration const diff = ceil_date - floor_date;
        long long gap_seconds = diff.total_seconds();
        long long step_seconds = Randomizer::get_instance().rand(0, gap_seconds);
        return floor_date + seconds(step_seconds);
    }
};

1
ตอนนี้เรามีการสุ่มเป็นส่วนหนึ่งของมาตรฐานฉันจะไม่สนับสนุนการใช้งานบูสต์รุ่นเว้นแต่คุณจะใช้คอมไพเลอร์รุ่นเก่าอย่างแท้จริง
Martin York

9
#include <iostream>
#include <cstdlib>
#include <ctime>

int main() {
    srand(time(NULL));
    int random_number = std::rand(); // rand() return a number between ​0​ and RAND_MAX
    std::cout << random_number;
    return 0;
}

http://en.cppreference.com/w/cpp/numeric/random/rand


รหัสของผู้เขียนคำถามต่างกันอย่างไร (ยกเว้นว่าคุณไม่ได้ใช้%6) และถ้าคุณตัดสินใจที่จะใช้std::randC ++ API ของrandฟังก์ชั่นไลบรารี C แล้วทำไมไม่ใช้std::timeและstd::srandเพื่อความสอดคล้องของสไตล์ C ++?
Serge Dundich

4

สามารถรับRandomerรหัสคลาสเต็มเพื่อสร้างตัวเลขสุ่มได้จากที่นี่!

หากคุณต้องการตัวเลขสุ่มในส่วนต่าง ๆ ของโครงการคุณสามารถสร้างคลาสที่แยกต่างหาก Randomerเพื่อแค็ปซูลrandomสิ่งที่อยู่ภายใน

อะไรแบบนั้น:

class Randomer {
    // random seed by default
    std::mt19937 gen_;
    std::uniform_int_distribution<size_t> dist_;

public:
    /*  ... some convenient ctors ... */ 

    Randomer(size_t min, size_t max, unsigned int seed = std::random_device{}())
        : gen_{seed}, dist_{min, max} {
    }

    // if you want predictable numbers
    void SetSeed(unsigned int seed) {
        gen_.seed(seed);
    }

    size_t operator()() {
        return dist_(gen_);
    }
};

ชั้นเรียนดังกล่าวจะมีประโยชน์ในภายหลัง:

int main() {
    Randomer randomer{0, 10};
    std::cout << randomer() << "\n";
}

คุณสามารถตรวจสอบลิงค์นี้เป็นตัวอย่างวิธีที่ฉันใช้Randomerคลาสดังกล่าวเพื่อสร้างสตริงแบบสุ่ม คุณสามารถใช้Randomerหากคุณต้องการ


คุณไม่ต้องการใช้ตัวสร้างซ้ำสำหรับวัตถุ Randomer ทั้งหมดของคุณหรือไม่ โดยเฉพาะอย่างยิ่งเนื่องจากมีราคาค่อนข้างแพงในการเริ่มต้นและรักษาสถานะ
Martin York

3

สร้างหมายเลขสุ่มที่แตกต่างกันในแต่ละครั้งไม่เหมือนกันหกครั้งติดต่อกัน

ใช้สถานการณ์กรณีและปัญหา

ฉันเปรียบปัญหา Predictability กับถุงกระดาษหกบิตแต่ละอันมีค่าตั้งแต่ 0 ถึง 5 ที่เขียนลงบนกระดาษ กระดาษถูกดึงออกมาจากถุงทุกครั้งที่ต้องการค่าใหม่ หากถุงว่างเปล่าตัวเลขจะใส่กลับเข้าไปในกระเป๋า

... จากนี้ฉันสามารถสร้างอัลกอริทึมแปลก ๆ

ขั้นตอนวิธี

Collectionถุงมักจะเป็น ฉันเลือกbool[] (หรือที่รู้จักกันในชื่อ boolean array, bit plane หรือ bit map) เพื่อรับบทบาทของกระเป๋า

เหตุผลที่ฉันเลือกbool[]คือเนื่องจากดัชนีของแต่ละรายการมีค่าของกระดาษแต่ละแผ่นอยู่แล้ว หากเอกสารต้องการสิ่งอื่นที่เขียนบนพวกเขาฉันก็จะใช้Dictionary<string, bool>แทน ค่าบูลีนจะใช้ในการติดตามว่ามีการวาดตัวเลขหรือยัง

ตัวนับที่เรียกRemainingNumberCountใช้นั้นถูกเตรียมใช้งาน5นับลงเมื่อเลือกหมายเลขสุ่ม สิ่งนี้ช่วยให้เราไม่ต้องนับว่าเหลือกระดาษกี่แผ่นในแต่ละครั้งที่เราต้องการวาดหมายเลขใหม่

ในการเลือกสุ่มค่าต่อไปฉันใช้for..loopการสแกนผ่านถุงของดัชนีและเคาน์เตอร์นับออกเมื่อindexถูกเรียกว่าfalseNumberOfMoves

NumberOfMovesใช้เพื่อเลือกหมายเลขที่มีอยู่ถัดไป NumberOfMovesมีการตั้งค่าแรกให้เป็นค่าสุ่มระหว่าง0และ5เนื่องจากมี 0..5 ขั้นตอนที่เราสามารถทำได้ผ่านกระเป๋า ในการทำซ้ำครั้งต่อไปNumberOfMovesจะถูกตั้งค่าเป็นค่าสุ่มระหว่าง0และ4เนื่องจากขณะนี้มี 0..4 ขั้นตอนที่เราสามารถทำผ่านถุง เมื่อมีการใช้ตัวเลขตัวเลขที่มีให้จะลดลงดังนั้นเราจึงใช้rand() % (RemainingNumberCount + 1)เพื่อคำนวณค่าถัดไปNumberOfMovesแทน

เมื่อตัวNumberOfMovesนับถึงศูนย์for..loopควรเป็นดังนี้:

  1. ตั้งค่าปัจจุบันให้เป็นเช่นเดียวกับfor..loopดัชนี
  2. falseตั้งตัวเลขทั้งหมดในถุงไป
  3. for..loopทำลายจาก

รหัส

รหัสสำหรับการแก้ปัญหาข้างต้นเป็นดังนี้:

(วางสามบล็อกต่อไปนี้ลงในไฟล์. cpp หลักหนึ่งไฟล์หลังจากบล็อกอื่น)

#include "stdafx.h"
#include <ctime> 
#include <iostream>
#include <string>

class RandomBag {
public:
    int Value = -1;

    RandomBag() {
        ResetBag();

    }

    void NextValue() {
        int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);

        int NumberOfMoves = rand() % (RemainingNumberCount + 1);

        for (int i = 0; i < BagOfNumbersLength; i++)            
            if (BagOfNumbers[i] == 0) {
                NumberOfMoves--;

                if (NumberOfMoves == -1)
                {
                    Value = i;

                    BagOfNumbers[i] = 1;

                    break;

                }

            }



        if (RemainingNumberCount == 0) {
            RemainingNumberCount = 5;

            ResetBag();

        }
        else            
            RemainingNumberCount--; 

    }

    std::string ToString() {
        return std::to_string(Value);

    }

private:
    bool BagOfNumbers[6]; 

    int RemainingNumberCount;

    int NumberOfMoves;

    void ResetBag() {
        RemainingNumberCount = 5;

        NumberOfMoves = rand() % 6;

        int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);

        for (int i = 0; i < BagOfNumbersLength; i++)            
            BagOfNumbers[i] = 0;

    }

};

คลาสคอนโซล

ฉันสร้างคลาส Console นี้เพราะทำให้เปลี่ยนเส้นทางเอาต์พุตได้ง่าย

ด้านล่างในรหัส ...

Console::WriteLine("The next value is " + randomBag.ToString());

... สามารถถูกแทนที่ด้วย ...

std::cout << "The next value is " + randomBag.ToString() << std::endl; 

... และจากนั้นConsoleคลาสนี้สามารถลบได้หากต้องการ

class Console {
public:
    static void WriteLine(std::string s) {
        std::cout << s << std::endl;

    }

};

วิธีการหลัก

ตัวอย่างการใช้งานดังต่อไปนี้:

int main() {
    srand((unsigned)time(0)); // Initialise random seed based on current time

    RandomBag randomBag;

    Console::WriteLine("First set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nSecond set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nThird set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nProcess complete.\n");

    system("pause");

}

ตัวอย่างผลลัพธ์

เมื่อฉันรันโปรแกรมฉันได้ผลลัพธ์ต่อไปนี้:

First set of six...

The next value is 2
The next value is 3
The next value is 4
The next value is 5
The next value is 0
The next value is 1

Second set of six...

The next value is 3
The next value is 4
The next value is 2
The next value is 0
The next value is 1
The next value is 5

Third set of six...

The next value is 4
The next value is 5
The next value is 2
The next value is 0
The next value is 3
The next value is 1

Process complete.

Press any key to continue . . .

คำสั่งปิด

โปรแกรมนี้ถูกเขียนโดยใช้Visual Studio 2017และฉันเลือกที่จะทำให้มันเป็นโครงการที่ใช้Visual C++ Windows Console Application.Net 4.6.1

ฉันไม่ได้ทำอะไรเป็นพิเศษที่นี่ดังนั้นรหัสควรทำงานกับ Visual Studio รุ่นก่อนหน้าด้วย


ถ้านี่เป็น VS 2017, คุณควรจะใช้รุ่นล่าสุดของห้องสมุดมาตรฐาน: en.cppreference.com/w/cpp/numeric/random ขณะนี้ตัวอย่างนี้ใช้ฟังก์ชันไลบรารีแบบสุ่มของ C และ "ไม่มีการรับประกันเกี่ยวกับคุณภาพของลำดับแบบสุ่มที่สร้าง"
Robert Andrzejuk

3

เมื่อใดก็ตามที่คุณค้นหาเว็บพื้นฐานrandom number generationในภาษาการเขียนโปรแกรม C ++ คำถามนี้มักจะปรากฏขึ้นเป็นครั้งแรก! ฉันต้องการที่จะโยนหมวกของฉันเข้าสู่วงแหวนเพื่อหวังดีขึ้นในการอธิบายแนวคิดของการสร้างตัวเลขแบบหลอกใน C ++ สำหรับผู้เขียนโค้ดในอนาคตที่จะค้นหาคำถามเดียวกันนี้ในเว็บอย่างหลีกเลี่ยงไม่ได้!

พื้นฐาน

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

ก่อนที่คุณจะสามารถใช้จริง PRNG ที่คือpseudo-random number generatorคุณต้องมีขั้นตอนวิธีการที่มีค่าเริ่มต้นมักจะเรียกว่ามากเกินไปเป็นเมล็ดพันธุ์ อย่างไรก็ตามเมล็ดจะต้องตั้งค่าเพียงครั้งเดียว ก่อนที่จะใช้อัลกอริทึมเอง!

/// Proper way!
seed( 1234 ) /// Seed set only once...
for( x in range( 0, 10) ):
  PRNG( seed ) /// Will work as expected

/// Wrong way!
for( x in rang( 0, 10 ) ):
  seed( 1234 ) /// Seed reset for ten iterations!
  PRNG( seed ) /// Output will be the same...

ดังนั้นหากคุณต้องการลำดับตัวเลขที่ดีแล้วคุณจะต้องให้เมล็ดพันธุ์ที่เพียงพอแก่ PRNG!

The Old C Way

ไลบรารีมาตรฐานที่เข้ากันได้แบบย้อนหลังของ C ที่ C ++ นั้นใช้สิ่งที่เรียกว่าเครื่องกำเนิดเชิงเส้นตรงที่พบในcstdlibไฟล์ส่วนหัว! ฟังก์ชั่นนี้ PRNG modulo operator '%'ผ่านฟังก์ชั่นค่ต่อเนื่องที่ใช้คณิตศาสตร์แบบแยกส่วนคือขั้นตอนวิธีการที่รวดเร็วที่ชอบที่จะใช้ ต่อไปนี้เป็นการใช้งานทั่วไปของ PRNG นี้เกี่ยวกับคำถามดั้งเดิมที่ถามโดย @Predictability:

#include <iostream>
#include <cstdlib>
#include <ctime>

int main( void )
{
  int low_dist  = 1;
  int high_dist = 6;
  std::srand( ( unsigned int )std::time( nullptr ) );
  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << low_dist + std::rand() % ( high_dist - low_dist ) << std::endl;
  return 0;
}

การใช้งานทั่วไปของ PRNG ของ C เป็นโฮสต์ของปัญหาทั้งหมดเช่น:

  1. อินเตอร์เฟซโดยรวมของ std::rand()ไม่ง่ายสำหรับการสร้างตัวเลขสุ่มหลอกที่เหมาะสมระหว่างช่วงที่กำหนดเช่นสร้างตัวเลขระหว่าง [1, 6] ตามที่ @Predictability ต้องการ
  2. การใช้งานทั่วไปของการstd::rand()กำจัดความเป็นไปได้ของการกระจายแบบสุ่มของตัวเลขหลอกแบบสุ่มเพราะหลักการ Pigeonholeนกพิราบอาศัยหลักการ
  3. วิธีที่ใช้กันstd::rand()ได้รับเมล็ดผ่านstd::srand( ( unsigned int )std::time( nullptr ) )ทางเทคนิคไม่ถูกต้องเพราะtime_tถือว่าจะเป็นประเภทที่ จำกัด ดังนั้นการแปลงจากtime_tการunsigned int ไม่รับประกัน!

สำหรับข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับปัญหาโดยรวมของการใช้ PRNG ของ C และวิธีการหลีกเลี่ยงโปรดอ่านการใช้ rand () (C / C ++): คำแนะนำสำหรับฟังก์ชั่น rand () ของไลบรารีมาตรฐาน C !

วิธีมาตรฐาน C ++

นับตั้งแต่มีการเผยแพร่มาตรฐาน ISO / IEC 14882: 2011 เช่น C ++ 11 randomห้องสมุดจึงแยกออกจากภาษาการเขียนโปรแกรม C ++ มาระยะหนึ่งแล้ว ห้องสมุดนี้มาพร้อมกับหลาย PRNGs และแตกต่างกันประเภทการกระจายเช่นการกระจายชุด , การกระจายปกติ , การกระจายทวินามฯลฯ ตัวอย่างเช่นแหล่งที่มาของรหัสต่อไปนี้แสดงให้เห็นถึงการใช้งานขั้นพื้นฐานมากของrandomห้องสมุดด้วยการไปถึง @ คำถามเดิมคาดการณ์ของ:

#include <iostream>
#include <cctype>
#include <random>

using u32    = uint_least32_t; 
using engine = std::mt19937;

int main( void )
{
  std::random_device os_seed;
  const u32 seed = os_seed();

  engine generator( seed );
  std::uniform_int_distribution< u32 > distribute( 1, 6 );

  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << distribute( generator ) << std::endl;
  return 0;
}

32 บิตMersenne Twisterเครื่องยนต์ที่มีการกระจายชุดของจำนวนเต็มค่าถูกใช้ในตัวอย่างข้างต้น (ชื่อของเอ็นจิ้นในซอร์สโค้ดฟังดูแปลกเพราะชื่อมาจากช่วงเวลา 2 ^ 19937-1) ตัวอย่างยังใช้std::random_deviceเพื่อ seed เอ็นจินซึ่งรับค่าจากระบบปฏิบัติการ (ถ้าคุณใช้ระบบ Linux จากนั้นstd::random_deviceส่งคืนค่าจาก/dev/urandom)

รับทราบว่าคุณไม่ได้มีการใช้std::random_deviceเมล็ดเครื่องมือใดคุณสามารถใช้ค่าคงที่หรือแม้แต่chronoห้องสมุด! คุณไม่จำเป็นต้องใช้เอ็นจิ้นรุ่น 32 บิตstd::mt19937มีตัวเลือกอื่น ! สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความสามารถของrandomห้องสมุดโปรดดูที่cplusplus.com

โดยรวมแล้วโปรแกรมเมอร์ C ++ ไม่ควรใช้std::rand()อีกต่อไปไม่ใช่เพราะมันแย่แต่เนื่องจากมาตรฐานปัจจุบันให้ทางเลือกที่ดีกว่าที่ตรงไปตรงมาและเชื่อถือได้มากกว่า หวังว่าคุณหลายคนจะพบว่าสิ่งนี้มีประโยชน์โดยเฉพาะอย่างยิ่งพวกคุณที่เพิ่งค้นเว็บgenerating random numbers in c++!


2

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

#include <iostream>
#include <cstdlib>
#include <ctime>
int rollDie();
using std::cout;
int main (){
    srand((unsigned)time(0));
    int die1;
    int die2;
    for (int n=10; n>0; n--){
    die1 = rollDie();
    die2 = rollDie();
    cout << die1 << " + " << die2 << " = " << die1 + die2 << "\n";
}
system("pause");
return 0;
}
int rollDie(){
    return (rand()%6)+1;
}

2

รหัสนี้ผลิตตัวเลขสุ่มจากไปnm

int random(int from, int to){
    return rand() % (to - from + 1) + from;
}

ตัวอย่าง:

int main(){
    srand(time(0));
    cout << random(0, 99) << "\n";
}

2
นี่ไม่ได้ตอบคำถามจริงๆ
HolyBlackCat

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

1
@HolyBlackCat ฉันตรวจสอบมันหลายครั้งแล้วมันใช้งานได้ คุณเคยเพิ่มsrand(time(0))ฟังก์ชั่นหลักมาก่อนrandom(n, m)หรือไม่?
Amir Fo

1
คุณควรเพิ่มsrand(time(0))ฟังก์ชั่นหลักไม่ใช่เพื่อลูปหรือใช้งานฟังก์ชัน
Amir Fo

1
ฉันได้คัดลอกคำต่อคำของคุณแล้ว คุณรันหลาย ๆ ครั้งต่อวินาทีหรือไม่?
HolyBlackCat

1

สำหรับสุ่มทุกไฟล์ RUN

size_t randomGenerator(size_t min, size_t max) {
    std::mt19937 rng;
    rng.seed(std::random_device()());
    //rng.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<std::mt19937::result_type> dist(min, max);

    return dist(rng);
}

1
คุณไม่ควรสร้างตัวสร้างขึ้นหลายครั้ง มันจะเก็บกลุ่มของรัฐเพื่อสร้างลำดับของตัวเลขสุ่มที่มีการแจกแจงที่เหมาะสม (เพื่อให้ดูสุ่ม)
Martin York

-2

นี่คือตัวสร้างแบบสุ่มอย่างง่ายที่มีค่าประมาณ ความน่าจะเป็นที่เท่ากันของการสร้างค่าบวกและลบประมาณ 0:

  int getNextRandom(const size_t lim) 
  {
        int nextRand = rand() % lim;
        int nextSign = rand() % lim;
        if (nextSign < lim / 2)
            return -nextRand;
        return nextRand;
  }


   int main()
   {
        srand(time(NULL));
        int r = getNextRandom(100);
        cout << r << endl;
        return 0;
   }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.