【问题标题】:Haskell recursion with random numbers and IO具有随机数和 IO 的 Haskell 递归
【发布时间】:2011-05-10 05:13:53
【问题描述】:

对于 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

据我所知,这是可行的,但我关心的是最后两行。我是新手,我不知道 '

感谢您的任何见解,因为我最近才开始在 Haskell 中学习这些更复杂的概念,还没有习惯于推理 Haskell 的 IO 系统。

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

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

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

【问题讨论】:

  • 你真的需要将结果包装在 IO monad 中吗?为什么不能是:rndSelect :: (RandomGen g) => [a] -> Int -> g -> [a] ...。如果需要,此函数的用户可以使用返回函数将结果列表包装在 IO 中。
  • 我使用 IO 是因为这是(我知道的)获取随机数的唯一方法。我希望从列表中随机选择/删除一个元素,然后返回列表,以便我可以再次执行此操作 (n-1) 次。由于 Haskell 的纯洁性,我认为我不能在 IO monad 之外执行此操作,但可以编写一个不使用 IO 的辅助函数。

标签: haskell recursion io


【解决方案1】:

你不需要 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 包,在这种情况下,它至少会快一个数量级。此外,如果您有很多元素,您可以使用任何随机访问数据结构而不是列表来获得额外的好处。

【讨论】:

  • 对,我明白了。我感到困惑并认为我需要 IO,因为我正在阅读 LYAH 中的 getStdGen,而我所需要的只是一开始的种子 randomGen。 >如果您担心从 IO 到纯代码的开销,请不要担心。谢谢。这个程序的优化并不像找出 IO 是否有相关成本那么重要,所以很高兴知道这一点。
【解决方案2】:

你可以避免 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

【讨论】:

  • 谢谢,我刚刚得到了 aleator 的回答,但你说得对,我误以为我需要 IO。
猜你喜欢
  • 2013-12-06
  • 1970-01-01
  • 1970-01-01
  • 2011-09-01
  • 1970-01-01
  • 2019-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多