尝试解决99个Haskell问题中的problem 23。
我写了这个
rnd_select :: (Eq a) => [a] -> Int -> [a]
rnd_select [] _ = []
rnd_select _ 0 = []
rnd_select ys n =
let
(rnd_index, gen) = randomR (1, length ys) (mkStdGen 200)
(x, xs) = removeAt rnd_index ys
in x : rnd_select xs (n-1)
哪个有效,但我不想使用mkStdGen但是使用
newStdGen or getStdGen
代替。 我已经看到了问题的解决方案,但是我想知道我应该如何修复这个代码来做到这一点,如果不可能为什么不这样做,因为直觉上它感觉它应该工作,但事实并非如此。
答案 0 :(得分:9)
请记住,Haskell函数是纯;在相同输入的情况下,它们必须始终返回相同的结果。您可以让函数返回IO [a]
,这样可以调用newStdGen
,但更好的方法是通过将随机数生成器作为函数的附加参数并返回来保持代码纯净之后的新发电机:
rnd_select :: (Eq a, RandomGen g) => [a] -> Int -> g -> ([a], g)
rnd_select [] _ gen = ([], gen)
rnd_select _ 0 gen = ([], gen)
rnd_select ys n gen =
let (rnd_index, gen') = randomR (1, length ys) gen
(x, xs) = removeAt rnd_index ys
(xs', gen'') = rnd_select xs (n-1) gen'
in (x : xs', gen'')
现在你可以使用它,例如getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
喜欢这样。
> getStdRandom (rnd_select [1..20] 10)
[12,11,14,4,16,7,1,2,18,15]
然而,手动传递发电机可能有点单调乏味。制作这种整洁器的一种方法是使用MonadRandom包。
rnd_select :: (MonadRandom m, Eq a) => [a] -> Int -> m [a]
rnd_select [] _ = return []
rnd_select _ 0 = return []
rnd_select ys n = do
rnd_index <- getRandomR (1, length ys)
let (x, xs) = removeAt rnd_index ys
xs' <- rnd_select xs (n-1)
return (x:xs')
由于IO
是MonadRandom
的实例,因此您可以将其直接用作IO
操作。
> rnd_select [1..20] 10
[20,18,12,13,5,7,17,9,3,4]
> rnd_select [1..20] 10
[9,18,4,20,6,5,3,15,13,7]
或者您可以使用evalRand
在纯monad中运行它,提供您自己的随机数生成器,这样您就可以获得可重复的结果(适合调试/测试)。
> evalRand (rnd_select [1..20] 10) (mkStdGen 200)
[4,16,15,13,8,20,6,14,5,3]
> evalRand (rnd_select [1..20] 10) (mkStdGen 200)
[4,16,15,13,8,20,6,14,5,3]