Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell recursion with random numbers and IO

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.

like image 574
Dave Avatar asked Oct 11 '22 02:10

Dave


1 Answers

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.

like image 86
aleator Avatar answered Oct 14 '22 03:10

aleator