Dirty Little Monad

water bottle1
Never tried random numbers before in Haskell. Obviously it must involve a Monad, because randomness is considered so unpure. Found this great line somewhere, worth sharing here:

Using Monads in Haskell is like drinking from the communal water bottle, but never letting your lips touch the bottle.

Programming Praxis 3: Bingo

Such a simple game, Bingo, but such an annoying problem in Haskell. I suppose most of my grief stems from the fact that I’m still thinking in imperative concepts. Eventually I figured out that I would need to use higher order functions (and/or tail recursion) in lieu of iteration. Then I spent the next week reviewing mind numbing concepts like filters, maps, folds, scans. All are incredibly powerful but eventually I didn’t need them. Everything reduced down to good old fashioned list comprehensions. Still my favourite Haskell feature.

Found that System.Random provided lots of handy functions. “randomRs” needs seeds, so I just used the count numbers. Not very random, but ok for these purposes. Since its a random generator, I couldn’t guarantee that all my needed numbers were picked. So I generated more than enough numbers over a range, removed duplicates with “nub” and took the right number.

The other interesting note is the use of Data.Maybe. When using “elemIndex“, it could throw an error, so the function will return a type “Maybe“, akin to a Null value in other languages. Used “fromJust” to strip the “Maybe” off and use the “Int“. Didn’t worry about the error because the pool always held all 75 numbers.

In main, printed just the two calculations. The first shows the average number of balls called to get to Bingo with only one card and 500 different pools. The second shows the average number calls used to get to Bingo with one pool and 500 different cards. Routine only takes a second to run in GHCi.

import System.Random
import Data.List
import Data.Maybe

makePool :: Int -> [Int]
makePool s = take 75 $ nub $ take (1000) $ randomRs (1,75) (mkStdGen s) :: [Int]

makeCard :: Int -> [Int]
makeCard seed = (b ++ i ++ n ++ g ++ o)
    b = take 5 $ nub $ take 20 $ randomRs (1,15) (mkStdGen seed) :: [Int]
    i = take 5 $ nub $ take 20 $ randomRs (16,30) (mkStdGen $ sum b) :: [Int]
    n = take 5 $ nub $ take 20 $ randomRs (31,45) (mkStdGen $ sum i) :: [Int]
    g = take 5 $ nub $ take 20 $ randomRs (46,60) (mkStdGen $ sum n) :: [Int]
    o = take 5 $ nub $ take 20 $ randomRs (61,75) (mkStdGen $ sum g) :: [Int]

filterCard :: [Int] -> [[Int]]
filterCard card = [[card !! x | x <- r] | r <- ranges]
    ranges = [[0..4],[5..9],[10,11,13,14],[15..19],[20..24],

evaluate :: [Int] -> [Int] -> Int
evaluate pool subcard = maximum [fromJust $ elemIndex x pool | x <- subcard, elem x pool]

bingoTest :: [Int] -> [Int] -> Int
bingoTest pool card = minimum [evaluate pool x | x <- (filterCard card)]

main = do
  print $ 0.002 * fromIntegral (sum [bingoTest (makePool x) $ makeCard 1 | x <- [1..500]])
  print $ 0.002 * fromIntegral (sum [bingoTest (makePool 1) $ makeCard x | x <- [1..500]])

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s