[英]How to chain binary functions in Haskell?
我想使用System.Random.next
function 生成 N 個數字。 我實現了一個 function ,它接受一個StdGen
和一個數字列表並返回新的生成器和更新的數字列表:
import System.Random
getRandNum :: StdGen -> [Int] -> (StdGen, [Int])
getRandNum gen nums = (newGen, newNums)
where
(randNum, newGen) = next gen
newNums = nums ++ [randNum]
然后我可以通過以下方式使用它:
λ: getRandNum (mkStdGen 1) []
(80028 40692,[39336])
但是我在執行 N 次 function 以獲取隨機數列表時遇到問題。 我怎樣才能以正確的方式鏈接它?
我也嘗試了遞歸方式——它有效,但我確信這個解決方案遠非優雅:
randomnumbers_ :: Int -> StdGen -> (StdGen, [Int])
randomnumbers_ 0 gen = (gen, [])
randomnumbers_ 1 gen = (newGen, [randNum])
where
(randNum, newGen) = next gen
randomnumbers_ n gen = (newGen2, nums ++ nums2)
where
(newGen, nums) = randomnumbers_ 1 gen
(newGen2, nums2) = randomnumbers_ (n - 1) newGen
randomnumbers :: Int -> Int -> [Int]
randomnumbers n seed = snd $ randomnumbers_ n generator
where
generator = mkStdGen seed
順便說一句,是的,我知道它可以使用State
monad 來實現,我知道該怎么做。
鏈接它的方法是以您不需要這樣做的方式實現它,即它自己進行鏈接。
無論如何,您的代碼中存在固有的矛盾,您將其getRandNum
,單數,它確實只有一個數字,但類型是[Int]
。
因此,我們通過對您的代碼進行最少的編輯來解決所有這些問題,如
getRandNums :: StdGen -> [Int]
getRandNums gen = randNum : newNums
where
(randNum, newGen) = next gen
newNums = getRandNums newGen
這種方案是 Haskell 的典型方案,使用所謂的受保護遞歸以自上而下的方式構建列表,即由惰性數據構造函數保護的遞歸(在本例中為:
)。 像您一樣使用重復附加單例構建的列表效率非常低,在訪問時具有二次行為。
newGen
安全地藏在里面,封裝起來,是一個額外的好處。 不想暴露,有什么用? 無論如何,從中間重新啟動隨機生成序列只會重新創建相同的數字序列。
當然,您可以使用take n
從列表中取出任意數量的數字。
您的遞歸解決方案很好,我們可以通過內聯您調用randomnumbers_ 1 gen
的位置來刪除“1”大小寫來縮短它。
randomnumbers_ :: Int -> StdGen -> (StdGen, [Int])
randomnumbers_ 0 gen = (gen, [])
randomnumbers_ n gen = (newGen2, num : nums)
where
(num, newGen) = next gen
(newGen2, nums) = randomnumbers_ (n -1) newGen
我們可以通過交換這對來改進這一點:讓標准next
在第二個 position 中返回生成器但讓randomnumbers_
在第一個 position 中返回生成器是很奇怪的。
還可以使用 state monad 或庫中的其他一些與隨機相關的 monad 來消除攜帶生成器 state 的需要(我不確定他們最近添加了什么)。 如果您使用大量隨機生成函數,並且隨身攜帶gen
開始變得很麻煩,那么這樣的 monad 可能是正確的工具。
非單子風格:
假設您可能需要生成器的最終 state 進行進一步處理,您可以像這樣編寫 function,使用手動生成器 state 鏈接:
import System.Random
randomNumbers :: Int -> StdGen -> (StdGen, [Int])
randomNumbers count gen0 =
if (count <= 0)
then (gen0, [])
else let (v0, gen1) = next gen0
(gen2, vs) = randomNumbers (count-1) gen1
in
(gen2, v0:vs)
可能的改進:
StdGen
這將給出類似的代碼:
randomNumbersInRange :: RandomGen gt => (Int, Int) -> Int -> gt -> (gt, [Int])
randomNumbersInRange range count gen0 =
if (count <= 0)
then (gen0, [])
else
let (v0, rng1) = randomR range gen0
(rng2, rest) = randomNumbersInRange range (count-1) rng1
in
(rng2, v0 : rest)
單子風格:
N 個 output 值的一元動作 object 非常簡單:
import System.Random
import Control.Monad.Random
mkRandSeqM :: (RandomGen tg, Random tv) => (tv,tv) -> Int -> Rand tg [tv]
mkRandSeqM range count = sequence (replicate count (getRandomR range))
要使用該操作,您只需將其輸入庫 function runRand
。
ghci下測試:
λ>
λ> action = mkRandSeqM (0,9) 20
λ>
λ> :type (runRand action (mkStdGen 43))
(runRand action (mkStdGen 43))
:: (Random tv, Num tv) => ([tv], StdGen)
λ>
λ> runRand action (mkStdGen 43)
([3,3,7,8,1,9,1,1,5,3,1,2,6,7,4,1,7,8,1,6],1270926008 238604751)
λ>
旁注:當心 Haskell 系統。隨機 package 最近經歷了很大的變化,產生了1.2 版本。
這里以哲學的變化為例。 希望向后兼容。 您可能想要檢查您的分發級別。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.