5

试图解决99 个 Haskell 问题中的第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

反而。我已经看到了问题的解决方案,但我想了解我应该如何修复这段代码来做到这一点,如果不可能,为什么不呢,因为直觉上感觉它应该可以工作,但事实并非如此。

4

1 回答 1

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]
于 2012-04-19T02:23:59.393 回答