Wie generiere ich eine Zufallszahl in C ++?


150

Ich versuche ein Spiel mit Würfeln zu machen, und ich muss Zufallszahlen enthalten (um die Seiten des Würfels zu simulieren. Ich weiß, wie man es zwischen 1 und 6 macht). Verwenden von

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

using namespace std;

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

funktioniert nicht sehr gut, denn wenn ich das Programm ein paar Mal starte, bekomme ich folgende Ausgabe:

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

Ich möchte also einen Befehl, der jedes Mal eine andere Zufallszahl generiert , nicht dieselbe fünfmal hintereinander. Gibt es einen Befehl, der dies erledigt?


54
Abgesehen von Verteilungsproblemen ist zu beachten, dass bei Zufallszahlen die Möglichkeit besteht, dass mehrere Ergebnisse hintereinander erzielt werden. Wenn Sie garantiert nicht zweimal hintereinander dieselbe Nummer erhalten würden, wären die Ergebnisse nicht wirklich zufällig, oder?
CDhowie

5
Was lässt Sie denken, dass diese Zahlen nicht zufällig sind? Werfen Sie einen Würfel und Sie könnten dieses Ergebnis sehr gut erzielen. Wenn sie garantiert zwischen den einzelnen Würfen unterschiedlich wären, wäre es nicht wirklich zufällig, oder?
Mattjgalloway

2
Lesen Sie auch eternallyconfuzzled.com/arts/jsw_art_rand.aspx, warum die Verwendung des Moduloperators nicht oft eine gute Idee ist.
Benjamin Bannier

4
Sie verstehen viel mehr falsch, als man in einen Kommentar oder sogar eine Antwort passen kann. Sie müssen unabhängig voneinander etwas über Pseudozufallszahlengeneratoren, über Samen, über die Wichtigkeit der Auswahl eines wirklich zufälligen Samens und über gleichmäßige Verteilungen lernen.
Kerrek SB

20
Wenn Sie mit der Zeit säen. Dies bedeutet auch, dass Sie dieselbe Nummer erhalten, wenn Sie Ihr Programm mehr als einmal pro Sekunde ausführen.
Martin York

Antworten:


79

Das grundlegendste Problem Ihrer Testanwendung besteht darin, dass Sie srandeinmal anrufen und dann einmal anrufen randund beenden.

Der ganze Sinn der srandFunktion besteht darin, die Folge von Pseudozufallszahlen mit einem zufälligen Startwert zu initialisieren .

Es bedeutet , dass , wenn Sie übergeben den gleichen Wert zu srandin zwei verschiedenen Anwendungen (mit der gleichen srand/ randUmsetzung) , dann erhalten Sie genau die gleiche Sequenz von rand()Werten nach , dass in beiden Anwendungen lesen.

In Ihrer Beispielanwendung besteht die Pseudozufallssequenz jedoch nur aus einem Element - dem ersten Element einer Pseudozufallssequenz, die aus einem Startwert erzeugt wird, der der aktuellen secondGenauigkeitszeit entspricht. Was erwarten Sie dann bei der Ausgabe?

Wenn Sie die Anwendung in derselben Sekunde ausführen - Sie verwenden denselben Startwert -, ist Ihr Ergebnis natürlich das gleiche (wie Martin York bereits in einem Kommentar zur Frage erwähnt hat).

Eigentlich sollten Sie einmal anrufen srand(seed)und dann rand() viele Male anrufen und diese Sequenz analysieren - sie sollte zufällig aussehen.

BEARBEITEN:

Oh ich verstehe. Anscheinend reicht eine verbale Beschreibung nicht aus (vielleicht Sprachbarriere oder so ... :)).

OK. Altmodisches C-Codebeispiel basierend auf denselben srand()/rand()/time()Funktionen, die in der Frage verwendet wurden:

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

^^^ DASS Sequenz aus einem einzigen Durchlauf des Programms soll zufällig aussehen.

EDIT2:

Bei Verwendung der C- oder C ++ - Standardbibliothek ist es wichtig zu verstehen, dass es derzeit keine einzige Standardfunktion oder -klasse gibt, die definitiv zufällige Daten erzeugt (garantiert durch den Standard). Das einzige Standardwerkzeug, das sich diesem Problem nähert, ist std :: random_device , das leider immer noch keine Garantie für die tatsächliche Zufälligkeit bietet.

Abhängig von der Art der Anwendung sollten Sie zunächst entscheiden, ob Sie wirklich zufällige (unvorhersehbare) Daten benötigen. Ein bemerkenswerter Fall, in dem Sie mit Sicherheit echte Zufälligkeit benötigen, ist die Informationssicherheit - z. B. das Generieren symmetrischer Schlüssel, asymmetrischer privater Schlüssel, Salt-Werte, Sicherheitstoken usw.

Zufallszahlen mit Sicherheitsstufe sind jedoch eine separate Branche, die einen separaten Artikel wert ist.

In den meisten Fällen ist der Pseudo-Zufallszahlengenerator ausreichend - z. B. für wissenschaftliche Simulationen oder Spiele. In einigen Fällen ist sogar eine konsistent definierte Pseudozufallssequenz erforderlich. In Spielen können Sie beispielsweise zur Laufzeit genau dieselben Karten erstellen, um zu vermeiden, dass viele Daten gespeichert werden.

Die ursprüngliche Frage und die wiederkehrende Vielzahl identischer / ähnlicher Fragen (und sogar viele fehlgeleitete "Antworten" darauf) zeigen, dass es in erster Linie wichtig ist, Zufallszahlen von Pseudozufallszahlen zu unterscheiden UND zu verstehen, in welcher Pseudozufallszahlenfolge sie sich befinden der erste Ort UND um zu erkennen, dass Pseudozufallszahlengeneratoren NICHT so verwendet werden, wie Sie echte Zufallszahlengeneratoren verwenden könnten.

Intuitiv, wenn Sie eine Zufallszahl anfordern - das zurückgegebene Ergebnis sollte nicht von zuvor zurückgegebenen Werten abhängen und sollte nicht davon abhängen, ob jemand zuvor etwas angefordert hat, und sollte nicht davon abhängen, in welchem ​​Moment und durch welchen Prozess und von welchem ​​Computer und von welchem ​​Generator und in welche Galaxie wurde es angefordert. Das ist es, was das Wort "zufällig" schließlich bedeutet - unvorhersehbar und unabhängig von irgendetwas -, sonst ist es nicht mehr zufällig, oder? Mit dieser Intuition ist es nur natürlich, im Internet nach Zaubersprüchen zu suchen, um eine solche Zufallszahl in einem möglichen Kontext zu erhalten.

^^^ DIESE Art von intuitiven Erwartungen ist SEHR FALSCH und schädlich in allen Fällen, in denen Pseudozufallszahlengeneratoren verwendet werden - obwohl sie für echte Zufallszahlen angemessen sind.

Während der sinnvolle Begriff "Zufallszahl" existiert, gibt es keine "Pseudozufallszahl". Ein Pseudo-Zufallszahlengenerator erzeugt tatsächlich Pseudozufallszahlenfolge .

Wenn Experten über die Qualität von PRNG sprechen, sprechen sie tatsächlich über statistische Eigenschaften der generierten Sequenz (und ihrer bemerkenswerten Teilsequenzen). Wenn Sie beispielsweise zwei PRNGs von hoher Qualität kombinieren, indem Sie beide nacheinander verwenden - Sie können eine schlechte resultierende Sequenz erzeugen - obwohl sie jeweils gute Sequenzen erzeugen (diese beiden guten Sequenzen können einfach miteinander korrelieren und somit schlecht kombinieren).

Die pseudozufällige Sequenz ist in der Tat immer deterministisch (vorgegeben durch ihren Algorithmus und ihre Anfangsparameter), dh es ist tatsächlich nichts Zufälliges daran.

Speziell rand()/ srand(s)Funktionspaar stellen eine singuläre, nicht threadsichere (!) Pseudozufallszahlenfolge pro Prozess bereit, die mit einem implementierungsdefinierten Algorithmus generiert wird. Die Funktion rand()erzeugt Werte im Bereich [0, RAND_MAX].

Zitat aus dem C11-Standard:

Die srandFunktion verwendet das Argument als Startwert für eine neue Folge von Pseudozufallszahlen, die bei nachfolgenden Aufrufen an zurückgegeben werden sollen rand. Wenn sranddann mit demselben Startwert aufgerufen wird, muss die Folge von Pseudozufallszahlen wiederholt werden. Wenn randvor einem Aufruf aufgerufen wird srand, wird dieselbe Sequenz generiert wie beim srandersten Aufruf mit einem Startwert von 1.

Viele Menschen erwarten vernünftigerweise, dass rand()dies eine Folge von halbunabhängigen, gleichmäßig verteilten Zahlen im Bereich 0von erzeugen würde RAND_MAX. Nun, es sollte auf jeden Fall (ansonsten ist es nutzlos), aber leider erfordert dies nicht nur der Standard - es gibt sogar einen expliziten Haftungsausschluss, der besagt, dass "es keine Garantien für die Qualität der erzeugten Zufallssequenz gibt" . In einigen historischen Fällen rand/ srandImplementierung war von sehr schlechter Qualität in der Tat. Obwohl es in modernen Implementierungen höchstwahrscheinlich gut genug ist - aber das Vertrauen ist gebrochen und nicht einfach wiederherzustellen. Abgesehen davon, dass es nicht threadsicher ist, ist seine sichere Verwendung in Multithread-Anwendungen schwierig und begrenzt (immer noch möglich - Sie können sie nur von einem dedizierten Thread aus verwenden).

Die neue Klassenvorlage std :: mersenne_twister_engine <> (und ihre praktischen typedefs - std::mt19937/ std::mt19937_64mit einer guten Kombination von Vorlagenparametern) bietet einen im C ++ 11-Standard definierten Pseudozufallszahlengenerator pro Objekt . Mit denselben Vorlagenparametern und denselben Initialisierungsparametern generieren verschiedene Objekte auf jedem Computer in jeder Anwendung, die mit einer C ++ 11-kompatiblen Standardbibliothek erstellt wurde, genau dieselbe Ausgabesequenz pro Objekt. Der Vorteil dieser Klasse ist die vorhersagbar hohe Ausgabesequenz und die vollständige Konsistenz über alle Implementierungen hinweg.

Es gibt auch mehr PRNG-Engines, die im C ++ 11-Standard definiert sind - std :: linear_congruential_engine <> ( srand/randin einigen Implementierungen der C-Standardbibliothek historisch als Algorithmus für faire Qualität verwendet) und std :: subtract_with_carry_engine <> . Sie erzeugen auch vollständig definierte parameterabhängige Ausgabesequenzen pro Objekt.

Beispiel für modernen C ++ 11-Ersatz für den veralteten C-Code oben:

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

Die Version des vorherigen Codes, die std :: uniform_int_distribution <> verwendet

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

Ich habe eine ähnliche Frage in diesem Link gestellt, konnte aber noch keine klare Antwort finden. Können Sie bitte "Eigentlich sollten Sie srand (seed) einmal aufrufen und dann rand () aufrufen" mit Codes demonstrieren, weil ich bereits getan habe, was Sie sagen, aber es funktioniert nicht richtig.
Bashburak

2
@bashburak Es scheint, dass Sie den Punkt dieser Antwort völlig verpasst haben. Warum genau hast du mein Zitat geschnitten? Ich sagte in meiner Antwort wörtlich: "Eigentlich sollten Sie srand (seed) einmal aufrufen und dann rand () viele Male aufrufen und diese Sequenz analysieren - sie sollte zufällig aussehen." Haben Sie bemerkt, dass Sie rand () VIELE MAL nach einem einzelnen srand (...) Aufruf aufrufen sollten? Ihre Frage in Ihrem Link ist ein genaues Duplikat dieser Frage mit genau demselben Missverständnis.
Serge Dundich

Dies ist eine alte Antwort, die jedoch angezeigt wird, wenn Sie "C ++ - Zufallszahlengenerierung" googeln. Es ist ein schlechter Rat für C ++ - Programmierer, weil es Ihnen rät, rand()und zu verwenden srand(). Kannst du es aktualisieren?
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont nicht raten es nicht wirklich zu nutzen rand()und srand(). Tatsächlich beantwortet es die Frage nur mit der bereitgestellten Beschreibung. Aus der Beschreibung (die rand/ verwendet srand) geht hervor, dass die Grundkonzepte der Erzeugung von Pseudozufallszahlen erklärt werden sollten - genau wie die Bedeutung der Pseudozufallssequenz und ihres Keims. Ich versuche , genau das zu tun, und verwenden Sie die einfache und vertraute rand/ srandKombination. Das Lustige ist, dass einige andere Antworten - selbst bei sehr hohen Bewertungen - unter denselben Missverständnissen leiden wie der Autor der Frage.
Serge Dundich

@ Yakk-AdamNevraumont Ich habe Ihren Rat befolgt und meine Antwort mit einigen Informationen zu den neuesten C ++ - Ergänzungen geändert. Obwohl ich das ein bisschen off topic betrachten - sondern auch Ihr Vorschlag , wie einige andere Antworten gibt an, dass beide gute alte std::rand/std::srandund neue C ++ Bibliothek Funktionen wie std::random_device<>, std :: mersenne_twister_engine <> und eine Vielzahl von Zufallsverteilungen erfordern eine Erklärung.
Serge Dundich

214

Die Verwendung von Modulo kann abhängig vom Zufallszahlengenerator zu einer Verzerrung der Zufallszahlen führen. Siehe diese Frage für weitere Informationen. Natürlich ist es durchaus möglich, sich wiederholende Zahlen in zufälliger Reihenfolge zu erhalten.

Probieren Sie einige C ++ 11-Funktionen für eine bessere Verteilung aus:

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

Weitere Informationen zu C ++ 11-Zufallszahlen finden Sie in dieser Frage / Antwort. Das Obige ist nicht der einzige Weg, dies zu tun, sondern ein Weg.


7
Das Ausmaß der durch Verwendung eingeführten Vorspannung %6ist verschwindend gering. Vielleicht von Bedeutung, wenn Sie ein Craps-Spiel schreiben, das in Las Vegas verwendet werden soll, aber in fast jedem anderen Kontext keine Konsequenz hat.
Hot Licks

9
HotLicks: Einverstanden, aber wenn Sie eine Version von C ++ verwenden, die dies unterstützt random_deviceund mt19937bereits unterstützt , gibt es buchstäblich keinen Grund, nicht alles zu tun und auch den Standard uniform_int_distributionzu verwenden.
Quuxplusone

4
Alle Programmierer sollten den Leuten raten, Modulo wie die Pest zu vermeiden, da es Teilung verwendet und das Hunderte von Taktzyklen kostet und das Timing Ihrer Anwendung durcheinander bringen und / oder viel Batteriestrom verbrauchen kann.

3
Ist rng für "Reichweite"?
Christoffer

4
@ ChristofferHjärtström: Es ist für r andom n umber g enerator.
Cornstalks

11

Wenn Sie Boost- Bibliotheken verwenden, können Sie auf folgende Weise einen Zufallsgenerator erhalten:

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

Wobei die Funktion current_time_nanoseconds()die aktuelle Zeit in Nanosekunden angibt, die als Keim verwendet wird.


Hier ist eine allgemeinere Klasse, um zufällige Ganzzahlen und Daten in einem Bereich zu erhalten:

#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
Jetzt, da wir Zufall als Teil des Standards haben, würde ich von der Verwendung der Boost-Version abraten, es sei denn, Sie verwenden einen wirklich alten Compiler.
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


Was ist der Unterschied zum Code des Frageautors? (Außer, dass Sie nicht verwenden %6.) Und wenn Sie sich für die Verwendung der std::randC ++ - API der randC-Bibliotheksfunktion entschieden haben, warum dann nicht std::timeund std::srandaus Gründen der Konsistenz des C ++ - Stils?
Serge Dundich

4

Hier erhalten Sie den vollständigen RandomerKlassencode zum Generieren von Zufallszahlen!

Wenn Sie Zufallszahlen in verschiedenen Teilen des Projekts benötigen, können Sie eine separate Klasse Randomererstellen, um alle darin enthaltenen Inhalte zusammenzufassen random.

Sowas in der Art:

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

Eine solche Klasse wäre später praktisch:

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

Sie können diesen Link als Beispiel überprüfen, wie ich eine solche RandomerKlasse verwende, um zufällige Zeichenfolgen zu generieren. Sie können auch verwenden, Randomerwenn Sie möchten.


Möchten Sie den Generator nicht für alle Ihre Randomer-Objekte wiederverwenden? Zumal das Erstellen und Initialisieren des Status relativ teuer ist.
Martin York

3

Generieren Sie jedes Mal eine andere Zufallszahl, nicht sechsmal hintereinander dieselbe.

Anwendungsfall

Ich verglich das Problem der Vorhersagbarkeit mit einer Tüte mit sechs Stück Papier, auf die jeweils ein Wert von 0 bis 5 geschrieben war. Jedes Mal, wenn ein neuer Wert benötigt wird, wird ein Stück Papier aus dem Beutel gezogen. Wenn der Beutel leer ist, werden die Nummern wieder in den Beutel gelegt.

... daraus kann ich eine Art Algorithmus erstellen.

Algorithmus

Eine Tasche ist normalerweise eine Collection. Ich habe ein bool[](auch als boolesches Array, Bitebene oder Bitmap bekannt) ausgewählt, um die Rolle des Beutels zu übernehmen.

Der Grund, warum ich mich für a entschieden habe, bool[]ist, dass der Index jedes Elements bereits der Wert jedes Blattes Papier ist. Wenn die Papiere etwas anderes erfordern würden, hätte ich Dictionary<string, bool>an seiner Stelle ein verwendet. Der boolesche Wert wird verwendet, um zu verfolgen, ob die Zahl noch gezeichnet wurde oder nicht.

Ein aufgerufener Zähler RemainingNumberCountwird so initialisiert, 5dass er herunterzählt, wenn eine Zufallszahl ausgewählt wird. Dies erspart uns das Zählen, wie viele Papierstücke jedes Mal übrig bleiben, wenn wir eine neue Zahl zeichnen möchten.

Den nächsten Zufallswert zu wählen , ich bin mit for..loopscannen durch den Beutel von Indizes und einem Zähler zählen ab , wenn eine indexwird falsegenannt NumberOfMoves.

NumberOfMoveswird verwendet, um die nächste verfügbare Nummer auszuwählen. NumberOfMoveswird zuerst als zufälliger Wert zwischen 0und festgelegt 5, da 0,5 Schritte verfügbar sind, die wir durch den Beutel ausführen können. Bei der nächsten Iteration NumberOfMoveswird ein zufälliger Wert zwischen 0und festgelegt 4, da jetzt 0 bis 4 Schritte durch den Beutel ausgeführt werden können. Wenn die Zahlen verwendet werden, verringern sich die verfügbaren Zahlen, sodass wir stattdessen rand() % (RemainingNumberCount + 1)den nächsten Wert für berechnen NumberOfMoves.

Wenn der NumberOfMovesZähler Null erreicht, for..loopsollte dies wie folgt sein:

  1. Stellen Sie den aktuellen Wert so ein, dass er mit dem for..loopIndex übereinstimmt.
  2. Stellen Sie alle Zahlen in der Tasche auf false.
  3. Pause von der for..loop.

Code

Der Code für die obige Lösung lautet wie folgt:

(Fügen Sie die folgenden drei Blöcke nacheinander in die Haupt-CPP-Datei ein.)

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

    }

};

Eine Konsolenklasse

Ich erstelle diese Konsolenklasse, weil sie das Umleiten von Ausgaben erleichtert.

Unten im Code ...

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

... kann ersetzt werden durch ...

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

... und dann kann diese ConsoleKlasse auf Wunsch gelöscht werden.

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

    }

};

Hauptmethode

Anwendungsbeispiel wie folgt:

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

}

Beispielausgabe

Als ich das Programm ausführte, erhielt ich die folgende Ausgabe:

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

Schlusserklärung

Dieses Programm wurde mit Visual Studio 2017 geschrieben und ich habe mich dafür entschieden, es zu einem Visual C++ Windows Console ApplicationProjekt zu machen .Net 4.6.1.

Ich mache hier nichts Besonderes, daher sollte der Code auch in früheren Versionen von Visual Studio funktionieren.


Wenn dies VS 2017 ist, sollten Sie die neueste Version der Standardbibliothek verwenden: en.cppreference.com/w/cpp/numeric/random . Derzeit verwendet dieses Beispiel die Funktionen der C-Zufallsbibliothek und "Es gibt keine Garantie für die Qualität der erzeugten Zufallssequenz".
Robert Andrzejuk

2

Hier ist eine Lösung. Erstellen Sie eine Funktion, die die Zufallszahl zurückgibt, und platzieren Sie sie außerhalb der Hauptfunktion, um sie global zu machen. Hoffe das hilft

#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

Dieser Code erzeugt Zufallszahlen von nbis m.

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

Beispiel:

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

2
Dies beantwortet die Frage nicht wirklich.
HolyBlackCat

1
Du hast es nicht repariert. Der Punkt der Frage ist, dass wenn Sie das Programm mehrmals pro Sekunde ausführen, es die gleichen Zufallswerte generiert. Ihr Code macht das auch.
HolyBlackCat

1
@HolyBlackCat Ich habe es auf mehrere Läufe überprüft, es funktioniert. Haben Sie schon einmal srand(time(0))zur Hauptfunktion hinzugefügt random(n, m)?
Amir Fo

1
Sie sollten srand(time(0))zur Hauptfunktion hinzufügen , nicht zur for-Schleife oder innerhalb der Funktionsimplementierung.
Amir Fo

1
Ich habe Ihren Code wörtlich kopiert. Haben Sie es mehrmals pro Sekunde ausgeführt ?
HolyBlackCat

2

Wann immer Sie eine einfache Websuche random number generationin der Programmiersprache C ++ durchführen, taucht diese Frage normalerweise zuerst auf! Ich möchte meinen Hut in den Ring werfen, um das Konzept der Pseudozufallszahlengenerierung in C ++ für zukünftige Codierer, die unweigerlich dieselbe Frage im Web suchen , hoffentlich besser zu klären !

Die Grundlagen

Die Erzeugung von Pseudozufallszahlen umfasst den Prozess der Verwendung eines deterministischen Algorithmus , der eine Folge von Zahlen erzeugt, deren Eigenschaften ungefähr Zufallszahlen ähneln . Ich sage ungefähr ähnlich , weil wahre Zufälligkeit in Mathematik und Informatik ein ziemlich schwer fassbares Rätsel ist . Daher wird der Begriff Pseudozufall verwendet, um pedantisch korrekter zu sein!

Bevor Sie tatsächlich eine PRNG verwenden können, das heißt pseudo-random number generator, müssen Sie den Algorithmus mit einem Anfangswert bieten oft auch als bezeichnet Samen . Der Startwert darf jedoch nur einmal gesetzt werden, bevor der Algorithmus selbst verwendet wird!

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

Wenn Sie also eine gute Folge von Zahlen wollen, müssen Sie dem PRNG einen ausreichenden Startwert geben!

Der alte C-Weg

Die abwärtskompatible Standardbibliothek von C, die C ++ hat, verwendet einen sogenannten linearen Kongruenzgenerator, der in der cstdlibHeader-Datei enthalten ist! Dieses PRNG funktioniert durch eine diskontinuierliche stückweise Funktion, die modulare Arithmetik verwendet, dh einen schnellen Algorithmus, der gerne die verwendet modulo operator '%'. Das Folgende ist die übliche Verwendung dieses PRNG in Bezug auf die ursprüngliche Frage, die von @Predictability gestellt wurde:

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

Die allgemeine Verwendung von Cs PRNG birgt eine ganze Reihe von Themen wie:

  1. Die Gesamtschnittstelle von std::rand()ist nicht sehr intuitiv für die richtige Erzeugung von Pseudozufallszahlen zwischen einem bestimmten Bereich, z. B. für die Erzeugung von Zahlen zwischen [1, 6], wie es @Predictability wollte.
  2. Die übliche Verwendung von std::rand()eliminiert aufgrund des Pigeonhole-Prinzips die Möglichkeit einer gleichmäßigen Verteilung von Pseudozufallszahlen .
  3. Der übliche Weg std::rand(), durch den std::srand( ( unsigned int )std::time( nullptr ) )technisch gesät wird, ist nicht korrekt, da er time_tals eingeschränkter Typ angesehen wird . Daher ist die Umstellung von time_tauf unsigned int nicht garantiert!

Ausführlichere Informationen zu den allgemeinen Problemen bei der Verwendung von PRNG von C und deren mögliche Umgehung finden Sie unter Verwenden von rand () (C / C ++): Hinweise zur rand () -Funktion der C-Standardbibliothek !

Der Standard C ++ Weg

Seit der Veröffentlichung der Norm ISO / IEC 14882: 2011, dh C ++ 11, ist die randomBibliothek seit einiger Zeit von der Programmiersprache C ++ getrennt. Diese Bibliothek ist mit mehreren PRNGs und verschiedenen Verteilungstypen ausgestattet, z. B. Gleichverteilung , Normalverteilung , Binomialverteilung usw. Das folgende Beispiel für den Quellcode zeigt eine sehr grundlegende Verwendung der randomBibliothek im Hinblick auf die ursprüngliche Frage von @ Predictability:

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

Die 32-Bit- Mersenne-Twister- Engine mit einer gleichmäßigen Verteilung von Ganzzahlwerten wurde im obigen Beispiel verwendet. (Der Name der Engine im Quellcode klingt seltsam, weil der Name aus der Zeit von 2 ^ 19937-1 stammt.) In diesem Beispiel wird auch std::random_devicedie Engine gesetzt, die ihren Wert vom Betriebssystem erhält (Wenn Sie ein Linux-System verwenden, wird std::random_deviceein Wert von zurückgegeben /dev/urandom).

Beachten Sie , dass Sie verwenden müssen nicht std::random_deviceSamen jeden Motor . Sie können Konstanten oder sogar die chronoBibliothek verwenden! Sie müssen auch nicht die 32-Bit-Version der std::mt19937Engine verwenden, es gibt andere Optionen ! Weitere Informationen zu den Funktionen der randomBibliothek finden Sie unter cplusplus.com

Alles in allem sollten C ++ - Programmierer nicht std::rand()mehr verwenden, nicht weil es schlecht ist , sondern weil der aktuelle Standard bessere Alternativen bietet, die einfacher und zuverlässiger sind . Hoffentlich finden viele von Ihnen dies hilfreich, insbesondere diejenigen von Ihnen, die kürzlich im Internet gesucht haben generating random numbers in c++!


1

für zufällig jede RUN-Datei

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
Sie sollten den Generator nicht mehrmals erstellen. Es behält eine Reihe von Zuständen bei, so dass eine Folge von Zufallszahlen mit der entsprechenden Verteilung generiert wird (damit es zufällig aussieht).
Martin York

-2

Hier ist ein einfacher Zufallsgenerator mit ca. gleiche Wahrscheinlichkeit, positive und negative Werte um 0 zu erzeugen:

  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;
   }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.