srand () - tại sao chỉ gọi nó một lần?


78

Câu hỏi này là về một nhận xét trong câu hỏi này Cách đề xuất để khởi tạo srand? Nhận xét đầu tiên nói rằng srand()chỉ nên được gọi là MỘT LẦN trong một ứng dụng. Tại sao nó như vậy?


cố gắng, trong vòng một, gọi srand và sau đó rand
Foo Bah

8

Câu trả lời:


111

Điều đó phụ thuộc vào những gì bạn đang cố gắng đạt được.

Ngẫu nhiên hóa được thực hiện như một hàm có giá trị bắt đầu, cụ thể là hạt giống .

Vì vậy, đối với cùng một hạt giống, bạn sẽ luôn nhận được cùng một chuỗi giá trị.

Nếu bạn cố gắng đặt hạt giống mỗi khi bạn cần một giá trị ngẫu nhiên, và hạt giống là cùng một số, bạn sẽ luôn nhận được cùng một giá trị "ngẫu nhiên".

Hạt giống thường được lấy từ thời điểm hiện tại, là giây, như trong time(NULL), vì vậy nếu bạn luôn đặt hạt giống trước khi lấy số ngẫu nhiên, bạn sẽ nhận được cùng một số miễn là bạn gọi combo srand / rand nhiều lần trong cùng giây .

Để tránh vấn đề này, srand chỉ được đặt một lần cho mỗi ứng dụng, vì có thể nghi ngờ rằng hai trong số các phiên bản ứng dụng sẽ được khởi tạo trong cùng một giây, vì vậy mỗi phiên bản sẽ có một chuỗi số ngẫu nhiên khác nhau.

Tuy nhiên, có một chút khả năng là bạn sẽ chạy ứng dụng của mình (đặc biệt nếu đó là một ứng dụng ngắn, hoặc một công cụ dòng lệnh hoặc thứ gì đó tương tự) nhiều lần trong một giây, khi đó bạn sẽ phải sử dụng một số cách khác để chọn seed (trừ khi bạn chấp nhận cùng một chuỗi trong các trường hợp ứng dụng khác nhau). Nhưng như tôi đã nói, điều đó phụ thuộc vào ngữ cảnh sử dụng ứng dụng của bạn.

Ngoài ra, bạn có thể muốn cố gắng tăng độ chính xác lên micro giây (giảm thiểu cơ hội của cùng một hạt giống), yêu cầu ( sys/time.h):


3
Lưu ý bên: gettimeofdayđã lỗi thời trong POSIX 2008. Thay vào đó, nó giới thiệu clock_gettimecó thể yêu cầu liên kết với -lrt. Tuy nhiên, nó có thể chưa có sẵn trên nhiều nền tảng. Trong Linux, điều này là ổn. Trên Mac, tôi nghĩ rằng nó chưa có sẵn. Trong Windows, nó có thể sẽ không bao giờ có sẵn.
Shahbaz

1
t1.tv_usec là một int dài và srand nhận đầu vào là một int không dấu. (Và tôi vừa gặp phải một vấn đề mà nó tạo ra sự khác biệt.)
Jiminion

Đó là mẹo. Bằng cách tăng độ chính xác, nó đã loại bỏ các bản sao của tôi. Cảm ơn bạn rất nhiều. Tôi có thời hạn để giao hàng và điều này đã cứu tôi.
Beezer

24

Số ngẫu nhiên thực sự là giả ngẫu nhiên. Một hạt giống được đặt trước, từ đó mỗi lệnh gọi củarand nhận được một số ngẫu nhiên, và sửa đổi trạng thái bên trong và trạng thái mới này được sử dụng trong lần randgọi tiếp theo để nhận một số khác. Bởi vì một công thức nhất định được sử dụng để tạo ra những "số ngẫu nhiên" này, do đó việc đặt một giá trị nhất định của hạt giống sau mỗi lần gọi tới randsẽ trả về cùng một số từ cuộc gọi. Ví dụ srand (1234); rand ();sẽ trả về cùng một giá trị. Khởi tạo khi trạng thái ban đầu với giá trị gốc sẽ tạo ra đủ số ngẫu nhiên khi bạn không đặt trạng thái bên trong srand, do đó làm cho các số có khả năng trở thành ngẫu nhiên hơn.

Nói chung, chúng tôi sử dụng time (NULL)giá trị giây trả về khi khởi tạo giá trị hạt giống. Nóisrand (time (NULL)); là trong một vòng lặp. Sau đó, vòng lặp có thể lặp lại nhiều lần trong một giây, do đó, số lần vòng lặp lặp lại bên trong vòng lặp trong lần randgọi thứ hai trong vòng lặp sẽ trả về cùng một "số ngẫu nhiên", không mong muốn. Khởi tạo nó một lần khi bắt đầu chương trình sẽ thiết lập hạt giống một lần và mỗi lần randđược gọi, một số mới được tạo ra và trạng thái bên trong được sửa đổi, vì vậy lần gọi tiếp theo randtrả về một số đủ ngẫu nhiên.

Ví dụ mã này từ http://linux.die.net/man/3/rand :

Trạng thái nội bộ nextđược khai báo là toàn cục. Mỗimyrand cuộc gọi sẽ sửa đổi trạng thái bên trong và cập nhật nó, đồng thời trả về một số ngẫu nhiên. Mỗi cuộc gọi của myrandsẽ có một nextgiá trị khác nhau do đó phương thức sẽ trả về các số khác nhau mỗi lần gọi.

Nhìn vào việc mysrandthực hiện; nó chỉ đơn giản là đặt giá trị hạt giống mà bạn chuyển đến next. Do đó nếu bạn đặtnext giá trị giống nhau mọi lúc trước khi gọi randnó sẽ trả về cùng một giá trị ngẫu nhiên, vì công thức giống hệt nhau được áp dụng trên nó, điều này không mong muốn, vì hàm được tạo thành ngẫu nhiên.

Nhưng tùy thuộc vào nhu cầu của bạn, bạn có thể đặt hạt giống thành một số giá trị nhất định để tạo ra cùng một "chuỗi ngẫu nhiên" cho mỗi lần chạy, chẳng hạn như đối với một số điểm chuẩn hoặc một số điểm khác.


Ý bạn không phải là (hạt dài không dấu) cho tham số của mysrand ()?
Jiminion

@Jiminion Đây là đoạn mã từ man srand. Phạm vi từ 0 đến 32767 (giả sử RAND_MAX), nhỏ hơn nhiều so với longphạm vi. Biến trạng thái nextđược thực hiện longkhi phép nhân và phép cộng bên trong sẽ vượt quá phạm vi của an unsigned int. Sau đó, kết quả được chia tỷ lệ hoặc sửa đổi trong phạm vi được chỉ định ở trên. Mặc dù bạn có thể tạo ra hạt giống long.
phoxis

1
Lưu ý rằng tiêu chuẩn C cũng bao gồm đoạn mã được hiển thị.
Jonathan Leffler

12

Trả lời ngắn gọn: gọi điện thoại srand()không giống như "cán dice" cho bộ tạo số ngẫu nhiên. Nó cũng không giống như xáo trộn một bộ bài. Nếu bất cứ điều gì, nó giống như chỉ cắt một bộ bài.

Hãy nghĩ về nó như thế này. rand()giao dịch từ một bộ bài lớn và mỗi khi bạn gọi nó, tất cả những gì nó làm là chọn một lá bài tiếp theo ở đầu bộ bài, cung cấp cho bạn giá trị và trả lại lá bài đó về cuối bộ bài. (Đúng, điều đó có nghĩa là chuỗi "ngẫu nhiên" sẽ lặp lại sau một thời gian. Tuy nhiên, đó là một bộ bài rất lớn: thường là 4,294,967,296 thẻ.)

Hơn nữa, mỗi khi chương trình của bạn chạy, một gói thẻ hoàn toàn mới được mua từ cửa hàng trò chơi mọi gói thẻ hoàn toàn mới luôn có cùng một trình tự. Vì vậy, trừ khi bạn làm điều gì đó đặc biệt, mỗi khi chương trình của bạn chạy, nó sẽ nhận lại chính xác các số "ngẫu nhiên" từ đó rand().

Bây giờ, bạn có thể nói, "Được rồi, vậy làm cách nào để xáo trộn bộ bài?" Và câu trả lời - ít nhất là trong chừng mực randsrandcó liên quan - là không có cách nào để xáo trộn bộ bài.

Vậy srandlàm gì? Dựa trên phép loại suy mà tôi đã xây dựng ở đây, cách gọi srand(n)về cơ bản giống như nói, "cắt các nlá bài từ trên xuống". Nhưng chờ đợi, một điều nữa: nó thực sự bắt đầu với một bộ bài mới toanh khác và cắt ncác lá bài từ trên xuống .

Vì vậy, nếu bạn gọi srand(n), rand(), srand(n), rand(), ..., với cùng nmọi thời gian, bạn sẽ không chỉ nhận được một chuỗi không-rất-ngẫu nhiên, bạn sẽ thực sự nhận được số lượng lại tương tự từ rand()mỗi lần. (Có thể không phải là cùng một số bạn đã trao srand, nhưng cùng một số trở lại từ randnhiều lần.)

Vì vậy, tốt nhất bạn có thể làm là cắt bộ bài một lần , nghĩa là, gọi srand()một lần, vào đầu chương trình của bạn, với một nđiều đó là ngẫu nhiên hợp lý, để bạn sẽ bắt đầu ở một nơi ngẫu nhiên khác trong bộ bài lớn mỗi lần chương trình chạy. Với rand(), đó thực sự là điều tốt nhất bạn có thể làm.

[Tái bút Vâng, tôi biết, trong cuộc sống thực, khi bạn mua một bộ bài hoàn toàn mới, nó thường theo thứ tự, không phải thứ tự ngẫu nhiên. Để sự tương tự ở đây hoạt động, tôi đang tưởng tượng rằng mỗi bộ bài bạn mua từ cửa hàng trò chơi có thứ tự dường như ngẫu nhiên, nhưng cùng một thứ tự dường như ngẫu nhiên như mọi bộ bài khác mà bạn mua từ cùng một cửa hàng đó. Sắp xếp giống như bộ bài xáo trộn giống hệt bộ bài mà họ sử dụng trong các giải đấu cầu.]


Lời giải thích tuyệt vời Steve.
DrunkenMaster

8

Lý do là srand()đặt trạng thái ban đầu của trình tạo ngẫu nhiên và tất cả các giá trị mà trình tạo tạo ra chỉ "đủ ngẫu nhiên" nếu bạn không tự mình chạm vào trạng thái ở giữa.

Ví dụ bạn có thể làm:

và sau đó nếu bạn gọi hàm đó lặp đi lặp lại để time()trả về các giá trị giống nhau trong các lệnh gọi liền kề, bạn chỉ nhận được cùng một giá trị được tạo ra - đó là do thiết kế.


3

Như đã thấy, một giải pháp đơn giản hơn để sử dụng srand()tạo các hạt giống khác nhau cho các phiên bản ứng dụng chạy cùng một giây.

Phương pháp này làm cho hạt giống của bạn rất gần với ngẫu nhiên vì không có cách nào để đoán lúc nào chuỗi của bạn bắt đầu và pid cũng sẽ khác.


2

srand gieo hạt tạo số giả ngẫu nhiên. Nếu bạn gọi nó nhiều lần, bạn sẽ gửi lại RNG. Và nếu bạn gọi nó với cùng một đối số, nó sẽ khởi động lại cùng một chuỗi.

Để chứng minh điều đó, nếu bạn làm điều gì đó đơn giản như:

bạn sẽ thấy cùng một số được in 100 lần.


2
Câu hỏi là về C, không phải C ++.
Spikatrix,

0

1 \ Dường như mỗi khi rand () chạy, nó sẽ đặt một hạt giống mới cho rand () tiếp theo.

2 \ Nếu srand () chạy nhiều lần, vấn đề là nếu hai lần chạy xảy ra trong một giây (thời gian (NULL) không thay đổi), thì rand () tiếp theo sẽ giống như rand () ngay sau lần trước srand ().


Điểm chính là khởi tạo srand()nhiều lần với cùng một hạt giống sẽ dẫn đến các giá trị giống hệt nhau được trả về rand().
King Thrushbeard
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.