如何解决如何在 Haskell 中链接二元函数?
我想使用 System.Random.next
函数生成 N 个数字。我实现了一个函数,它接受一个 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 次以获取随机数列表时遇到问题。我怎样才能以适当的方式链接它?
我也尝试了递归方式——它有效,但我确信这个解决方案远非优雅:
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 的典型方案,使用所谓的保护递归以自上而下的方式构建列表,即递归由惰性数据构造函数保护(在这种情况下,{{ 1}})。使用重复追加单例构建的列表效率非常低,具有二次行为 when accessed。
将 :
安全地隐藏在内部并封装起来是一个额外的好处。不想暴露,有什么用?无论如何,从中间重新开始随机生成序列只会重新创建相同的数字序列。
当然,您可以使用 newGen
从列表中删除任意数量的数字。
您的递归解决方案很好,我们可以通过内联您调用 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
将生成器返回到第二个位置而让 randomnumbers_
将生成器返回到第一个位置是很奇怪的。
还可以使用状态 monad 或其他一些与库中的随机相关的 monad(我不确定他们最近添加了什么)来消除携带生成器状态的需要。如果您使用大量随机生成函数,并且随身携带 gen
开始变得麻烦,那么这样的 monad 可能是正确的工具。
非一元风格:
假设您可能需要生成器的最终状态以进行进一步处理,您可以像这样使用手动生成器状态链接编写函数:
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)
Monadic 风格:
N 个输出值的 monadic action 对象编写起来非常简单:
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))
要使用该操作,您只需将其提供给库函数 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,5,2,6,4,6],1270926008 238604751)
λ>
旁注: 请注意 Haskell System.Random 包最近发生了重大变化,导致 1.2 version。
例如更改here的理念。希望向后兼容。您可能需要检查您的分发级别。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。