Bagaimana cara menghasilkan angka acak dalam C ++?

150

Saya mencoba membuat permainan dengan dadu, dan saya perlu memiliki angka acak di dalamnya (untuk mensimulasikan sisi-sisi dadu. Saya tahu bagaimana membuatnya antara 1 dan 6). Menggunakan

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

using namespace std;

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

tidak bekerja dengan baik, karena ketika saya menjalankan program beberapa kali, inilah output yang saya dapatkan:

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

Jadi saya ingin perintah yang akan menghasilkan angka acak yang berbeda setiap kali, bukan yang sama 5 kali berturut-turut. Apakah ada perintah yang akan melakukan ini?

Prediktabilitas
sumber
55
Selain masalah distribusi, perlu diingat bahwa dengan angka acak muncul kemungkinan mendapatkan hasil yang sama beberapa kali berturut-turut. Jika Anda dijamin tidak mendapatkan nomor yang sama dua kali berturut-turut, hasilnya tidak akan benar-benar acak, bukan?
cdhowie
5
Apa yang membuat Anda berpikir angka-angka itu tidak acak? Lempar dadu dengan nyata dan Anda sangat bisa mendapatkan hasil itu. Jika mereka dijamin akan berbeda antara setiap lemparan maka itu tidak akan benar-benar acak kan.
mattjgalloway
2
Baca juga eternallyconfuzzled.com/arts/jsw_art_rand.aspx mengapa menggunakan operator modulus seringkali bukan ide yang baik.
Benjamin Bannier
4
Anda salah paham lebih dari yang bisa ditampung oleh komentar atau bahkan jawaban. Anda perlu belajar, secara mandiri, tentang generator bilangan pseudo-acak, tentang benih, tentang pentingnya memilih benih yang benar-benar acak, dan tentang distribusi seragam.
Kerrek SB
20
Ketika Anda menyemai dengan waktu. Ini juga berarti bahwa jika Anda menjalankan program Anda lebih dari sekali per detik Anda akan mendapatkan nomor yang sama.
Martin York

Jawaban:

79

Masalah paling mendasar dari aplikasi pengujian Anda adalah bahwa Anda menelepon srandsekali dan kemudian memanggil randsatu kali dan keluar.

Inti dari srand fungsi adalah untuk menginisialisasi urutan bilangan pseudo-acak dengan seed acak.

Ini berarti bahwa jika Anda lulus nilai yang sama untuk sranddua aplikasi yang berbeda (dengan sama srand/rand pelaksanaan) maka Anda akan mendapatkan persis urutan yang sama dari rand()nilai-nilai membaca setelah itu di kedua aplikasi.

Namun dalam contoh aplikasi Anda urutan pseudo-acak hanya terdiri dari satu elemen - elemen pertama dari urutan pseudo-acak yang dihasilkan dari seed sama dengan waktu secondpresisi saat ini. Apa yang Anda harapkan dari output?

Tentunya ketika Anda menjalankan aplikasi pada detik yang sama - Anda menggunakan nilai seed yang sama - dengan demikian hasil Anda sama saja (seperti Martin York sudah disebutkan dalam komentar untuk pertanyaan).

Sebenarnya Anda harus menelepon srand(seed)satu kali dan kemudian menelepon rand() berkali-kali dan menganalisis urutan itu - itu akan terlihat acak.

EDIT:

Oh, aku mengerti. Rupanya deskripsi verbal tidak cukup (mungkin hambatan bahasa atau sesuatu ... :)).

BAIK. Contoh kode C kuno berdasarkan pada srand()/rand()/time()fungsi yang sama yang digunakan dalam pertanyaan:

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

^^^ ITU berurutan dari menjalankan tunggal dari program ini seharusnya terlihat acak.

EDIT2:

Ketika menggunakan pustaka standar C atau C ++, penting untuk dipahami bahwa sampai sekarang tidak ada fungsi standar tunggal atau kelas yang benar-benar menghasilkan data acak (dijamin oleh standar). Satu-satunya alat standar yang mendekati masalah ini adalah std :: random_device yang sayangnya masih tidak memberikan jaminan keacakan yang sebenarnya.

Tergantung pada sifat aplikasi Anda harus memutuskan terlebih dahulu apakah Anda benar-benar membutuhkan data yang benar-benar acak (tidak dapat diprediksi). Kasus penting ketika Anda paling pasti membutuhkan keacakan benar adalah keamanan informasi - misalnya menghasilkan kunci simetris, kunci pribadi asimetris, nilai garam, token keamanan, dll.

Namun angka acak tingkat keamanan adalah industri terpisah yang bernilai artikel terpisah.

Dalam kebanyakan kasus Pseudo-Random Number Generator sudah cukup - misalnya untuk simulasi ilmiah atau permainan. Dalam beberapa kasus, urutan pseudo-acak yang didefinisikan secara konsisten bahkan diperlukan - misalnya dalam game Anda dapat memilih untuk menghasilkan peta yang sama persis dalam runtime untuk menghindari penyimpanan banyak data.

Pertanyaan asli dan pengulangan banyak pertanyaan yang identik / serupa (dan bahkan banyak "jawaban" yang salah kepada mereka) menunjukkan bahwa pertama dan terutama penting untuk membedakan angka acak dari angka pseudo-acak DAN untuk memahami apa urutan nomor pseudo-acak dalam tempat pertama DAN untuk menyadari bahwa generator nomor pseudo-acak TIDAK digunakan dengan cara yang sama Anda bisa menggunakan generator nomor acak benar.

Secara intuitif ketika Anda meminta nomor acak - hasil yang dikembalikan tidak harus bergantung pada nilai yang dikembalikan sebelumnya dan tidak boleh tergantung jika ada yang meminta sesuatu sebelumnya dan tidak boleh bergantung pada saat apa dan pada proses apa dan pada komputer apa dan dari generator apa dan dalam galaksi apa yang diminta. Lagipula, itulah arti kata "acak" - tidak dapat diprediksi dan terlepas dari apa pun - jika tidak, kata itu tidak acak lagi, bukan? Dengan intuisi ini, adalah wajar untuk mencari di web beberapa mantra sihir untuk mendapatkan angka acak dalam konteks yang memungkinkan.

^^^ ITU semacam ekspektasi intuitif SANGAT SALAH dan berbahaya dalam semua kasus yang melibatkan Pseudo-Random Number Generator - meskipun masuk akal untuk nomor acak yang sebenarnya.

Sementara pengertian "bilangan acak" yang berarti ada - tidak ada yang namanya "bilangan acak-semu". Sebuah Pseudo-Random Number Generator benar-benar menghasilkan pseudo-acak nomor urut .

Ketika para ahli berbicara tentang kualitas PRNG mereka benar-benar berbicara tentang sifat statistik dari urutan yang dihasilkan (dan sub-urutan yang penting). Misalnya jika Anda menggabungkan dua PRNG berkualitas tinggi dengan menggunakan keduanya secara bergantian - Anda dapat menghasilkan urutan hasil yang buruk - meskipun mereka menghasilkan urutan yang baik masing-masing secara terpisah (dua urutan yang baik mungkin hanya berkorelasi satu sama lain dan dengan demikian menggabungkan dengan buruk).

Urutan pseudo-acak sebenarnya selalu deterministik (ditentukan sebelumnya oleh algoritma dan parameter awal) yaitu sebenarnya tidak ada yang acak tentang hal itu.

Secara khusus rand()/ srand(s)sepasang fungsi menyediakan urutan nomor pseudo-acak pseudo-acak tunggal per-proses tunggal yang dihasilkan dengan algoritma yang ditentukan-implementasi. Fungsi rand()menghasilkan nilai dalam rentang [0, RAND_MAX].

Kutipan dari standar C11:

The srandfungsi menggunakan argumen sebagai benih untuk urutan baru angka pseudo-random akan dikembalikan oleh panggilan berikutnya ke rand. Jika srandkemudian dipanggil dengan nilai seed yang sama, urutan bilangan pseudo-acak harus diulang. Jika randdipanggil sebelum panggilan apa pun srandtelah dibuat, urutan yang sama harus dihasilkan seperti ketika srandpertama kali dipanggil dengan nilai seed 1.

Banyak orang cukup berharap bahwa rand()akan menghasilkan urutan semi-independen nomor terdistribusi secara merata dalam kisaran 0untuk RAND_MAX. Yah itu pasti harus (kalau tidak itu tidak berguna) tapi sayangnya tidak hanya standar tidak mengharuskan - bahkan ada penyangkalan eksplisit yang menyatakan "tidak ada jaminan untuk kualitas urutan acak yang dihasilkan" . Dalam beberapa kasus sejarah rand/srand implementasi memang kualitasnya sangat buruk. Meskipun dalam implementasi modern kemungkinan besar cukup baik - tetapi kepercayaan itu rusak dan tidak mudah untuk pulih. Selain itu sifatnya yang non-thread-safe membuat penggunaannya yang aman dalam aplikasi multi-thread menjadi sulit dan terbatas (masih mungkin - Anda dapat menggunakannya dari satu utas khusus).

Template kelas baru std :: mersenne_twister_engine <> (dan kemudahan mengetiknya - std::mt19937/ std::mt19937_64dengan kombinasi parameter template yang baik) menyediakan per-objek generator nomor pseudo-acak didefinisikan dalam standar C ++ 11. Dengan parameter templat yang sama dan parameter inisialisasi yang sama, objek yang berbeda akan menghasilkan urutan output per objek yang persis sama pada komputer apa pun di aplikasi apa pun yang dibangun dengan pustaka standar yang memenuhi standar C ++ 11. Keuntungan dari kelas ini adalah urutan output yang diprediksi berkualitas tinggi dan konsistensi penuh di seluruh implementasi.

Juga ada lebih banyak mesin PRNG yang didefinisikan dalam standar C ++ 11 - std :: linear_congruential_engine <> (secara historis digunakan sebagai srand/randalgoritma kualitas adil dalam beberapa implementasi perpustakaan standar C) dan std :: subtract_with_carry_engine <> . Mereka juga menghasilkan urutan output per objek yang ditentukan-parameter sepenuhnya ditentukan.

Modern C + + 11 contoh pengganti untuk kode C usang di atas:

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

Versi kode sebelumnya yang menggunakan 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;
}
Serge Dundich
sumber
Saya telah mengajukan pertanyaan serupa di tautan di sini tetapi masih belum dapat menemukan jawaban yang jelas. Bisakah Anda menunjukkan "Sebenarnya Anda harus memanggil srand (seed) satu kali dan kemudian memanggil rand ()" dengan kode karena saya sudah melakukan apa yang Anda katakan tetapi tidak berfungsi dengan benar.
bashburak
2
@ bashburak Sepertinya Anda benar-benar kehilangan titik jawaban ini. Mengapa tepatnya Anda memotong kutipan saya? Saya mengatakan dalam jawaban saya secara harfiah "Sebenarnya Anda harus memanggil srand (seed) satu kali dan kemudian memanggil rand () berkali-kali dan menganalisis urutan itu - itu akan terlihat acak." Apakah Anda memperhatikan bahwa Anda harus memanggil rand () BANYAK KALI setelah panggilan srand tunggal (...)? Pertanyaan Anda di tautan adalah duplikat yang tepat dari pertanyaan ini dengan kesalahpahaman yang sama persis.
Serge Dundich
Ini adalah jawaban lama, tetapi muncul ketika Anda google "C ++ generasi nomor acak". Ini adalah saran yang buruk untuk programmer C ++, karena itu menyarankan Anda menggunakan rand()dan srand(). Bisakah Anda memperbaruinya?
Yakk - Adam Nevraumont
@ Yakk-AdamNevraumont Ini sebenarnya tidak menyarankan untuk menggunakan rand()dan srand(). Bahkan itu hanya menjawab pertanyaan dengan deskripsi yang diberikan. Jelas dari deskripsi (yang menggunakan rand/ srand) bahwa konsep dasar generasi bilangan pseudo-acak harus dijelaskan - seperti makna urutan pseudo-acak dan keturunannya. Saya mencoba untuk melakukan hal itu dan menggunakan kombinasi rand/ yang paling sederhana dan akrab srand. Yang lucu adalah bahwa beberapa jawaban lain - bahkan dengan peringkat sangat besar - menderita kesalahpahaman yang sama dengan penulis pertanyaan.
Serge Dundich
@ Yakk-AdamNevraumont Saya menerima saran Anda dan mengubah jawaban saya dengan beberapa info tentang tambahan C ++ terbaru. Meskipun saya menganggap ini sedikit di luar topik - tetapi saran Anda serta beberapa jawaban lain menunjukkan bahwa baik std::rand/std::srandfitur perpustakaan C ++ lama DAN baru seperti std::random_device<>, std :: mersenne_twister_engine <> dan banyak distribusi acak memerlukan beberapa penjelasan.
Serge Dundich
216

Menggunakan modulo dapat menyebabkan bias ke dalam angka acak, tergantung pada generator angka acak. Lihat pertanyaan ini untuk info lebih lanjut. Tentu saja, sangat mungkin untuk mendapatkan angka berulang dalam urutan acak.

Cobalah beberapa fitur C ++ 11 untuk distribusi yang lebih baik:

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

Lihat pertanyaan / jawaban ini untuk info lebih lanjut tentang C ++ 11 angka acak. Di atas bukan satu-satunya cara untuk melakukan ini, tetapi satu cara.

Batang jagung
sumber
7
Jumlah bias yang diperkenalkan dengan menggunakan %6semakin kecil. Mungkin penting jika Anda menulis game dadu untuk digunakan di Las Vegas, tetapi tidak ada konsekuensi dalam hampir semua konteks lainnya.
Hot Licks
9
HotLicks: setuju, tetapi jika Anda menggunakan versi C ++ yang mendukung random_devicedan mt19937sudah ada, secara harfiah tidak ada alasan untuk tidak keluar semua dan menggunakan standar uniform_int_distributionjuga.
Quuxplusone
4
Semua programmer harus menasihati orang-orang untuk menghindari modulo seperti wabah karena menggunakan divisi dan biaya ratusan siklus clock dan dapat mengacaukan waktu aplikasi Anda dan / atau membakar banyak daya baterai.
3
Apakah rng untuk "range"?
Christoffer
4
@ ChristofferHjärtström: Ini untuk r andom n umber g enerator.
Cornstalks
11

Jika Anda menggunakan boost libs, Anda bisa mendapatkan generator acak dengan cara ini:

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

Di mana fungsinya current_time_nanoseconds()memberikan waktu saat ini dalam nanodetik yang digunakan sebagai benih.


Berikut adalah kelas yang lebih umum untuk mendapatkan bilangan bulat dan tanggal acak dalam rentang:

#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);
    }
};
madx
sumber
1
Sekarang kita memiliki acak sebagai bagian dari standar saya akan mencegah penggunaan versi boost kecuali jika Anda menggunakan kompiler yang benar-benar lama.
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

Arley
sumber
Apa bedanya dengan kode penulis pertanyaan? (Kecuali Anda tidak menggunakan %6.) Dan jika Anda memutuskan untuk menggunakan std::randC ++ API dari randfungsi pustaka C mengapa tidak menggunakan std::timedan std::sranddemi konsistensi gaya C ++?
Serge Dundich
4

Bisa mendapatkan Randomerkode kelas penuh untuk menghasilkan angka acak dari sini!

Jika Anda membutuhkan angka acak di bagian proyek yang berbeda, Anda dapat membuat kelas terpisah Randomeruntuk merangkum semua randomhal di dalamnya.

Sesuatu seperti itu:

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_);
    }
};

Kelas seperti itu akan berguna nanti:

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

Anda dapat memeriksa link ini sebagai contoh bagaimana saya menggunakan seperti Randomerkelas untuk menghasilkan string acak. Anda juga dapat menggunakan Randomerjika Anda mau.

Gusev Slava
sumber
Apakah Anda tidak ingin menggunakan kembali generator untuk semua objek Randomer Anda? Terutama karena itu relatif mahal untuk membuat menginisialisasi dan mempertahankan negaranya.
Martin York
3

Hasilkan nomor acak yang berbeda setiap kali, bukan yang sama enam kali berturut-turut.

Gunakan skenario kasus

Saya menyamakan masalah Predictability dengan sekantong enam bit kertas, masing-masing dengan nilai dari 0 hingga 5 tertulis di atasnya. Selembar kertas diambil dari tas setiap kali nilai baru diperlukan. Jika tas kosong, maka nomor dimasukkan kembali ke dalam tas.

... dari ini, saya dapat membuat semacam algoritma.

Algoritma

Tas biasanya a Collection. Saya memilih bool[](atau dikenal sebagai array boolean, bit plane atau bit map) untuk mengambil peran tas.

Alasan saya memilih bool[]adalah karena indeks setiap item sudah menjadi nilai dari setiap lembar kertas. Jika kertas-kertas itu membutuhkan apa pun yang tertulis di atasnya, maka saya akan menggunakannya sebagai Dictionary<string, bool>gantinya. Nilai boolean digunakan untuk melacak apakah nomor telah ditarik atau belum.

Penghitung dipanggil RemainingNumberCountdiinisialisasi ke 5yang menghitung mundur sebagai nomor acak dipilih. Ini menyelamatkan kita dari keharusan menghitung berapa banyak kertas yang tersisa setiap kali kita ingin menggambar nomor baru.

Untuk memilih nilai random berikutnya saya menggunakan for..loopuntuk memindai melalui kantong indeks, dan counter untuk menghitung off ketika indexsedang falsedisebut NumberOfMoves.

NumberOfMovesdigunakan untuk memilih nomor yang tersedia berikutnya. NumberOfMovespertama-tama ditetapkan sebagai nilai acak antara 0dan 5, karena ada 0..5 langkah yang tersedia yang dapat kita lakukan melalui tas. Pada iterasi berikutnya NumberOfMovesdiatur menjadi nilai acak antara 0dan 4, karena sekarang ada 0,4 langkah yang bisa kita buat melalui tas. Karena angka-angka tersebut digunakan, angka-angka yang tersedia berkurang sehingga kami gunakan rand() % (RemainingNumberCount + 1)untuk menghitung nilai selanjutnya untuk NumberOfMoves.

Ketika NumberOfMovespenghitung mencapai nol, for..loopharus sebagai berikut:

  1. Tetapkan Nilai saat ini sama dengan for..loopindeks.
  2. Setel semua nomor di dalam tas false.
  3. Istirahat dari for..loop.

Kode

Kode untuk solusi di atas adalah sebagai berikut:

(letakkan tiga blok berikut ke dalam file .cpp utama satu demi satu)

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

    }

};

Kelas Konsol

Saya membuat kelas Konsol ini karena membuatnya mudah untuk mengarahkan ulang keluaran.

Di bawah ini dalam kode ...

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

... dapat diganti dengan ...

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

... dan kemudian Consolekelas ini dapat dihapus jika diinginkan.

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

    }

};

Metode utama

Contoh penggunaan sebagai berikut:

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");

}

Contoh output

Ketika saya menjalankan program, saya mendapat hasil sebagai berikut:

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 . . .

Pernyataan penutup

Program ini ditulis menggunakan Visual Studio 2017 , dan saya memilih untuk menjadikannya Visual C++ Windows Console Applicationproyek menggunakan .Net 4.6.1.

Saya tidak melakukan sesuatu yang istimewa di sini, jadi kodenya harus bekerja pada versi Visual Studio sebelumnya juga.

WonderWorker
sumber
Jika ini VS 2017, Anda harus menggunakan versi terbaru dari perpustakaan standar: en.cppreference.com/w/cpp/numeric/random . Saat ini contoh ini menggunakan fungsi pustaka acak C dan "Tidak ada jaminan kualitas urutan acak yang dihasilkan".
Robert Andrzantai
3

Setiap kali Anda melakukan pencarian web dasar random number generationdalam bahasa pemrograman C ++ pertanyaan ini biasanya yang pertama muncul! Saya ingin melemparkan topi saya ke atas ring untuk semoga lebih memperjelas konsep generasi nomor pseudo-acak di C ++ untuk coders masa depan yang pasti akan mencari pertanyaan yang sama di web!

Dasar

Pembuatan bilangan pseudo-acak melibatkan proses penggunaan algoritma deterministik yang menghasilkan urutan angka yang propertinya kira-kira menyerupai bilangan acak . Saya kira kira mirip , karena keacakan yang sebenarnya adalah misteri yang agak sulit dipahami dalam matematika dan ilmu komputer. Oleh karena itu, mengapa istilah pseudo-acak digunakan untuk menjadi lebih benar secara pedantik!

Sebelum Anda benar-benar dapat menggunakan PRNG, yaitu, pseudo-random number generatorAnda harus memberikan algoritma dengan nilai awal yang sering disebut juga seed . Namun, seed hanya harus diset satu kali sebelum menggunakan algoritma itu sendiri!

/// 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...

Jadi, jika Anda menginginkan urutan angka yang baik, maka Anda harus memberikan benih yang cukup kepada PRNG!

Jalan C Lama

Pustaka standar yang kompatibel dengan C dari yang dimiliki C ++, menggunakan apa yang disebut generator kongruensial linier yang ditemukan di cstdlibfile header! PRNG ini berfungsi melalui fungsi sambungan terputus-putus yang memanfaatkan aritmatika modular, yaitu algoritma cepat yang suka menggunakan modulo operator '%'. Berikut ini adalah penggunaan umum dari PRNG ini, sehubungan dengan pertanyaan awal yang diajukan oleh @Prediktabilitas:

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

Penggunaan umum PRNG C menampung sejumlah masalah seperti:

  1. Antarmuka keseluruhan dari std::rand()tidak terlalu intuitif untuk menghasilkan angka pseudo-acak yang tepat antara rentang yang diberikan, misalnya, menghasilkan angka antara [1, 6] seperti yang diinginkan @Pictictability.
  2. Penggunaan umum std::rand()menghilangkan kemungkinan distribusi seragam nomor pseudo-acak, karena Prinsip Pigeonhole .
  3. Cara umum std::rand()disemai secara std::srand( ( unsigned int )std::time( nullptr ) )teknis tidak benar, karena time_tdianggap sebagai tipe terbatas . Karenanya, konversi dari time_tke unsigned int tidak dijamin!

Untuk informasi yang lebih terperinci tentang masalah keseluruhan penggunaan PRNG C, dan bagaimana cara menghindarinya, silakan merujuk ke Menggunakan rand () (C / C ++): Saran untuk fungsi rand () perpustakaan standar C !

Cara C ++ Standar

Sejak standar ISO / IEC 14882: 2011 diterbitkan, yaitu, C ++ 11, randomperpustakaan telah terpisah dari bahasa pemrograman C ++ untuk sementara waktu sekarang. Perpustakaan ini dilengkapi dengan beberapa PRNGs, dan berbeda jenis distribusi seperti: distribusi seragam , distribusi normal , distribusi binomial , dll kode sumber berikut Contoh menunjukkan penggunaan yang sangat dasar dari randomperpustakaan, berkaitan dengan @ pertanyaan awal Prediktabilitas ini:

#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-bit Mersenne Twister mesin, dengan distribusi seragam dari bilangan bulat nilai-nilai yang digunakan dalam contoh di atas. (Nama mesin dalam kode sumber terdengar aneh, karena namanya berasal dari periode 2 ^ 19937-1). Contoh ini juga digunakan std::random_deviceuntuk seed engine, yang memperoleh nilainya dari sistem operasi (Jika Anda menggunakan sistem Linux, maka std::random_devicemengembalikan nilai dari /dev/urandom).

Perhatikan, bahwa Anda tidak harus menggunakan std::random_deviceuntuk menabur mesin apa pun . Anda dapat menggunakan konstanta atau bahkan chronoperpustakaan! Anda juga tidak harus menggunakan std::mt19937mesin versi 32-bit , ada opsi lain ! Untuk informasi lebih lanjut tentang kapabilitas randomperpustakaan, silakan merujuk ke cplusplus.com

Secara keseluruhan, programmer C ++ seharusnya tidak menggunakan std::rand()lagi, bukan karena itu buruk , tetapi karena standar saat ini memberikan alternatif yang lebih baik yang lebih lurus dan dapat diandalkan . Semoga banyak dari Anda yang merasa terbantu, terutama Anda yang baru saja mencari di web generating random numbers in c++!

Snuggly Plushy
sumber
2

Ini solusinya. Buat fungsi yang mengembalikan angka acak dan letakkan di luar fungsi utama untuk menjadikannya global. Semoga ini membantu

#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;
}
HDSSNET
sumber
2

Kode ini menghasilkan angka acak dari nhingga m.

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

contoh:

int main(){
    srand(time(0));
    cout << random(0, 99) << "\n";
}
Amir Fo
sumber
2
Ini tidak benar-benar menjawab pertanyaan.
HolyBlackCat
1
Anda tidak memperbaikinya. Inti dari pertanyaan adalah bahwa jika Anda menjalankan program beberapa kali per detik, maka itu menghasilkan nilai acak yang sama. Kode Anda juga melakukannya.
HolyBlackCat
1
@HolyBlackCat Saya sudah memeriksanya untuk beberapa kali, itu berfungsi. Sudahkah Anda menambahkan srand(time(0))ke fungsi utama sebelumnya random(n, m)?
Amir Fo
1
Anda harus menambahkan srand(time(0))ke fungsi utama bukan untuk loop atau di dalam implementasi fungsi.
Amir Fo
1
Saya telah menyalin kode Anda kata demi kata. Apakah Anda menjalankannya beberapa kali per detik ?
HolyBlackCat
1

untuk setiap file RUN acak

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);
}
Nehigienix
sumber
1
Anda tidak seharusnya membuat generator beberapa kali. Ia memelihara banyak status sehingga menghasilkan urutan angka acak yang memiliki distribusi yang sesuai (agar terlihat acak).
Martin York
-2

Berikut ini adalah generator acak sederhana dengan kira-kira. probabilitas yang sama untuk menghasilkan nilai positif dan negatif sekitar 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;
   }
Andrushenko Alexander
sumber