Jak wygenerować liczbę losową w C ++?


150

Próbuję stworzyć grę w kości i muszę mieć w niej losowe liczby (aby zasymulować boki kostki. Wiem, jak zrobić to od 1 do 6). Za pomocą

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

using namespace std;

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

nie działa zbyt dobrze, ponieważ gdy uruchamiam program kilka razy, oto wynik, który otrzymuję:

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

Dlatego potrzebuję polecenia, które za każdym razem wygeneruje inną liczbę losową, a nie tę samą 5 razy z rzędu. Czy istnieje polecenie, które to zrobi?


55
Pomijając kwestie dystrybucji, należy pamiętać, że w przypadku liczb losowych istnieje możliwość uzyskania tego samego wyniku kilka razy z rzędu. Gdybyś miał gwarancję, że nie dostaniesz tej samej liczby dwa razy z rzędu, wyniki nie byłyby tak naprawdę przypadkowe, prawda?
cdhowie

5
Dlaczego myślisz, że te liczby nie są przypadkowe? Rzuć kostką naprawdę i bardzo dobrze możesz uzyskać taki wynik. Gdyby gwarantowano, że będą różne w każdym rzucie, nie byłoby to przypadkowe, prawda?
mattjgalloway

2
Przeczytaj również eternallyconfuzzled.com/arts/jsw_art_rand.aspx, dlaczego używanie operatora modułu nie jest często dobrym pomysłem.
Benjamin Bannier,

4
Nie rozumiesz o wiele więcej, niż można zmieścić w komentarzu lub nawet odpowiedzi. Musisz dowiedzieć się, niezależnie, o generatorach liczb pseudolosowych, o ziarnach, o tym, jak ważne jest wybranie prawdziwie losowego ziarna i o jednolitych dystrybucjach.
Kerrek SB

20
Kiedy wysiewasz z czasem. Oznacza to również, że jeśli uruchomisz program więcej niż raz na sekundę, otrzymasz ten sam numer.
Martin York,

Odpowiedzi:


79

Najbardziej podstawowym problemem twojej aplikacji testowej jest to, że dzwonisz srandraz, a potem dzwonisz randjeden raz i kończysz.

Głównym celem srandfunkcji jest zainicjowanie sekwencji liczb pseudolosowych pseudolosowych losowym ziarnem.

Oznacza to, że jeśli przejdą taką samą wartość , aby srandw dwóch różnych aplikacji (z tym samym srand/ randrealizacji) wtedy dostaniesz dokładnie tę samą sekwencję zrand() wartości odczytu po czym w obu aplikacjach.

Jednak w Twojej przykładowej aplikacji sekwencja pseudolosowa składa się tylko z jednego elementu - pierwszego elementu pseudolosowej sekwencji wygenerowanej z ziarna równego aktualnemu czasowi second precyzji. Czego spodziewasz się wtedy zobaczyć na wyjściu?

Oczywiście, gdy zdarzy ci się uruchomić aplikację w tej samej sekundzie - używasz tej samej wartości początkowej - więc twój wynik jest oczywiście taki sam (o czym Martin York wspomniał już w komentarzu do pytania).

Właściwie powinieneś zadzwonić srand(seed)jeden raz, a potem zadzwonić rand() wiele razy i przeanalizować tę sekwencję - powinna wyglądać losowo.

EDYTOWAĆ:

Oh, rozumiem. Z pozoru opis słowny to za mało (może bariera językowa czy coś… :)).

DOBRZE. Przykład staromodnego kodu C oparty na tych samych srand()/rand()/time()funkcjach, które zostały użyte w pytaniu:

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

^^^ TO sekwencja z jednego przebiegu programu ma wyglądać przypadkowe.

EDYCJA2:

Korzystając z biblioteki standardowej C lub C ++, ważne jest, aby zrozumieć, że w chwili obecnej nie ma ani jednej standardowej funkcji ani klasy, która ostatecznie tworzy faktycznie losowe dane (gwarantowane przez standard). Jedynym standardowym narzędziem, które rozwiązuje ten problem, jest std :: random_device, które niestety nadal nie daje gwarancji faktycznej losowości.

W zależności od charakteru aplikacji należy najpierw zdecydować, czy naprawdę potrzebujesz naprawdę losowych (nieprzewidywalnych) danych. Godny uwagi przypadek, w którym z całą pewnością potrzebujesz prawdziwej przypadkowości jest bezpieczeństwo informacji - np. Generowanie kluczy symetrycznych, asymetrycznych kluczy prywatnych, wartości soli, tokenów bezpieczeństwa itp.

Jednak liczby losowe o stopniu bezpieczeństwa to osobna branża warta osobnego artykułu.

W większości przypadków generator liczb pseudolosowych jest wystarczający - np. Do symulacji naukowych lub gier. W niektórych przypadkach konsekwentnie zdefiniowana sekwencja pseudolosowa jest nawet wymagana - np. W grach możesz zdecydować się na generowanie dokładnie takich samych map w czasie wykonywania, aby uniknąć przechowywania dużej ilości danych.

Oryginalne pytanie i powtarzająca się mnogość identycznych / podobnych pytań (a nawet wiele błędnych „odpowiedzi” na nie) wskazują, że przede wszystkim ważne jest, aby odróżnić liczby losowe od liczb pseudolosowych ORAZ zrozumieć, czym jest sekwencja liczb pseudolosowych w Po pierwsze ORAZ zdać sobie sprawę, że generatory liczb pseudolosowych NIE są używane w taki sam sposób, jak można używać prawdziwych generatorów liczb losowych.

Intuicyjnie, gdy zażądasz liczby losowej - zwracany wynik nie powinien zależeć od wcześniej zwróconych wartości i nie powinien zależeć od tego, czy ktoś wcześniej o coś poprosił i nie powinien zależeć w jakim momencie i przez jaki proces, na jakim komputerze i z jakiego generatora o jaką galaktykę został poproszony. To w końcu oznacza słowo „losowy” - bycie nieprzewidywalnym i niezależnym od wszystkiego - w przeciwnym razie nie jest już przypadkowe, prawda? Mając taką intuicję, naturalne jest przeszukiwanie sieci w poszukiwaniu jakichś magicznych zaklęć, które można rzucić, aby uzyskać taką losową liczbę w dowolnym możliwym kontekście.

^^^ TEN rodzaj intuicyjnych oczekiwań jest BARDZO NIEPRAWIDŁOWY i szkodliwy we wszystkich przypadkach związanych z generatorami liczb pseudolosowych liczb pseudolosowych - mimo że są rozsądne dla prawdziwych liczb losowych.

Chociaż istnieje sensowne pojęcie „liczby losowej” - nie ma czegoś takiego jak „liczba pseudolosowa”. Pseudo-Random Number Generator faktycznie produkuje liczb pseudolosowych sekwencji .

Kiedy eksperci mówią o jakości PRNG, w rzeczywistości mówią o statystycznych właściwościach wygenerowanej sekwencji (i jej godnych uwagi podsekwencji). Na przykład, jeśli połączysz dwa wysokiej jakości PRNG, używając ich obu na zmianę - możesz wytworzyć złą wynikową sekwencję - mimo że generują one dobre sekwencje każda z osobna (te dwie dobre sekwencje mogą po prostu korelować ze sobą, a zatem źle się łączyć).

Sekwencja pseudolosowa jest w rzeczywistości zawsze deterministyczna (z góry określona przez jej algorytm i parametry początkowe), tj. Właściwie nie ma w niej nic losowego.

W szczególności rand()/ srand(s)para funkcji zapewnia pojedynczą sekwencję liczb pseudolosowych na proces, która nie jest bezpieczna dla wątków (!), Generowaną za pomocą algorytmu zdefiniowanego w ramach implementacji. Funkcja rand()generuje wartości w zakresie [0, RAND_MAX].

Cytat ze standardu C11:

srandFunkcja używa argumentu jako nasiona do nowej sekwencji liczb pseudo-losowych być zwrócony przez kolejnych wywołań rand. Jeśli srandzostanie wywołany z tą samą wartością początkową, sekwencja liczb pseudolosowych zostanie powtórzona. Jeśli randzostanie wywołana przed jakimikolwiek wywołaniami, które srandmiały zostać wykonane, zostanie wygenerowana ta sama sekwencja, jak przy srandpierwszym wywołaniu z wartością początkową 1.

Wiele osób słusznie spodziewa się, że rand()dałoby to sekwencję półniezależnych, równomiernie rozłożonych liczb w zakresie 0do RAND_MAX. Cóż, zdecydowanie powinno (w przeciwnym razie jest bezużyteczne), ale niestety nie tylko standard tego nie wymaga - istnieje nawet wyraźne zastrzeżenie, które stwierdza, że „nie ma gwarancji co do jakości wyprodukowanej sekwencji losowej” . W niektórych przypadkach historycznych rand/srand wdrożenie było rzeczywiście bardzo złej jakości. Chociaż w nowoczesnych wdrożeniach jest najprawdopodobniej wystarczająco dobry - ale zaufanie jest zepsute i niełatwe do odzyskania. Poza tym, że nie jest bezpieczny dla wątków, jego bezpieczne użycie w aplikacjach wielowątkowych jest trudne i ograniczone (nadal jest możliwe - możesz po prostu używać ich z jednego dedykowanego wątku).

Nowy szablon klasy std :: mersenne_twister_engine <> (i jego wygodne typy czcionek - std::mt19937/ std::mt19937_64z dobrą kombinacją parametrów szablonu) zapewnia per-obiekt pseudolosowych generator liczb zdefiniowane w standardzie C ++ 11. Z tymi samymi parametrami szablonu i tymi samymi parametrami inicjalizacji różne obiekty będą generować dokładnie tę samą sekwencję wyjściową dla każdego obiektu na dowolnym komputerze w dowolnej aplikacji zbudowanej przy użyciu biblioteki standardowej zgodnej z C ++ 11. Zaletą tej klasy jest przewidywalna sekwencja wyjściowa o wysokiej jakości i pełna spójność między implementacjami.

Istnieje również więcej silników PRNG zdefiniowanych w standardzie C ++ 11 - std :: linear_congruential_engine <> (historycznie używany jako srand/randalgorytm uczciwej jakości w niektórych implementacjach bibliotek standardowych C) i std :: subtract_with_carry_engine <> . Generują również w pełni zdefiniowane zależne od parametrów sekwencje wyjściowe na obiekt.

Współczesny przykład C ++ 11 zastępujący przestarzały kod C powyżej:

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

Wersja poprzedniego kodu, która używa 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;
}

Zadałem podobne pytanie w tym linku, ale nadal nie mogłem znaleźć jasnej odpowiedzi. Czy możesz zademonstrować „Właściwie powinieneś raz wywołać srand (seed), a następnie wywołać rand ()” z kodami, ponieważ już zrobiłem to, co mówisz, ale nie działa poprawnie.
bashburak

2
@bashburak Wygląda na to, że całkowicie przegapiłeś sens tej odpowiedzi. Dlaczego właściwie skróciłeś moją wycenę? Powiedziałem w mojej odpowiedzi dosłownie "Właściwie powinieneś wywołać srand (seed) jeden raz, a następnie wywołać rand () wiele razy i przeanalizować tę sekwencję - powinna wyglądać losowo". Czy zauważyłeś, że powinieneś wywołać rand () WIELE RAZY po pojedynczym wywołaniu srand (...)? Twoje pytanie w linku jest dokładnym duplikatem tego pytania z dokładnie tym samym nieporozumieniem.
Serge Dundich

To stara odpowiedź, ale pojawia się, gdy wyszukujesz w Google „Generowanie liczb losowych w C ++”. To kiepska rada dla programistów C ++, ponieważ radzi używać rand()i srand(). Czy możesz to zaktualizować?
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont W rzeczywistości nie zaleca się używania rand()i srand(). Właściwie to po prostu odpowiada na pytanie z podanym opisem. Z opisu (który używa rand/ srand) jasno wynika, że należy wyjaśnić podstawowe koncepcje generowania liczb pseudolosowych - podobnie jak samo znaczenie ciągu pseudolosowego i jego zalążka. Próbuję zrobić dokładnie to i użyć najbardziej prosty i znajomy rand/ srandkombinację. Zabawne jest to, że niektóre inne odpowiedzi - nawet z bardzo dużą oceną - zawierają te same nieporozumienia, co autor pytania.
Serge Dundich

@ Yakk-AdamNevraumont Skorzystałem z twojej rady i zmieniłem moją odpowiedź o kilka informacji o najnowszych dodatkach C ++. Choć uważam, że to trochę off topic - ale Twoja sugestia, jak również kilka innych odpowiedzi wskazuje, że obie stare dobre std::rand/std::srandcechy i Nowa biblioteka C ++ podoba std::random_device<>, std :: mersenne_twister_engine <> i mnóstwo losowych rozkładów wymaga pewnego wyjaśnienia.
Serge Dundich

216

Użycie modulo może wprowadzić odchylenie do liczb losowych, w zależności od generatora liczb losowych. Zobacz to pytanie, aby uzyskać więcej informacji. Oczywiście możliwe jest uzyskanie powtarzających się liczb w losowej kolejności.

Wypróbuj kilka funkcji C ++ 11 dla lepszej dystrybucji:

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

Zobacz to pytanie / odpowiedź, aby uzyskać więcej informacji na temat liczb losowych w C ++ 11. Powyższe nie jest jedynym sposobem, aby to zrobić, ale jest jednym ze sposobów.


7
Ilość odchyleń wprowadzanych przez używanie %6jest znikomo mała. Może to istotne, jeśli piszesz grę w kości do wykorzystania w Las Vegas, ale nie ma znaczenia w prawie każdym innym kontekście.
Hot Licks

9
HotLicks: zgadza się, ale jeśli używasz wersji C ++, która obsługuje random_devicei mt19937już jest, dosłownie nie ma powodu, aby nie iść na całość i nie używać standardu uniform_int_distribution.
Quuxplusone

4
Wszyscy programiści powinni doradzać ludziom, aby unikali modulo jak zarazy, ponieważ wykorzystuje dzielenie, a to kosztuje setki cykli zegara i może zepsuć synchronizację aplikacji i / lub spalić dużo energii z baterii.

3
Czy rng oznacza „zakres”?
Christoffer

4
@ ChristofferHjärtström: domena R andom n umbra g enerator.
Cornstalks

11

Jeśli korzystasz z bibliotek boost , możesz uzyskać generator losowy w następujący sposób:

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

Gdzie funkcja current_time_nanoseconds()podaje aktualny czas w nanosekundach, który jest używany jako ziarno.


Oto bardziej ogólna klasa do uzyskiwania losowych liczb całkowitych i dat w zakresie:

#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
Teraz, gdy mamy losowy jako część standardu, odradzam używanie wersji boost, chyba że używasz naprawdę starego kompilatora.
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


Jaka jest różnica w kodzie autora pytania? (Z wyjątkiem tego, że nie używasz %6.) A jeśli zdecydowałeś się użyć std::randC ++ API randfunkcji biblioteki C, to dlaczego nie użyć std::timei std::srandze względu na spójność stylu C ++?
Serge Dundich

4

Może być pełny Randomer kod klasy do generowania liczb losowych!

Jeśli potrzebujesz liczb losowych w różnych częściach projektu, możesz utworzyć oddzielną klasę, Randomeraby zawrzeć w niej wszystkie elementy random.

Coś w tym stylu:

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

Taka klasa przydałaby się później:

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

Możesz sprawdzić ten link jako przykład, jak używam takiej Randomerklasy do generowania losowych ciągów. Możesz również użyć, Randomerjeśli chcesz.


Czy nie chciałbyś ponownie użyć generatora dla wszystkich swoich losowych obiektów? Zwłaszcza, że ​​utworzenie inicjalizacji i utrzymanie jej stanu jest stosunkowo drogie.
Martin York

3

Generuj za każdym razem inną liczbę losową, a nie tę samą sześć razy z rzędu.

Scenariusz użycia

Porównałam problem Przewidywalności do worka sześciu kawałków papieru, z których każdy ma zapisaną wartość od 0 do 5. Za każdym razem, gdy wymagana jest nowa wartość, z worka wyciąga się kartkę papieru. Jeśli worek jest pusty, numery są umieszczane z powrotem w torbie.

... na tej podstawie mogę stworzyć swego rodzaju algorytm.

Algorytm

Torba jest zwykle plikiem Collection. Wybrałem bool[](znaną również jako tablica logiczna, płaszczyzna bitowa lub mapa bitowa), aby przejąć rolę worka.

Powodem, dla którego wybrałem, jest bool[]to, że indeks każdego elementu jest już wartością każdej kartki papieru. Gdyby dokumenty wymagały czegoś innego napisanego na nich, użyłbym plikuDictionary<string, bool> zamiast niego litery. Wartość logiczna służy do śledzenia, czy liczba została jeszcze narysowana, czy nie.

Wywołany licznik RemainingNumberCountjest inicjalizowany tak, aby 5odliczał w miarę wybierania liczby losowej. Dzięki temu nie musimy liczyć, ile kartek papieru pozostało za każdym razem, gdy chcemy narysować nową liczbę.

Aby wybrać następną losową wartość, używam a for..loopdo przeszukiwania worka indeksów i licznika do odliczania, gdy indexzostanie falsewywołany NumberOfMoves.

NumberOfMovessłuży do wyboru następnego dostępnego numeru. NumberOfMovesjest najpierw ustawiana na losową wartość z zakresu od 0do 5, ponieważ dostępnych jest 0..5 kroków, które możemy wykonać przez worek. W następnej iteracji NumberOfMovesjest ustawiana losowa wartość z przedziału od 0do 4, ponieważ jest teraz 0..4 kroki, które możemy wykonać przez worek. Ponieważ liczby są używane, dostępne liczby zmniejszają się, więc zamiast tego używamy rand() % (RemainingNumberCount + 1)do obliczenia następnej wartości NumberOfMoves.

Gdy NumberOfMoveslicznik osiągnie zero, for..looppowinno wyglądać następująco:

  1. Ustaw bieżącą wartość tak, aby była taka sama jak for..loopindeks.
  2. Ustaw wszystkie liczby w torbie na false.
  3. Zerwij z for..loop.

Kod

Kod powyższego rozwiązania jest następujący:

(umieść następujące trzy bloki w głównym pliku .cpp jeden po drugim)

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

    }

};

Klasa konsoli

Tworzę tę klasę Console, ponieważ ułatwia ona przekierowywanie danych wyjściowych.

Poniżej w kodzie ...

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

... można zastąpić ...

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

... a następnie tę Consoleklasę można usunąć w razie potrzeby.

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

    }

};

Główna metoda

Przykładowe użycie w następujący sposób:

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

}

Przykładowe dane wyjściowe

Po uruchomieniu programu otrzymałem następujące dane wyjściowe:

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

Oświadczenie końcowe

Ten program został napisany przy użyciu Visual Studio 2017 i zdecydowałem się na Visual C++ Windows Console Applicationprojekt przy użyciu .Net 4.6.1.

Nie robię tu nic szczególnie specjalnego, więc kod powinien działać również na wcześniejszych wersjach Visual Studio.


Jeśli jest to VS 2017, powinieneś używać najnowszej wersji biblioteki standardowej: en.cppreference.com/w/cpp/numeric/random . Obecnie ten przykład wykorzystuje funkcje biblioteki losowej C i „Nie ma gwarancji co do jakości utworzonej sekwencji losowej”.
Robert Andrzejuk

3

Ilekroć przeprowadzasz podstawowe wyszukiwanie random number generationw Internecie w języku programowania C ++, to pytanie jest zwykle wyświetlane jako pierwsze! Chcę rzucić swój kapelusz na ring, aby, miejmy nadzieję, lepiej wyjaśnić koncepcję generowania liczb pseudolosowych w C ++ dla przyszłych programistów, którzy nieuchronnie będą szukać tego samego pytania w Internecie!

Podstawy

Generowanie liczb pseudolosowych obejmuje proces wykorzystania deterministycznego algorytmu, który tworzy sekwencję liczb, których właściwości w przybliżeniu przypominają liczby losowe . Mówię, że w przybliżeniu przypominają , ponieważ prawdziwa przypadkowość jest raczej nieuchwytną tajemnicą w matematyce i informatyce. Stąd, dlaczego termin pseudolosowy jest używany, aby był bardziej pedantycznie poprawny!

Zanim będziesz mógł faktycznie użyć PRNG, tj. pseudo-random number generatorMusisz podać algorytmowi wartość początkową, często nazywaną również ziarnem . Jednak ziarno należy ustawić tylko raz przed użyciem samego algorytmu!

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

Tak więc, jeśli chcesz mieć dobrą sekwencję liczb, musisz dostarczyć wystarczającą ilość nasion do PRNG!

Stary sposób C.

Biblioteka C ++ kompatybilna wstecz ze standardem C, używa tak zwanego generatora liniowej kongruencji, który znajduje się w cstdlibpliku nagłówkowym! Ten PRNG działa poprzez nieciągłą funkcję fragmentaryczną, która wykorzystuje arytmetykę modularną, tj. Szybki algorytm, który lubi używać modulo operator '%'. Poniżej przedstawiono typowe użycie tego PRNG w odniesieniu do pierwotnego pytania zadanego przez @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;
}

Powszechne użycie PRNG C obejmuje cały szereg problemów, takich jak:

  1. Ogólny interfejs programu std::rand()nie jest zbyt intuicyjny dla prawidłowego generowania liczb pseudolosowych z podanego zakresu, np. Tworzenia liczb z zakresu [1, 6] tak, jak chciał @Predictability.
  2. Powszechne użycie std::rand()wyklucza możliwość równomiernego rozmieszczenia liczb pseudolosowych ze względu na zasadę Pigeonhole .
  3. Typowy sposób std::rand()wysiewania std::srand( ( unsigned int )std::time( nullptr ) )jest technicznie nieprawidłowy, ponieważ time_tjest uważany za typ ograniczony . Dlatego konwersja z time_tna unsigned int nie jest gwarantowana!

Aby uzyskać bardziej szczegółowe informacje na temat ogólnych problemów związanych z używaniem PRNG języka C i jak je obejść, zobacz Using rand () (C / C ++): Porady dotyczące funkcji rand () w bibliotece standardowej !

Standardowy sposób C ++

Od czasu opublikowania standardu ISO / IEC 14882: 2011, tj. C ++ 11, randombiblioteka od jakiegoś czasu jest częścią języka programowania C ++. Ta biblioteka wyposażona jest w wielu PRNGs i różnych rodzajów dystrybucji , takie jak: równomiernego rozkładu , rozkładu normalnego , rozkładu dwumianowego itp Poniższy przykład kodu źródłowego wykazuje bardzo podstawowe korzystanie z randombiblioteki, w odniesieniu do pierwotnego pytania @ przewidywalność za:

#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-bitowy Mersenne Twister silnika, z równomiernym rozkładem w całkowitoliczbowych wartości stosowano w powyższym przykładzie. (Nazwa silnika w kodzie źródłowym brzmi dziwnie, ponieważ jego nazwa pochodzi z okresu 2 ^ 19937-1). Przykład używa również std::random_devicedo zapoczątkowania silnika, który pobiera swoją wartość z systemu operacyjnego (jeśli używasz systemu Linux, std::random_devicezwraca wartość z /dev/urandom).

Zwróć uwagę, że nie musisz używać std::random_devicedo inicjowania żadnego silnika . Możesz użyć stałych lub nawet chronobiblioteki! Nie musisz też używać 32-bitowej wersji std::mt19937silnika, są inne opcje ! Więcej informacji o możliwościach randombiblioteki można znaleźć na stronie cplusplus.com

Podsumowując, programiści C ++ nie powinni std::rand()już używać , nie dlatego, że jest zły , ale dlatego, że obecny standard zapewnia lepsze alternatywy, które są prostsze i bardziej niezawodne . Mamy nadzieję, że wielu z Was uzna to za pomocne, zwłaszcza ci z Was, którzy niedawno szukali w internecie generating random numbers in c++!


2

Oto rozwiązanie. Utwórz funkcję, która zwraca liczbę losową i umieść ją poza funkcją główną, aby stała się globalna. Mam nadzieję że to pomoże

#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

Ten kod tworzy losowe liczby od ndo m.

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

przykład:

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

2
To naprawdę nie odpowiada na pytanie.
HolyBlackCat

1
Nie naprawiłeś tego. Chodzi o to, że jeśli uruchamiasz program wiele razy na sekundę, generuje on te same losowe wartości. Twój kod też to robi.
HolyBlackCat

1
@HolyBlackCat Sprawdziłem to pod kątem wielu uruchomień, działa. Czy dodałeś wcześniej srand(time(0))do głównej funkcji random(n, m)?
Amir Fo

1
Należy dodać srand(time(0))do funkcji main, a nie pętli for lub wewnątrz implementacji funkcji.
Amir Fo

1
Skopiowałem twój kod dosłownie. Czy uruchomiłeś to wiele razy na sekundę ?
HolyBlackCat

1

losowo każdy plik 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
Nie powinieneś wielokrotnie tworzyć generatora. Utrzymuje zbiór stanów, dzięki czemu generuje sekwencję liczb losowych o odpowiednim rozkładzie (aby wyglądała losowo).
Martin York

-2

Oto prosty generator losowy z ok. równe prawdopodobieństwo wygenerowania wartości dodatnich i ujemnych wokół 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;
   }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.