2013-06-25 33 views
12

Tôi vừa mới học về chức năng random.Cố gắng tìm ra hàm `ngẫu nhiên` trong Haskell

Để hiểu biết của tôi, hàm random có giá trị loại là trường hợp RandomGen và trả về giá trị ngẫu nhiên có giá trị chúng tôi có thể chỉ định. Mặt khác, mksdGen mất một số Int và tạo một trình tạo ngẫu nhiên có nhu cầu chức năng random.

Tôi đã cố tạo ngẫu nhiên các giá trị Bool. Để làm điều đó, tôi đã thực hiện một hàm randomBool.

randomBool :: Int -> Bool 
randomBool = fst . random . mkStdGen 

Sau đó, tôi tìm thấy nhiều hơn True s hơn False s với số lượng nhỏ. Và tôi đã tò mò về nó và kiểm tra như sau

> length $ takeWhile randomBool [1..] 
53667 

Tôi nghĩ rằng điều này có nghĩa rằng đối với 53.667 số nguyên dương đầu tiên, random . mkStdGen lợi nhuận True, mà dường như không thể rất ngẫu nhiên đối với tôi. Nó rất bình thường? Hoặc tôi đang làm điều gì đó sai khiến cho True xảy ra dễ dàng hơn?

Trả lời

2

Trình tạo ngẫu nhiên được tạo bởi mkStdGen không nhất thiết tạo ra giá trị ngẫu nhiên làm kết quả đầu tiên. Để tạo số ngẫu nhiên tiếp theo, hãy sử dụng trình tạo ngẫu nhiên được trả về bởi lệnh gọi random trước đó. Ví dụ, mã này tạo ra 10 Bool s.

take 10 $ unfoldr (Just . random) (mkStdGen 1) :: [Bool] 
+9

Xây dựng về vấn đề này: máy phát điện ngẫu nhiên không được bảo đảm để có một phân phối ngẫu nhiên trên các giá trị hạt giống, nhưng nó được đảm bảo phân phối ngẫu nhiên qua các chuỗi giá trị được tạo ra. – mange

16

Thông thường, khi bạn gọi mkStdGen với các hạt gần nhau, bạn sẽ nhận được hai máy phát 'tương tự'. Trong ví dụ của bạn, bạn đang thực sự tạo ra các máy phát mới cho mỗi hạt giống được cung cấp, và vì những hạt giống đó là 1, 2, 3, v.v., chúng sẽ tạo ra các luồng tương tự.

Khi bạn gọi random với một máy phát điện, bạn thực sự có được trở lại một máy phát điện mới tại phần tử thứ hai của cặp:

Prelude System.Random> random (mkStdGen 100) :: (Bool, StdGen) 
(True,4041414 40692) 

Vì vậy, một ý tưởng tốt là sử dụng máy phát điện cung cấp này cho cuộc gọi tiếp theo của bạn để random . Ví dụ:

Prelude System.Random> let (i, gen0) = random (mkStdGen 100) :: (Bool, StdGen) 
Prelude System.Random> let (j, gen1) = random gen0   :: (Bool, StdGen) 
Prelude System.Random> let (k, gen2) = random gen1   :: (Bool, StdGen) 
Prelude System.Random> (i, j, k) 
(True, False, False) 

Vì vậy, để thực hiện một loạt các giá trị ngẫu nhiên, bạn muốn vượt qua các máy phát điện như nhà nước. Bạn có thể thiết lập này bằng tay thông qua một đơn nguyên State hoặc một cái gì đó, hoặc chỉ cần sử dụng chức năng randoms, nơi quản lý đi qua các trạng thái máy phát điện dành cho bạn:

Prelude System.Random> take 10 $ randoms (mkStdGen 100) :: [Bool] 
[True,False,False,False,False,True,True,False,False,True] 

Nếu bạn không đặc biệt quan tâm về cuộc sống ở IO (nó sẽ xảy ra), bạn có thể sử dụng randomIO:

Prelude System.Random> import Control.Monad 
Prelude System.Random Control.Monad> replicateM 10 randomIO :: IO [Bool] 
[True,True,False,True,True,False,False,False,True,True] 

This section của LYAH có thể là một đọc hữu ích.

+0

Cảm ơn bạn đã trả lời. Tôi thực sự đã đưa ra câu hỏi này sau khi đọc LYAH, nơi tôi thấy rằng đồng tiền đầu tiên trong hầu hết các trường hợp là 'True'. (Khi tôi kiểm tra vài nghìn trường hợp đầu tiên, tôi nhận được nhiều hơn 'True' hơn' False') Hm ... Mặc dù hạt giống 'gần' tạo ra máy phát 'tương tự', tôi vẫn cảm thấy thật kỳ quặc khi chương trình 'True' lên tới 50 nghìn con số liên tiếp ... – Tengu

+1

Vấn đề là ở chỗ ~ 50000 máy phát điện từ 1-50000 tương tự nhau sao cho phần tử đầu tiên mà chúng tạo ra là 'True'. Hãy thử 'random (mkStdGen 100000) :: (Bool, StdGen)' để quan sát một máy phát điện ban đầu tạo ra một 'False'. Điều quan trọng là bạn nên thực sự chỉ * hạt giống * một máy phát điện duy nhất, chứ không phải là hạt giống một loạt các máy phát điện với các giá trị tương tự. Sử dụng bất kỳ hạt giống nào bạn muốn, nhưng sau đó sử dụng 'tạo ra máy phát điện' để tạo ra các giá trị ngẫu nhiên bổ sung. – jtobin

3

Máy tính xác định và không thể tạo số ngẫu nhiên. Thay vào đó, chúng dựa vào các công thức toán học trả về phân phối các số trông ngẫu nhiên.Chúng được gọi là các trình tạo số giả ngẫu nhiên. Tuy nhiên, do tính quyết định, chúng ta có vấn đề là nếu chúng ta chạy các công thức này theo cách tương tự trong mỗi lần gọi chương trình của chúng ta, chúng ta sẽ nhận được cùng một trình tạo số ngẫu nhiên. Rõ ràng, điều này là không tốt, vì chúng tôi muốn số của chúng tôi là ngẫu nhiên! Vì vậy, chúng ta phải cung cấp cho trình tạo ngẫu nhiên giá trị hạt giống ban đầu thay đổi từ chạy đến chạy. Đối với hầu hết mọi người (tức là, những người không làm công cụ mã hóa), trình tạo số ngẫu nhiên được gieo vào thời điểm hiện tại. Trong Haskell, trình tạo giả ngẫu nhiên này được thể hiện bằng loại StdGen. Hàm mkStdGen được sử dụng để tạo trình tạo số ngẫu nhiên với một hạt giống. Không giống như C, nơi có một trình tạo số ngẫu nhiên toàn cục, trong Haskell, bạn có thể có bao nhiêu tùy thích và bạn có thể tạo chúng bằng các hạt giống khác nhau. Tuy nhiên, có một thông báo trước: vì các con số là giả ngẫu nhiên, không có gì đảm bảo rằng các trình tạo số ngẫu nhiên được tạo ra với các số hạt giống khác nhau trả về ngẫu nhiên so với số khác. Điều này có nghĩa rằng khi bạn gọi randomBool và cung cấp cho nó giá trị hạt giống liên tiếp, không có gì đảm bảo rằng số bạn nhận được từ số StdGen bạn tạo là ngẫu nhiên so với số StdGen được ghép với người kế thừa. Đây là lý do tại sao bạn nhận được gần 50000 True của.

Để nhận dữ liệu thực sự trông ngẫu nhiên, bạn cần tiếp tục sử dụng cùng trình tạo số ngẫu nhiên. Nếu bạn thấy, hàm random Haskell có loại StdGen -> (a, StdGen). Bởi vì Haskell là tinh khiết, chức năng random có một bộ tạo số ngẫu nhiên, tạo ra một giá trị giả ngẫu nhiên (phần tử đầu tiên của giá trị trả về) và sau đó trả về một số mới StdGen đại diện cho bộ tạo hạt giống với hạt giống ban đầu, nhưng sẵn sàng cho số ngẫu nhiên mới. Bạn cần giữ lại số StdGen khác xung quanh và chuyển nó đến hàm random tiếp theo để nhận dữ liệu ngẫu nhiên.

Dưới đây là ví dụ, tạo ba bool ngẫu nhiên, a, bc.

randomBools :: StdGen -> (Bool, Bool, Bool) 
randomBools gen = let (a, gen') = random gen 
         (b, gen'') = random gen'' 
         (c, gen''') = random gen''' 
        in (a, b, c) 

Lưu ý cách biến số gen được "luồn" thông qua các cuộc gọi đến ngẫu nhiên.

Bạn có thể đơn giản hóa trạng thái vượt qua bằng cách sử dụng đơn vị trạng thái. Ví dụ,

import Control.Monad.State 
import System.Random 

type MyRandomMonad a = State StdGen a 

myRandom :: Random a => MyRandomMonad a 
myRandom = do 
    gen <- get -- Get the StdGen state from the monad 
    let (nextValue, gen') = random gen -- Generate the number, and keep the new StdGen 
    put gen' -- Update the StdGen in the monad so subsequent calls generate new random numbers 
    return nextValue 

Bây giờ bạn có thể viết các randomBools chức năng như:

randomBools' :: StdGen -> (Bool, Bool, Bool) 
randomBools' gen = fst $ runState doGenerate gen 
    where doGenerate = do 
      a <- myRandom 
      b <- myRandom 
      c <- myRandom 
      return (a, b, c) 

Nếu bạn muốn tạo ra một (hữu hạn) danh sách các Bool s, bạn có thể làm

randomBoolList :: StdGen -> Int -> ([Bool], StdGen) 
randomBoolList gen length = runState (replicateM length myRandom) gen 

Chú ý cách chúng ta trả về StdGen làm phần tử thứ hai của cặp được trả về, để cho phép nó được gán cho các hàm mới.

Đơn giản hơn, nếu bạn chỉ muốn tạo danh sách vô hạn các giá trị ngẫu nhiên cùng loại từ StdGen, bạn có thể sử dụng hàm randoms. Điều này có chữ ký (RandomGen g, Random a) => g -> [a]. Để tạo danh sách vô hạn gồm Bool sử dụng hạt giống bắt đầu là x, bạn chỉ cần chạy randoms (mkStdGen x). Bạn có thể triển khai ví dụ của mình bằng cách sử dụng length $ takeWhile id (randoms (mkStdGen x)). Bạn nên xác minh rằng bạn nhận được các giá trị khác nhau cho các giá trị ban đầu khác nhau của x, nhưng luôn có cùng giá trị nếu bạn cung cấp cùng một số x.

Cuối cùng, nếu bạn không quan tâm đến việc bị ràng buộc với đơn độc IO, Haskell cũng cung cấp trình tạo số ngẫu nhiên toàn cầu, giống như ngôn ngữ mệnh lệnh. Gọi hàm randomIO trong đơn vị IO sẽ cung cấp cho bạn một giá trị ngẫu nhiên của bất kỳ loại nào bạn thích (miễn là nó là một thể hiện của Random typeclass, ít nhất). Bạn có thể sử dụng điều này tương tự như myRandom ở trên, ngoại trừ trong đơn IO. Điều này có thêm sự tiện lợi rằng nó được tiền hạt giống trước thời gian chạy Haskell, có nghĩa là bạn thậm chí không phải lo lắng về việc tạo ra một StdGen. Vì vậy, để tạo ra một danh sách ngẫu nhiên của 10 Bool s trong IO đơn nguyên, tất cả các bạn phải làm là replicateM 10 randomIO :: IO [Bool].

Hope this helps :)

+0

Nghiêm túc, các máy tính _can_ tạo ra các số ngẫu nhiên nếu bạn có nguồn phần cứng phù hợp. Các bộ vi xử lý mới nhất của Intel thậm chí đã thêm hướng dẫn mã máy cho mục đích này ... (Có, điều này chỉ liên quan đến điểm mà bạn đang cố gắng thực hiện). – MathematicalOrchid

Các vấn đề liên quan