使用随机数和 IO 的 Haskell 递归

发布于 2024-11-06 02:38:18 字数 1194 浏览 4 评论 0原文

对于 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

记忆で 2024-11-13 02:38:18

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

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:

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. 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.

不离久伴 2024-11-13 02:38:18

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

You can avoid IO as :

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文