[英]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)。
一種可能性在於分兩步操作:
我假設讀者理解命令式語言中的偽隨機數生成。
給定一個種子,您可以使用函數 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.