簡體   English   中英

關於 Haskell 中隨機數的另一個問題

[英]Another question about random numbers in Haskell

我正在嘗試Haskell 中的Pokemon Gold and Silver制作一個Voltorb 游戲版本。 現在為了生成板,我想要一個 (l,r,v) 三元組列表,其中 l 是線,r 是行,v 是字段的值。

值 l 和 r 是通過列表推導實現的,因為它們每次都應該相同。 至於 v,雖然我找不到實現它的選項,使其“隨機”為 0、1、2 或 3(我知道 Haskell 是純函數式的,沒有真正的隨機性,這就是為什么我與此斗爭)。

如果有人可以幫助解決這個問題,我將不勝感激。 如果您也可以簡要說明該解決方案為何有效,那將對我有很大幫助。

我目前對 l 和 r 的實現:

field n = [(l,r,v) | l <- [0..n], r <- [0..n]]

如果我正確理解了這個問題,那么每個 (Int, Int) 棋盤位置都有一個隨機值。 所以問題不能通過在理解列表中添加第三個子句來解決,例如:

field n = [(l,r,v) | l <- [0..n], r <- [0..n], v <- someRandomStuff]

因為field表達式的長度將是 (n+1)x(n+1)x(隨機內容的長度),而您想要的只是 (n+1)x(n+1)。

一種可能性在於分兩步操作:

  1. 生成所需的 (n+1)*(n+1) 0 到 3 之間的隨機值
  2. 將其與 (l,r) 值相結合

我假設讀者理解命令式語言中的偽隨機數生成。

給定一個種子,您可以使用函數 mkStdGen 返回的一次性隨機數生成器來生成隨機值,使用函數randomRs 讓我們使用ghci會話作為測試平台。

關於第 1 步:

 λ> import System.Random
 λ> :t randomRs
randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
 λ> 
 λ> seed1=42
 λ> 
 λ> getVSeq n seed = let rng0 = mkStdGen seed  in take  ((n+1)^2) (randomRs (0,3) rng0)
 λ> 
 λ> getVSeq 5 seed1
[1,1,3,0,2,1,0,1,0,1,3,1,2,0,2,3,1,1,3,2,0,2,2,0,2,0,0,0,1,0,2,1,0,2,0,1]
 λ> 
 λ> length $ getVSeq 5 seed1
36
 λ> field0 n = [(l,r) | l <- [0..n], r <- [0..n]]
 λ> field0 5
[(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,0),(1,1),(1,2),(1,3),(1,4),(1,5),(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(3,0),(3,1),(3,2),(3,3),(3,4),(3,5),(4,0),(4,1),(4,2),(4,3),(4,4),(4,5),(5,0),(5,1),(5,2),(5,3),(5,4),(5,5)]
 λ> 

 λ> 
 λ> 
 λ> length  $ field0 5
36
 λ> 

現在關於第 2 步,函數zip幾乎解決了我們的問題,除了我們沒有完全得到三元組:

 λ> 
 λ> sol0 n seed = zip (field0 n) (getVSeq n seed)
 λ> sol0 5 seed1
[((0,0),1),((0,1),1),((0,2),3),((0,3),0),((0,4),2),((0,5),1),((1,0),0),((1,1),1),((1,2),0),((1,3),1),((1,4),3),((1,5),1),((2,0),2),((2,1),0),((2,2),2),((2,3),3),((2,4),1),((2,5),1),((3,0),3),((3,1),2),((3,2),0),((3,3),2),((3,4),2),((3,5),0),((4,0),2),((4,1),0),((4,2),0),((4,3),0),((4,4),1),((4,5),0),((5,0),2),((5,1),1),((5,2),0),((5,3),2),((5,4),0),((5,5),1)]
 λ> 

所以我們需要稍微按摩一下sol0的結果:

 λ> 
 λ> sol1 n seed = let flatten = (\((a,b),c) -> (a,b,c))  in  map flatten (sol0 n seed)
 λ> sol1 5 seed1
[(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]
 λ> 

所以這就是我理解你想要的。 如果這是在您的應用程序中唯一使用隨機數,這可能就足夠了。 否則,恐怕需要在 Haskell 函數式編程上下文中建立一些有關隨機數生成的知識。 您可能想從這里那里開始。

此外,正如 Thomas M. DuBuisson 所提到的,這已經在幾個 SO 問題中得到解決。 您可以使用本地搜索引擎。 例如,這是最近的其中之一

如果您需要取回發電機以供重復使用怎么辦?

在這種情況下,您需要有一個函數,它接受一個預先構建的生成器,並將“字段”三元組列表和生成器的(最終狀態)作為 (list,finalRng) 對返回。

您可以將硬(隨機)工作分包給一個函數,該函數返回另一個更簡單的對,僅包含 v 值列表和生成器的最終狀態。 該函數可以遞歸方式編寫,如下所示。


import  System.Random
import  Control.Monad.Random

-- returns the random v values AND the final state of the generator
seqAndGen :: RandomGen tg => (Int,Int) -> Int-> tg -> ([Int], tg)
seqAndGen range count rng0 =
    if (count <= 0)
        then ([],rng0)
        else
            let (v,rng1) = randomR range rng0
                nextSeq  = seqAndGen range (count-1) rng1  -- recursive call
            in
                (v:(fst nextSeq), snd nextSeq)

-- returns the "field" values AND the final state of the generator
fieldAndGen :: RandomGen tg => Int -> tg -> ([(Int,Int,Int)], tg)
fieldAndGen n rng0 =
    let  field0  = [(l,r) | l <- [0..n], r <- [0..n]]
         range   = (0,3)       -- at that level, range gets hardwired
         count   = (n+1)*(n+1) -- number of field/board positions
         pair    = seqAndGen range count rng0  -- the hard work
         vSeq    = fst pair
         endRng  = snd pair
         flatten = \((a,b),c) -> (a,b,c)
         field   = map flatten  (zip field0 vSeq)
    in
         (field, endRng)

主程序:

main = do
    let mySeed = 42
        n      = 5
    putStrLn $ "seed=" ++ (show mySeed) ++ "  " ++ "n=" ++ (show n)
    -- get a random number generator:
    let rng0    = mkStdGen mySeed  

    let (field, endRng) = fieldAndGen n rng0
        fieldv = map  (\(a,b,c) -> c)  field
    putStrLn $ "endRng = " ++ (show endRng)
    putStrLn $ "field  = " ++ (show field)

程序輸出:


seed=42  n=5
endRng = 1388741923 1700779863
field  = [(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]

請注意,有一個可能的變體,而不是傳遞生成器,而是傳遞由函數randomRs生成的 v 值的無限列表。 為此目的使用函數splitAt很方便。 但這假設您僅對 v 值使用隨機性,而沒有其他任何用途,因此它不太通用且不太靈活。

這是將 (1) 偽隨機純代碼與 (2) 偽隨機生成器的隨機種子分開的簡短方法:

--Get the generator in IO monad
main :: IO ()
main = do
  g <- getStdGen
  print $ buildGrid g 5

--Keep as much code as possible pure
buildGrid :: StdGen -> Int -> [(Int, Int, Int)]
buildGrid g n = zipWith ($) ((,,) <$> [0..n] <*> [0..n])
                            (take ((n+1) * (n+1)) $ randomRs (0,3) g)

或者您可以保留原始列表理解,但在這種情況下,您必須使用語言擴展:

{-# LANGUAGE TupleSections #-}
...
buildGrid g n = zipWith ($) [(y,x,) | y <- [0..n], x <- [0..n]] 
                            (take ((n + 1) * (n + 1)) $ randomRs (0,3) g) 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM