随机数和IO的Haskell递归

时间:2011-05-10 05:13:53

标签: haskell recursion io

对于99个Haskell问题,特别是23rd问题,我需要

“从列表中提取给定数量的随机选择的元素。

示例(在lisp中):

(rnd-select '(a b c d e f g h) 3)
(E D A)

我实施的是这样的:

import System.Random
import Control.Monad

removeAt :: [a] -> Int -> [a]
removeAt (x:xs) i
    | i > 0  = x : removeAt xs (i-1)
    | otherwise = xs

rndSelect :: (RandomGen g) => [a] -> Int ->  g -> IO [a]
rndSelect _ 0 _ = return []
rndSelect xs n gen = do
    let (pos, newGen) = randomR (0, length xs - 1) gen
    rest <- rndSelect (removeAt xs pos) (n-1) newGen
    return $ (xs!!pos):rest

-- for an explanation of what this is doing see EXPLANATION below

据我所知,这可行,但我关注的是最后两行。我是新手,我不知道'&lt; - '运算符的相关成本是或者像我一样反复弹跳进出IO。这是否有效,有没有更好的方法来做到这一点,不涉及弹跳IO,或者没有涉及真正的开销?

您对此有任何见解表示赞赏,因为我最近才开始在Haskell中学习这些更复杂的概念,并且尚未习惯于推理Haskell的IO系统。

解释:为了做到这一点,我决定我应该使用randomR函数从列表中随机选择一个元素(返回给定范围内的随机数),并继续递归,直到我采取n元件。

我对这个引起我这种方法的问题做了一些假设。首先,我假设rndSelect只能从列表中选择一个特定元素,其次我假设每个元素应该具有相同的被拾取概率。

PS:这是我关于SO的第一个问题,所以如果我把这个问题格式化了,请随时告诉我。

2 个答案:

答案 0 :(得分:3)

您不需要IO,因为randomR不需要它。但是,您需要做的是通过计算线程化随机数生成器:

import System.Random
import Control.Monad

removeAt :: [a] -> Int -> [a]
removeAt (x:xs) i
    | i > 0  = x : removeAt xs (i-1)
    | otherwise = xs


rndSelect :: (RandomGen t, Num a) => [a1] -> a -> t -> ([a1], t)
rndSelect _ 0 g = ([],g)
rndSelect xs n gen =
   let (pos, newGen) = randomR (0, length xs - 1) gen
       (rest,ng)     = rndSelect (removeAt xs pos) (n-1) newGen
   in  ((xs!!pos):rest, ng)

如果您担心从IO到纯代码的开销,请不要这样做。相反,您可以尝试使用mwc-random软件包,在这种情况下,它将至少快一个数量级。此外,如果您有许多元素,则可以使用任何随机访问数据结构而不是列表获得额外的好处。

答案 1 :(得分:1)

您可以将IO避免为:

rndSelect :: (RandomGen g) => [a] -> Int ->  g -> [a]
rndSelect _ 0 _ = return []
rndSelect xs n gen = do
    let (pos, newGen) = randomR (0, length xs - 1) gen
         rest = rndSelect (removeAt xs pos) (n-1) newGen
    in (xs!!pos):rest