使用随机数和 IO 的 Haskell 递归
对于 99 个 Haskell 问题,特别是 第 23 个问题,我需要
“提取给定的数字”从列表中随机选择的元素。
示例(在 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
据我所知,这是可行的,但我关心的是最后两行。我对此很陌生,我不知道“<-”运算符的相关成本是或像我一样反复进出 IO。这是否有效,是否有更好的方法来做到这一点,不涉及反弹 IO,或者不涉及真正的开销?
感谢您的任何见解,因为我最近才开始学习 Haskell 中的这些更复杂的概念,并且还没有习惯对 Haskell 的 IO 系统进行推理。
说明:为了做到这一点,我决定应该使用 randomR 函数从列表中随机选择一个元素(返回给定范围内的随机数),并继续递归执行此操作,直到获取 n 个元素。
我对导致我采用这种方法的问题做出了一些假设。首先,我假设 rndSelect 只能从列表中选择一个特定元素一次,其次,我假设每个元素应该有相同的被选择概率。
PS:这是我的第一个问题,所以如果我的问题格式不好,请随时告诉我。
For the 99 Haskell questions, specifically the 23rd one, I need to
"Extract a given number of randomly selected elements from a list.
Example (in lisp):
(rnd-select '(a b c d e f g h) 3)
(E D A)
"
Which I have implemented like so:
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
As far as I can tell this works, but what I'm concerned about are those last two lines. I'm new to this and I don't know the associated costs of the '<-' operator is or bouncing in and out of IO repeatedly like I'm doing. Is this efficient, is there a better way to do this that doesn't involve bouncing IO, or is there no real overheads involved?
Any insight you have is appreciated, since I've only recently started learning these more sophisticated concepts in Haskell and haven't yet gotten used to reasoning about Haskell's IO system.
EXPLANATION: In order to do this I've decided that I should randomly select one element from the list using the randomR function (returns a random number in a given range), and keep doing this recursively until I've taken n elements.
I've made a couple assumptions about the problem that have lead me to this approach. Firstly I've assumed that rndSelect can select a specific element from the list only once, and secondly I've assumed that each element should have an equal probability of being picked.
PS: it's my first question on SO so if I've formatted the question poorly feel free to tell me.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
为此,您不需要 IO,因为 randomR 不需要它。然而,您需要做的是将随机数生成器贯穿您的计算:
如果您担心从 IO 到纯代码的开销,请不要担心。相反,您可以尝试 mwc-random 包,在这种情况下,速度至少会快一个数量级。此外,如果您有许多元素,您可以使用任何随机访问数据结构而不是列表获得额外的好处。
You do not need IO for this, since randomR does not require it. What you need to do however, is to thread the random number generator through your computation:
If you're concerned about overheads going from IO to pure code, don't be. Instead you can try mwc-random package which will be atleast an order of magnitude faster in this case. Further, you could get additional benefit using any random access data structure instead of list if you have many elements.
您可以避免 IO,如下所示:
You can avoid IO as :