对于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的第一个问题,所以如果我把这个问题格式化了,请随时告诉我。
答案 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