Random pain in Haskell

Comments

I think you're looking for mapM :: (a -
I think you're looking for mapM :: (a -
I apologise for the repeated comments -- it appears that this software sucks with regard to greater-than signs.

I think you're looking for mapM :: (a -
Nope, I can't even html-escape them. Oh well. I'll tell you later on IRC.
(there may be better ways of doing this — I just cargo culted from the Haskell Report)
In this particular case you can cut some of the boiler-plate code:

pick a = do r <- randomRIO (1, length a)
return $ a !! (r - 1)

Or shorter (but probably less clear):

pick a = randomRIO (0, length a - 1) >>= return . (a !!)

This is annoying. OK, maybe an IO value can't be showable because show returns a String, not an IO String, so using Show would allow you to subvert the IO sandbox. Or something. But if ghci can fake it in the case of a single IO value, why can't it do something sensible for a list of IO values?
This is because you can't really show an IO action, (what ghci basicly does is execute any IO actions then print their results) what you can do though is turn a list of IO actions and turn them into an single IO action that produces a list. using the sequence function:

*Main> sequence $ map (\_ -> pick [1..5]) [1..5]
[3,4,1,4,4]

Theres even a shortcut for sequencing the results of a map:

*Main> mapM (\_ -> pick [1..5]) [1..5]
[1,2,2,1,1]
Do you have a reference for that single pass pick algorithm?

Also you might like to read my blog post on a random monad at http://paulspontifications.blogspot.com/2007/08/anatomy-of-new-monad.html

Paul.
[this is good]
There's a lovely algorithm to choose a random element of a sequence that only involves scanning it once. You start with your first element and select it. You move to the second element, and have a probability of (1/2) of selecting it. You move to the third, with a probability of (1/3)
Lovely, yes. Functional, no.
You have a 50% chance to choose the first element. 33% to choose the second, 25% to choose the third, and so on, 1/(p+1) to choose.

You need a fair, equal chance to choose any number.
If you're truly concerned about having to run the list 'twice' (which is actually one and a half times in average), you can do this:
call the random generation function recursively, with a depth argument. If the element is the last of the list, you have a 1/(depth+1) chance to choose it.
When you start to go back in the stack, check if the last call returned a number. If so, return it. Otherwise, 1/(depth+1) to choose it.

Hey, it's stupid, slow, needs a huge stack, and you need to choose an element to represent "nothing chosen," but at least it's fair and doesn't run the list twice...
No, the algorithm stated does work. Just to recap, you walk through the result, always maintaining a "candidate result". For the rth element of the list, there is a 1/r chance of that you will make that element your candidate result when you reach it. When you get to the end, the candidate result becomes the real result.

Note that the algorithm is trivially fair for any list of length 1.

Now suppose it is fair for any list of length n, and consider an arbitrary list of length n+1. After we have visited the first n elements, the chance that we will have picked each of those elements is 1/n, because the algorithm is fair for lists of length n. There is now a 1/(n+1) chance we will throw away the current candidate result and pick the last element. So, the probability that we will return the last element is 1/(n+1), and the probability that we will pick any other element is the probability that we had already picked it, times the probability that we don't throw it away in favour of the last element, which is 1/n * n/(n+1) = 1/n+1.

So by induction, the algorithm is always fair.


You can remove the need for the value to be in the IO monad using unsafePerformIO, a quick change of your code and I came up with this:

module UnsafeRandom (pick) where
import System.Random
import Foreign

pick :: [a] -> a
pick rl = unsafePerformIO $ do
r <- case rl of
[] -> error "empty list"
[x] -> return x
z@(x:xs) -> pick' x xs $ length z
return r
where
pick' x [] _ = do return x
pick' x (y:ys) z = do
r <- (getStdRandom . randomR) (1,z)
let c' = if r == 1 then y else x
pick' c' ys (z+1)
Hm. I apologize for the bad formatting, try this link instead.
Hmm... so it does. I hadn't read the code, and the description was misleading.
[this is good]
Test.QuickCheck has some random functions that it uses to generate random test data but which are useful elsewhere too.
I'm not quite sure why your comment system required me to register (and needed my date of birth for legal reasons) nor why it ate the majority of my comment.

The oneof function in Test.QuickCheck is like your pick. It is in the Gen monad. Call generate with stdGen to remove from Gen.

More detail under
Using QuickCheck to Generate Random Data on http://en.wikibooks.org/wiki/Haskell/Hierarchical_libraries/Randoms
Yeah, I've sent feedback about people having problems with comments to Vox, fingers crossed. Thanks for the pointer to QuickCheck - I'll have a look!

Post a comment

Already a Vox member? Sign in