Genera numeri casuali utilizzando la libreria casuale C ++ 11


135

Come suggerisce il titolo, sto cercando di capire un modo per generare numeri casuali usando la nuova libreria C ++ 11 <random>. L'ho provato con questo codice:

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

Il problema con il codice che ho è che ogni volta che lo compilo ed eseguo, genera sempre gli stessi numeri. Quindi la mia domanda è: quali altre funzioni nella libreria casuale possono realizzare questo mentre sono veramente casuali?

Per il mio caso d'uso particolare, stavo cercando di ottenere un valore all'interno dell'intervallo [1, 10]


3
Questa domanda è pericolosamente confinante con "principalmente l'opinione pubblica". Se riesci a liberarti della richiesta di parere, vedo questa domanda molto utile (se non è già stata posta).
John Dibling,

4
Suggerisco di usare a std::mt19937come motore a meno che tu non abbia una buona ragione per non farlo. E la distribuzione è un intervallo chiuso su entrambe le estremità.
chris,


2
@chris la distribuzione non è chiusa su entrambe le estremità, controlla questo link o questo link
memo1288

1
@ memo1288, Grazie, ho pensato che l'OP stesse usando un std::uniform_int_distribution, che è chiuso su entrambe le estremità.
chris,

Risposte:


191

Stephan T. Lavavej (stl) di Microsoft ha tenuto un discorso a Going Native su come utilizzare le nuove funzioni casuali C ++ 11 e perché non utilizzarle rand(). In esso, ha incluso una diapositiva che risolve sostanzialmente la tua domanda. Ho copiato il codice da quella diapositiva in basso.

Puoi vedere il suo discorso completo qui: 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";
}

Usiamo random_deviceuna volta per seminare il generatore di numeri casuali chiamato mt. random_device()è più lento di mt19937, ma non è necessario eseguirne il seeding poiché richiede dati casuali dal sistema operativo (che provengono da varie posizioni, ad esempio RdRand ).


Guardando questa domanda / risposta , sembra che uniform_real_distributionritorni un numero nell'intervallo [a, b), dove vuoi [a, b]. Per fare ciò, il nostro uniform_real_distibutiondovrebbe effettivamente apparire come:

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

3
Poiché la domanda è il modo più generale per generare numeri casuali che potresti voler usare default_random_engine, secondo il c ++ primer è quello che l'implementazione ha ritenuto più utile
aaronman,

2
@aaronman: vado dal discorso di STL, dove esplicitamente non gli piace che default_random_engineesista.
Bill Lynch,

5
@chris sappiamo tutti la differenza tra un vettore e una mappa, non tutti conoscono la differenza tra mt19937 e ranlux24, se qualcuno è riuscito a diventare un programmatore senza sapere che cosa sono un vettore e un dizionario, forse dovrebbero avere std::default_container, speriamo che non ci siano persone che si considerano programmatori che non conoscono le differenze, molti linguaggi di scripting hanno una struttura di tipo di mappa predefinita, che potrebbe essere implementata in una varietà di modi che l'utente potrebbe non conoscere
aaronman,

21
La nextafterchiamata è eccessiva per la maggior parte delle applicazioni. Le possibilità di un doubleatterraggio casuale esattamente sull'endpoint sono così minuscole che non c'è alcuna differenza pratica tra l'inclusione e l'esclusione.
Mark Ransom,

3
@chris Non correlato (ma hai aperto la porta), la tua std::vectoranalogia non funziona qui perché in realtà std::vector è un buon default a causa della cache della CPU. Supera addirittura l' std::listinserimento nel mezzo. Questo è vero anche se capisci tutti i contenitori e potresti prendere una decisione informata in base alla complessità algoritmica.
void.pointer

24

La mia libreria "random" fornisce un wrapper estremamente conveniente per le classi casuali C ++ 11. Puoi fare quasi tutto con un semplice metodo "get".

Esempi:

  1. Numero casuale in un intervallo

    auto val = Random::get(-10, 10); // Integer
    auto val = Random::get(10.f, -10.f); // Float point
  2. Booleano casuale

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
  3. Valore casuale da un elenco std :: initilizer_list

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
  4. Iteratore casuale dalla gamma di iteratori o da tutti i contenitori

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

E ancora più cose! Dai un'occhiata alla pagina di github:

https://github.com/effolkronium/random


4

Ho scritto tutto sopra, circa 40 altre pagine con c ++ in questo modo e guardato il video di Stephan T. Lavavej "STL" e non ero ancora sicuro di come i numeri casuali funzionino nella prassi, quindi ho impiegato un'intera domenica per capire di cosa si tratta e come funziona e può essere utilizzato.

Secondo me STL ha ragione sul "non usare più srand" e lo ha spiegato bene nel video 2 . Inoltre consiglia di utilizzare:

a) void random_device_uniform()- per generazione crittografata ma più lenta (dal mio esempio)

b) gli esempi con mt19937- più veloce, capacità di creare semi, non crittografati


Ho tirato fuori tutti i libri di c ++ 11 a cui ho accesso e ho scoperto che autori tedeschi come Breymann (2015) usano ancora un clone di

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

solo con <random>invece di <time> and <cstdlib>#includings - quindi fai attenzione a imparare solo da un libro :).

Significato: non dovrebbe essere usato da c ++ 11 perché:

I programmi hanno spesso bisogno di una fonte di numeri casuali. Prima del nuovo standard, sia C che C ++ si basavano su una semplice funzione di libreria C denominata rand. Questa funzione produce numeri interi pseudocasuali distribuiti uniformemente nell'intervallo da 0 a un valore massimo dipendente dal sistema che è almeno 32767. La funzione rand presenta diversi problemi: molti, se non la maggior parte, i programmi richiedono numeri casuali in un intervallo diverso da uno prodotto da Rand. Alcune applicazioni richiedono numeri casuali in virgola mobile. Alcuni programmi richiedono numeri che riflettano una distribuzione non uniforme. I programmatori spesso introducono la non casualità quando provano a trasformare l'intervallo, il tipo o la distribuzione dei numeri generati da Rand. (citazione da Lippmans C ++ primer quinta edizione 2012)


Alla fine ho trovato la migliore spiegazione tra i 20 libri di Bjarne Stroustrups e quelli più recenti - e dovrebbe conoscere le sue cose - in "Un tour di C ++ 2019", "Principi di programmazione e pratica usando C ++ 2016" e "La quarta edizione del linguaggio di programmazione C ++ 2014 "e anche alcuni esempi in" Lippmans C ++ primer 5th edition 2012 ":

Ed è davvero semplice perché un generatore di numeri casuali è composto da due parti: (1) un motore che produce una sequenza di valori casuali o pseudo-casuali. (2) una distribuzione che mappa quei valori in una distribuzione matematica in un intervallo.


Nonostante l'opinione del ragazzo di Microsofts STL, Bjarne Stroustrups scrive:

In, la libreria standard fornisce motori e distribuzioni di numeri casuali (§24.7). Per impostazione predefinita, utilizzare default_random_engine, scelto per ampia applicabilità e basso costo.

L' void die_roll()esempio è di Bjarne Stroustrups - buona idea per generare motore e distribuzione con using (più di quello qui) .


Per essere in grado di fare un uso pratico dei generatori di numeri casuali forniti dalla libreria standard <random> qui alcuni codici eseguibili con diversi esempi ridotti al minimo necessario che speriamo di risparmiare tempo e denaro per voi ragazzi:

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

Penso che aggiunga tutto e come ho detto, mi ci sono voluti un sacco di letture e tempo per dirlo a quegli esempi - se hai altre cose sulla generazione di numeri, sono felice di sentirlo via pm o nella sezione commenti e lo aggiungerà se necessario o modificherà questo post. Bool


0

Ecco qualcosa che ho appena scritto in tal senso:

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

~



-3

Hai due situazioni comuni. Il primo è che vuoi numeri casuali e non sei troppo agitato per la qualità o la velocità di esecuzione. In tal caso, utilizzare la seguente macro

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

che ti dà p nell'intervallo da 0 a 1 - epsilon (a meno che RAND_MAX non sia più grande della precisione di un doppio, ma preoccupati di questo quando ci arrivi).

int x = (int) (uniform () * N);

Ora fornisce un numero intero casuale da 0 a N -1.

Se hai bisogno di altre distribuzioni, devi trasformare p. O a volte è più facile chiamare uniform () più volte.

Se desideri un comportamento ripetibile, esegui il seeding con una costante, altrimenti esegui il seeding con una chiamata a time ().

Ora, se sei infastidito dalla qualità o dalle prestazioni in fase di esecuzione, riscrivi uniform (). Ma per il resto non toccare il codice. Mantieni sempre uniforme () su 0 a 1 meno epsilon. Ora puoi avvolgere la libreria di numeri casuali C ++ per creare una migliore uniforme (), ma questa è una sorta di opzione di medio livello. Se sei preoccupato per le caratteristiche dell'RNG, allora vale anche la pena investire un po 'di tempo per capire come funzionano i metodi sottostanti, quindi fornirne uno. Quindi hai il controllo completo del codice e puoi garantire che con lo stesso seed, la sequenza sarà sempre esattamente la stessa, indipendentemente dalla piattaforma o dalla versione di C ++ a cui stai collegando.


3
Tranne che non è uniforme (da 0 a N-1). Il motivo è semplice, supponiamo che N = 100 e RAND_MAX = 32758. Non esiste un modo per mappare uniformemente 32758 elementi (RAND_MAX) su 100 input. Il modo unico è impostare un limite su 32000 e rieseguire rand () se esce dai limiti
amchacon

1
Se N è 100, l'RNG deve essere estremamente buono per poter rilevare la deviazione da una distribuzione piatta.
Malcolm McLean,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.