简体   繁体   English

随机数和IO的Haskell递归

[英]Haskell recursion with random numbers and IO

For the 99 Haskell questions, specifically the 23rd one, I need to 对于99个Haskell问题,特别是第23个问题,我需要

"Extract a given number of randomly selected elements from a list. “从列表中提取给定数量的随机选择的元素。

Example (in lisp): 示例(在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. 我是新手,我不知道'< - '运算符的相关成本是或者像我一样反复弹跳进出IO。 Is this efficient, is there a better way to do this that doesn't involve bouncing IO, or is there no real overheads involved? 这是否有效,有没有更好的方法来做到这一点,不涉及弹跳IO,或者没有涉及真正的开销?

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

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

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. 首先,我假设rndSelect只能从列表中选择一个特定元素,其次我假设每个元素应该具有相同的被拾取概率。

PS: it's my first question on SO so if I've formatted the question poorly feel free to tell me. PS:这是我关于SO的第一个问题,所以如果我把这个问题格式化,请不要随便告诉我。

You do not need IO for this, since randomR does not require it. 您不需要IO,因为randomR不需要它。 What you need to do however, is to thread the random number generator through your computation: 但是,您需要做的是通过计算线程化随机数生成器:

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)

If you're concerned about overheads going from IO to pure code, don't be. 如果您担心从IO到纯代码的开销,请不要。 Instead you can try mwc-random package which will be atleast an order of magnitude faster in this case. 相反,您可以尝试使用mwc-random软件包,在这种情况下,它将至少快一个数量级。 Further, you could get additional benefit using any random access data structure instead of list if you have many elements. 此外,如果您有许多元素,您可以使用任何随机访问数据结构而不是列表获得额外的好处。

You can avoid IO as : 你可以避免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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM