Làm thế nào để tạo một số ngẫu nhiên trong C ++?


150

Tôi đang cố gắng tạo ra một trò chơi với xúc xắc, và tôi cần phải có các số ngẫu nhiên trong đó (để mô phỏng các mặt của con súc sắc. Tôi biết cách làm cho nó nằm trong khoảng từ 1 đến 6). Sử dụng

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

using namespace std;

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

không hoạt động tốt lắm, vì khi tôi chạy chương trình một vài lần, đây là đầu ra tôi nhận được:

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

Vì vậy, tôi muốn một lệnh sẽ tạo ra một số ngẫu nhiên khác nhau mỗi lần, không phải là một lệnh 5 lần liên tiếp. Có một lệnh sẽ làm điều này?


54
Vấn đề phân phối sang một bên, hãy nhớ rằng với các số ngẫu nhiên có khả năng nhận được kết quả tương tự nhiều lần liên tiếp. Nếu bạn được đảm bảo không nhận được cùng một số hai lần liên tiếp, kết quả sẽ không thực sự ngẫu nhiên, phải không?
cdhowie

5
Điều gì khiến bạn nghĩ rằng những con số đó không phải là ngẫu nhiên? Ném một cái chết thực sự và bạn rất có thể có được kết quả đó. Nếu chúng được đảm bảo là khác nhau giữa mỗi lần ném thì nó sẽ không thực sự ngẫu nhiên.
mattjgalloway

2
Đồng thời đọc mãi mãiconfuzzled.com / phi / jsw_art_rand.aspx tại sao sử dụng toán tử mô đun thường không phải là một ý tưởng tốt.
Benjamin Bannier

4
Bạn đang hiểu nhầm nhiều hơn một người có thể phù hợp với một nhận xét hoặc thậm chí là một câu trả lời. Bạn cần học, một cách độc lập, về các trình tạo số giả ngẫu nhiên, về các hạt giống, về tầm quan trọng của việc chọn một hạt giống thực sự ngẫu nhiên và về các phân phối đồng đều.
Kerrek SB

20
Khi bạn gieo hạt giống với thời gian. Điều này cũng có nghĩa là nếu bạn chạy chương trình của mình nhiều hơn một lần một giây, bạn sẽ nhận được cùng một số.
Martin York

Câu trả lời:


79

Vấn đề cơ bản nhất của ứng dụng thử nghiệm của bạn là bạn gọi srandmột lần và sau đó gọi randmột lần và thoát.

Toàn bộ điểm của srandhàm là khởi tạo chuỗi các số giả ngẫu nhiên với một hạt ngẫu nhiên.

Điều đó có nghĩa là nếu bạn chuyển cùng một giá trị cho srandhai ứng dụng khác nhau (có cùng srand/ randthực hiện) thì bạn sẽ nhận được chính xác cùng một chuỗi các rand()giá trị được đọc sau đó trong cả hai ứng dụng.

Tuy nhiên, trong ví dụ ứng dụng của bạn, chuỗi giả ngẫu nhiên chỉ bao gồm một phần tử - phần tử đầu tiên của chuỗi giả ngẫu nhiên được tạo từ hạt giống với thời gian secondchính xác hiện tại . Bạn mong đợi gì để thấy trên đầu ra sau đó?

Rõ ràng khi bạn tình cờ chạy ứng dụng trong cùng một giây - bạn sử dụng cùng một giá trị hạt giống - do đó, kết quả của bạn là như nhau tất nhiên (như Martin York đã đề cập trong một nhận xét cho câu hỏi).

Trên thực tế, bạn nên gọi srand(seed)một lần và sau đó gọi rand() nhiều lần và phân tích chuỗi đó - nó sẽ trông ngẫu nhiên.

BIÊN TẬP:

Ồ tôi hiểu rồi Rõ ràng mô tả bằng lời nói là không đủ (có thể là rào cản ngôn ngữ hoặc một cái gì đó ... :)).

ĐỒNG Ý. Ví dụ mã C lỗi thời dựa trên cùng các srand()/rand()/time()chức năng đã được sử dụng trong câu hỏi:

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

^^ ^ RATNG chuỗi từ một lần chạy chương trình được cho là trông ngẫu nhiên.

EDIT2:

Khi sử dụng thư viện chuẩn C hoặc C ++, điều quan trọng là phải hiểu rằng cho đến nay không có một hàm tiêu chuẩn hoặc lớp nào tạo ra dữ liệu thực sự ngẫu nhiên một cách dứt khoát (được đảm bảo bởi tiêu chuẩn). Công cụ tiêu chuẩn duy nhất tiếp cận vấn đề này là std :: Random_device mà không may vẫn không đảm bảo tính ngẫu nhiên thực tế.

Tùy thuộc vào bản chất của ứng dụng, trước tiên bạn nên quyết định xem bạn có thực sự cần dữ liệu thực sự ngẫu nhiên (không thể đoán trước) hay không. Trường hợp đáng chú ý khi bạn chắc chắn cần sự ngẫu nhiên thực sự là bảo mật thông tin - ví dụ: tạo khóa đối xứng, khóa riêng không đối xứng, giá trị muối, mã thông báo bảo mật, v.v.

Tuy nhiên, số ngẫu nhiên cấp bảo mật là một ngành riêng biệt có giá trị một bài viết riêng.

Trong hầu hết các trường hợp, Trình tạo số giả ngẫu nhiên là đủ - ví dụ: cho các mô phỏng hoặc trò chơi khoa học. Trong một số trường hợp, chuỗi giả ngẫu nhiên được xác định nhất quán thậm chí còn được yêu cầu - ví dụ: trong các trò chơi, bạn có thể chọn tạo chính xác các bản đồ trong thời gian chạy để tránh lưu trữ nhiều dữ liệu.

Câu hỏi ban đầu và lặp lại vô số câu hỏi giống hệt / tương tự (và thậm chí nhiều câu trả lời sai cho họ) chỉ ra rằng trước hết, điều quan trọng là phải phân biệt các số ngẫu nhiên với các số giả ngẫu nhiên VÀ để hiểu chuỗi số giả ngẫu nhiên trong là gì? vị trí đầu tiên VÀ để nhận ra rằng các trình tạo số giả ngẫu nhiên KHÔNG được sử dụng giống như cách bạn có thể sử dụng các trình tạo số ngẫu nhiên thực sự.

Theo trực giác khi bạn yêu cầu số ngẫu nhiên - kết quả trả về không nên phụ thuộc vào các giá trị được trả lại trước đó và không nên phụ thuộc nếu có ai yêu cầu bất cứ điều gì trước đó và không nên phụ thuộc vào thời điểm nào và theo quy trình nào và trên máy tính nào và từ máy phát điện nào và trong thiên hà nào nó được yêu cầu. Rốt cuộc, đó là từ "ngẫu nhiên" nghĩa là gì - không thể đoán trước và độc lập với bất cứ điều gì - nếu không nó không còn là ngẫu nhiên nữa, phải không? Với trực giác này, việc tìm kiếm trên web một số phép thuật để sử dụng để có được số ngẫu nhiên như vậy trong bất kỳ bối cảnh có thể là điều tự nhiên.

^^^ RẰNG loại mong đợi trực quan là rất sai lầm và có hại trong mọi trường hợp liên quan đến Máy phát điện Pseudo-Random Number - mặc dù là hợp lý cho số ngẫu nhiên đúng.

Trong khi khái niệm có ý nghĩa về "số ngẫu nhiên" tồn tại - không có thứ gọi là "số giả ngẫu nhiên". Trình tạo số giả ngẫu nhiên thực sự tạo ra chuỗi số giả ngẫu nhiên .

Khi các chuyên gia nói về chất lượng của PRNG, họ thực sự nói về các thuộc tính thống kê của chuỗi được tạo (và các chuỗi con đáng chú ý của nó). Ví dụ: nếu bạn kết hợp hai PRNG chất lượng cao bằng cách sử dụng cả hai lần lượt - bạn có thể tạo ra chuỗi kết quả xấu - mặc dù chúng tạo ra các chuỗi tốt riêng biệt (hai chuỗi tốt đó có thể chỉ tương quan với nhau và do đó kết hợp xấu).

Trình tự giả ngẫu nhiên trong thực tế luôn luôn xác định (được xác định trước bởi thuật toán và các tham số ban đầu của nó) tức là thực sự không có gì ngẫu nhiên về nó.

Cụ thể rand()/ srand(s)cặp hàm cung cấp một chuỗi số giả ngẫu nhiên trên mỗi quy trình không an toàn (!) Được tạo bằng thuật toán xác định thực hiện. Hàm rand()tạo các giá trị trong phạm vi [0, RAND_MAX].

Trích dẫn từ tiêu chuẩn C11:

Các srandchức năng sử dụng lập luận như một hạt giống cho một chuỗi mới của số giả ngẫu nhiên để được trả lại bởi các cuộc gọi tiếp theo để rand. Nếu srandsau đó được gọi với cùng giá trị hạt giống, chuỗi số giả ngẫu nhiên sẽ được lặp lại. Nếu randđược gọi trước bất kỳ lệnh gọi nào srandđược thực hiện, trình tự tương tự sẽ được tạo như khi srandlần đầu tiên được gọi với giá trị seed là 1.

Nhiều người kỳ vọng một cách hợp lý rằng rand()sẽ tạo ra một chuỗi các số phân bố đồng nhất bán độc lập trong phạm vi 0tới RAND_MAX. Chà, chắc chắn là nó (nếu không nó vô dụng) nhưng thật không may, không chỉ tiêu chuẩn không yêu cầu điều đó - thậm chí còn có tuyên bố từ chối rõ ràng rằng "không có gì đảm bảo về chất lượng của chuỗi ngẫu nhiên được tạo ra" . Trong một số trường hợp lịch sử rand/ srandthực hiện có chất lượng rất xấu thực sự. Mặc dù trong các triển khai hiện đại rất có thể là đủ tốt - nhưng niềm tin đã bị phá vỡ và không dễ phục hồi. Bên cạnh tính chất không an toàn của luồng làm cho việc sử dụng an toàn trong các ứng dụng đa luồng trở nên khó khăn và hạn chế (vẫn có thể - bạn chỉ có thể sử dụng chúng từ một luồng chuyên dụng).

Mẫu lớp mới std :: mersenne_twister_engine <> (và typedefs tiện lợi của nó - std::mt19937/ std::mt19937_64với sự kết hợp tham số mẫu tốt) cung cấp trình tạo số giả ngẫu nhiên cho mỗi đối tượng được xác định trong tiêu chuẩn C ++ 11. Với cùng các tham số mẫu và cùng tham số khởi tạo, các đối tượng khác nhau sẽ tạo ra chính xác trình tự đầu ra cho mỗi đối tượng trên bất kỳ máy tính nào trong bất kỳ ứng dụng nào được xây dựng với thư viện chuẩn tuân thủ C ++ 11. Ưu điểm của lớp này là trình tự đầu ra chất lượng cao có thể dự đoán được và tính nhất quán đầy đủ trong các lần triển khai.

Ngoài ra, có nhiều công cụ PRNG được định nghĩa trong tiêu chuẩn C ++ 11 - std :: linear_congruential_engine <> (trước đây được sử dụng làm srand/randthuật toán chất lượng công bằng trong một số triển khai thư viện tiêu chuẩn C) và std :: subtract_with_carry_engine <> . Chúng cũng tạo ra các chuỗi đầu ra cho mỗi đối tượng phụ thuộc tham số được xác định đầy đủ.

Ví dụ hiện đại C ++ 11 thay thế cho mã C lỗi thời ở trên:

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

Phiên bản của mã trước đó sử dụng std :: thống_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;
}

Tôi đã hỏi câu hỏi tương tự trong liên kết ở đây nhưng vẫn chưa thể tìm thấy câu trả lời rõ ràng nào. Bạn có thể vui lòng chứng minh "Thực ra bạn nên gọi srand (seed) một lần và sau đó gọi rand ()" bằng mã vì tôi đã làm những gì bạn nói nhưng nó không hoạt động đúng.
bashburak

2
@bashburak Có vẻ như bạn hoàn toàn bỏ lỡ quan điểm của câu trả lời này. Tại sao chính xác bạn đã cắt trích dẫn của tôi? Tôi đã nói trong câu trả lời của mình theo nghĩa đen "Thực ra bạn nên gọi srand (hạt giống) một lần và sau đó gọi rand () nhiều lần và phân tích chuỗi đó - nó sẽ trông ngẫu nhiên." Bạn có để ý rằng bạn nên gọi rand () NHIỀU THỜI GIAN sau cuộc gọi srand (...) không? Câu hỏi của bạn trong liên kết của bạn là chính xác trùng lặp của câu hỏi này với sự hiểu lầm chính xác tương tự.
Serge Dundich

Đây là một câu trả lời cũ, nhưng nó xuất hiện khi bạn google "Tạo số ngẫu nhiên C ++". Đó là lời khuyên tồi cho các lập trình viên C ++, vì nó khuyên bạn nên sử dụng rand()srand(). Bạn có thể cập nhật nó?
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont Nó không thực sự khuyên bạn nên sử dụng rand()srand(). Trong thực tế, nó chỉ trả lời câu hỏi với mô tả được cung cấp. Rõ ràng từ mô tả (sử dụng rand/ srand) rằng các khái niệm cơ bản về tạo số giả ngẫu nhiên cần được giải thích - giống như ý nghĩa của chuỗi giả ngẫu nhiên và hạt giống của nó. Tôi đang cố gắng thực hiện chính xác điều đó và sử dụng kết hợp rand/ đơn giản và quen thuộc nhất srand. Điều buồn cười là một số câu trả lời khác - ngay cả với đánh giá rất lớn - phải chịu những hiểu lầm tương tự như tác giả của câu hỏi.
Serge Dundich

@ Yakk-AdamNevraumont Tôi đã tư vấn và sửa đổi câu trả lời của tôi với một số thông tin về các bổ sung C ++ mới nhất. Mặc dù tôi cho rằng điều này hơi lạc đề - nhưng gợi ý của bạn cũng như một số câu trả lời khác chỉ ra rằng cả hai std::rand/std::srandtính năng thư viện C ++ cũ và mới như std::random_device<>, std :: mersenne_twister_engine <> và vô số phân phối ngẫu nhiên yêu cầu một số giải thích.
Serge Dundich

214

Sử dụng modulo có thể giới thiệu sai lệch thành các số ngẫu nhiên, tùy thuộc vào trình tạo số ngẫu nhiên. Xem câu hỏi này để biết thêm. Tất nhiên, hoàn toàn có thể nhận được các số lặp lại theo một chuỗi ngẫu nhiên.

Hãy thử một số tính năng của C ++ 11 để phân phối tốt hơn:

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

Xem câu hỏi / câu trả lời này để biết thêm thông tin về các số ngẫu nhiên C ++ 11. Trên đây không phải là cách duy nhất để làm điều này, nhưng là một cách.


7
Lượng sai lệch được giới thiệu bằng cách sử dụng %6rất nhỏ. Có thể có ý nghĩa nếu bạn đang viết một trò chơi craps được sử dụng ở Las Vegas, nhưng không có kết quả trong hầu hết các bối cảnh khác.
Hot Licks

9
HotLicks: đã đồng ý, nhưng nếu bạn đang sử dụng phiên bản C ++ hỗ trợ random_devicemt19937đã có, thì không có lý do gì để không sử dụng hết và cũng sử dụng tiêu chuẩn uniform_int_distribution.
Quuxplusone

4
Tất cả các lập trình viên nên khuyên mọi người tránh modulo như bệnh dịch hạch vì nó sử dụng phép chia và tốn hàng trăm chu kỳ đồng hồ và có thể làm rối loạn thời gian ứng dụng của bạn và / hoặc đốt cháy rất nhiều pin.

3
Là rng cho "phạm vi"?
Christoffer

4
@ ChristofferHjärtström: Nó dành cho r andom n umber g enerator.
Bắp ngô

11

Nếu bạn đang sử dụng tăng libs bạn có thể có được một máy phát điện ngẫu nhiên theo cách này:

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

Trong đó hàm current_time_nanoseconds()cho thời gian hiện tại tính bằng nano giây được sử dụng làm hạt giống.


Dưới đây là một lớp tổng quát hơn để lấy số nguyên và ngày ngẫu nhiên trong một phạm vi:

#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
Bây giờ chúng tôi có ngẫu nhiên là một phần của tiêu chuẩn, tôi sẽ không khuyến khích việc sử dụng phiên bản boost trừ khi bạn đang sử dụng một trình biên dịch thực sự cũ.
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


Sự khác biệt với mã của tác giả câu hỏi là gì? (Ngoại trừ việc bạn không sử dụng %6.) Và nếu bạn đã quyết định sử dụng chức năng thư viện std::randC ++ API của randC thì tại sao không sử dụng std::timestd::srandvì sự nhất quán của phong cách C ++?
Serge Dundich

4

Có thể lấy Randomermã lớp đầy đủ để tạo số ngẫu nhiên từ đây!

Nếu bạn cần số ngẫu nhiên trong các phần khác nhau của dự án, bạn có thể tạo một lớp riêng Randomerđể bao gồm tất cả những randomthứ bên trong nó.

Một cái gì đó như thế:

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

Một lớp học như vậy sẽ có ích sau này:

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

Bạn có thể kiểm tra liên kết này như một ví dụ về cách tôi sử dụngRandomer lớp như vậy để tạo các chuỗi ngẫu nhiên. Bạn cũng có thể sử dụng Randomernếu bạn muốn.


Bạn có muốn sử dụng lại trình tạo cho tất cả các đối tượng Randomer của mình không? Đặc biệt là vì nó tương đối tốn kém để tạo khởi tạo và duy trì trạng thái của nó.
Martin York

3

Tạo một số ngẫu nhiên khác nhau mỗi lần, không phải cùng một sáu lần liên tiếp.

Kịch bản ca sử dụng

Tôi đã ví vấn đề của Dự đoán với một túi sáu mẩu giấy, mỗi tờ có giá trị từ 0 đến 5 được viết trên đó. Một mảnh giấy được rút ra từ túi mỗi lần yêu cầu một giá trị mới. Nếu túi rỗng, sau đó các số được đặt lại vào túi.

... Từ đây, tôi có thể tạo ra một thuật toán sắp xếp.

Thuật toán

Một cái túi thường là a Collection. Tôi đã chọn một bool[](còn được gọi là mảng boolean, mặt phẳng bit hoặc bản đồ bit) để đảm nhận vai trò của túi.

Lý do tôi chọn bool[]là vì chỉ mục của từng mục đã là giá trị của từng tờ giấy. Nếu các giấy tờ yêu cầu bất cứ điều gì khác được viết trên chúng thì tôi đã sử dụng một Dictionary<string, bool>vị trí của nó. Giá trị boolean được sử dụng để theo dõi xem số đã được rút ra hay chưa.

Một bộ đếm được gọi RemainingNumberCountđược khởi tạo để 5đếm ngược như một số ngẫu nhiên được chọn. Điều này giúp chúng tôi không phải đếm số lượng giấy còn lại mỗi lần chúng tôi muốn vẽ một số mới.

Để chọn giá trị ngẫu nhiên tiếp theo tôi đang sử dụng for..loopđể quét qua các túi chỉ mục và bộ đếm để đếm ngược khi indexđược falsegọi NumberOfMoves.

NumberOfMovesđược sử dụng để chọn số có sẵn tiếp theo. NumberOfMovesđầu tiên được đặt là một giá trị ngẫu nhiên giữa 05, bởi vì có 0..5 bước có sẵn mà chúng ta có thể thực hiện thông qua túi. Ở lần lặp tiếp theo NumberOfMovesđược đặt là một giá trị ngẫu nhiên giữa 04, bởi vì hiện có 0..4 bước chúng ta có thể thực hiện thông qua túi. Khi các số được sử dụng, các số có sẵn giảm đi, thay vào đó chúng tôi sử dụng rand() % (RemainingNumberCount + 1)để tính giá trị tiếp theo cho NumberOfMoves.

Khi bộ NumberOfMovesđếm đạt đến 0, for..loopthì như sau:

  1. Đặt Giá trị hiện tại giống với for..loopchỉ mục của.
  2. Đặt tất cả các số trong túi thành false.
  3. Phá vỡ từ for..loop.

Mã cho giải pháp trên như sau:

(đặt ba khối sau vào tệp .cpp chính lần lượt)

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

    }

};

Một lớp Console

Tôi tạo lớp Console này vì nó giúp dễ dàng chuyển hướng đầu ra.

Dưới đây trong mã ...

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

...có thể được thay thế bởi...

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

... và sau đó Consolelớp này có thể bị xóa nếu muốn.

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

    }

};

Phương pháp chính

Ví dụ sử dụng như sau:

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

}

Ví dụ đầu ra

Khi tôi chạy chương trình, tôi nhận được kết quả sau:

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

Tuyên bố đóng cửa

Chương trình này được viết bằng Visual Studio 2017 và tôi đã chọn biến nó thành một Visual C++ Windows Console Applicationdự án bằng cách sử dụng .Net 4.6.1.

Tôi không làm gì đặc biệt ở đây, vì vậy mã cũng hoạt động trên các phiên bản trước của Visual Studio.


Nếu đây là VS 2017, bạn nên sử dụng phiên bản mới nhất của thư viện chuẩn: en.cppreference.com/w/cpp/numeric/random . Hiện tại ví dụ này sử dụng các hàm thư viện ngẫu nhiên C và "Không có gì đảm bảo về chất lượng của chuỗi ngẫu nhiên được tạo ra".
Robert Andrzejuk

2

Đây là một giải pháp. Tạo một hàm trả về số ngẫu nhiên và đặt nó bên ngoài hàm chính để làm cho nó toàn cầu. Hi vọng điêu nay co ich

#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

Mã này tạo ra các số ngẫu nhiên từ nđến m.

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

thí dụ:

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

2
Điều này không thực sự trả lời câu hỏi.
HolyBlackCat

1
Bạn đã không sửa nó. Điểm của câu hỏi là nếu bạn chạy chương trình nhiều lần trong một giây, thì nó sẽ tạo ra các giá trị ngẫu nhiên giống nhau. Mã của bạn cũng làm điều đó.
HolyBlackCat

1
@HolyBlackCat Tôi đã kiểm tra nó cho nhiều lần chạy, nó hoạt động. Bạn đã thêm vào srand(time(0))chức năng chính trước đây random(n, m)?
Amir cho

1
Bạn nên thêm srand(time(0))vào chức năng chính không cho vòng lặp hoặc bên trong thực hiện chức năng.
Amir cho

1
Tôi đã sao chép nguyên văn mã của bạn. Bạn đã chạy nó nhiều lần trong một giây ?
HolyBlackCat

2

Bất cứ khi nào bạn thực hiện tìm kiếm web cơ bản random number generationbằng ngôn ngữ lập trình C ++, câu hỏi này thường là câu hỏi đầu tiên xuất hiện! Tôi muốn ném chiếc mũ của mình vào vòng để hy vọng làm rõ hơn khái niệm tạo số giả ngẫu nhiên trong C ++ cho các lập trình viên tương lai chắc chắn sẽ tìm kiếm câu hỏi tương tự này trên web!

Những thứ cơ bản

Tạo số giả ngẫu nhiên liên quan đến quá trình sử dụng thuật toán xác định tạo ra một chuỗi các số có các thuộc tính gần giống với các số ngẫu nhiên . Tôi nói gần giống , bởi vì tính ngẫu nhiên thực sự là một bí ẩn khá khó nắm bắt trong toán học và khoa học máy tính. Do đó, tại sao thuật ngữ giả ngẫu nhiên được sử dụng để chính xác hơn về mặt giáo dục!

Trước khi bạn thực sự có thể sử dụng PRNG, tức là, pseudo-random number generatorbạn phải cung cấp thuật toán với giá trị ban đầu thường được gọi là hạt giống . Tuy nhiên, hạt giống chỉ phải được đặt một lần trước khi sử dụng thuật toán chính nó!

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

Vì vậy, nếu bạn muốn có một chuỗi số tốt, thì bạn phải cung cấp một hạt giống dồi dào cho PRNG!

Con đường cũ

Thư viện chuẩn C tương thích ngược mà C ++ có, sử dụng cái được gọi là trình tạo cộng hưởng tuyến tính được tìm thấy trong cstdlibtệp tiêu đề! PRNG này hoạt động thông qua một hàm piecewise không liên tục sử dụng số học mô-đun, tức là một thuật toán nhanh thích sử dụng modulo operator '%'. Sau đây là cách sử dụng phổ biến của PRNG này, liên quan đến câu hỏi ban đầu được hỏi bởi @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;
}

Việc sử dụng phổ biến của PRNG của C chứa rất nhiều vấn đề như:

  1. Giao diện tổng thể std::rand()không trực quan cho việc tạo các số giả ngẫu nhiên thích hợp giữa một phạm vi nhất định, ví dụ: tạo ra các số giữa [1, 6] theo cách mà @Predictability muốn.
  2. Việc sử dụng phổ biến std::rand()loại bỏ khả năng phân phối đồng đều các số giả ngẫu nhiên, vì Nguyên tắc Pigeonhole .
  3. Cách phổ biến std::rand()được gieo mầm thông qua std::srand( ( unsigned int )std::time( nullptr ) )kỹ thuật là không chính xác, bởi vì time_tđược coi là một loại hạn chế . Do đó, việc chuyển đổi từ time_tsang unsigned int không được đảm bảo!

Để biết thông tin chi tiết hơn về các vấn đề chung của việc sử dụng PRNG của C và cách khắc phục chúng, vui lòng tham khảo Sử dụng rand () (C / C ++): Lời khuyên cho chức năng rand () của thư viện chuẩn C !

Cách C ++ chuẩn

Kể từ khi tiêu chuẩn ISO / IEC 14882: 2011 được công bố, tức là C ++ 11, randomthư viện đã tách rời khỏi ngôn ngữ lập trình C ++ trong một thời gian. Thư viện này được trang bị nhiều PRNG và các loại phân phối khác nhau như: phân phối đồng đều , phân phối bình thường , phân phối nhị thức , v.v. Ví dụ về mã nguồn sau đây cho thấy cách sử dụng rất cơ bản của randomthư viện, liên quan đến câu hỏi ban đầu của @ Dự đoán:

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

Công cụ Mersenne Twister 32 bit , với sự phân bố đồng đều các giá trị số nguyên đã được sử dụng trong ví dụ trên. (Tên của công cụ trong mã nguồn nghe có vẻ lạ, vì tên của nó xuất phát từ thời kỳ 2 ^ 19937-1). Ví dụ này cũng sử dụng std::random_deviceđể tạo mầm cho động cơ, lấy giá trị của nó từ hệ điều hành (Nếu bạn đang sử dụng hệ thống Linux, thì std::random_devicesẽ trả về một giá trị từ đó /dev/urandom).

Hãy lưu ý rằng bạn không phải sử dụng std::random_deviceđể gieo bất kỳ động cơ nào . Bạn có thể sử dụng hằng hoặc thậm chí chronothư viện! Bạn cũng không phải sử dụng phiên bản 32 bit của std::mt19937động cơ, có các tùy chọn khác ! Để biết thêm thông tin về khả năng của randomthư viện, vui lòng tham khảo cplusplus.com

Nói chung, các lập trình viên C ++ không nên sử dụng std::rand()nữa, không phải vì nó xấu , mà vì tiêu chuẩn hiện tại cung cấp các lựa chọn thay thế tốt hơn, thẳng hơn và đáng tin cậy hơn . Hy vọng, nhiều bạn thấy điều này hữu ích, đặc biệt là những bạn gần đây đã tìm kiếm trên web generating random numbers in c++!


1

ngẫu nhiên mỗi tệp 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
Bạn không được phép tạo trình tạo nhiều lần. Nó duy trì một loạt các trạng thái để nó tạo ra một chuỗi các số ngẫu nhiên có phân phối thích hợp (để làm cho nó trông ngẫu nhiên).
Martin York

-2

Đây là một trình tạo ngẫu nhiên đơn giản với khoảng. xác suất bằng nhau của việc tạo ra các giá trị dương và âm khoảng 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;
   }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.