How can I create a random 2d array in Haskell?

Viewed 122

I am fairly new to Haskell and I'm always confused when it comes to dealing with random values. This time I'm trying to create a 5x5 2d-array in which every cell contains a random value between 4 options.

data Content = Bomb | One | Two | Three deriving (Eq, Show)
data Board = Array (Int, Int) Cell
data Cell = Cell {
   content :: Content,
   state :: CellState,
   notes :: Notes
}
type Notes = [String]
type CellState = Bool

then in the main function I typed

main :: IO()
main = do
let cellMatrix = createMatrix
print cellMatrix

How can I create a function that creates a Board, and where do I need to do all the g<-newStdGen stuff?

Any advice would be greatly appreciated!

1 Answers

Focusing solely on the randomness aspect of the problem here, we see that the System.Random module in its version 1.2 provides an uniformR function that can produce one pseudo-random integer value from a specified range. Typically, we would need 5*5=25 values from the (0,3) range.

Side Note: Beware the changes between Random v1.1 and v1.2 are massive ones.

Alternatively, the same module provides a randomRs function, but that one provides an infinite list of integer values, hence cannot return an updated value of the generator. That might not be what you require.

We can use as our workhorse a function that returns a finite count of random integer values, together with an updated generator:

import  System.Random

getManyInts :: RandomGen g => g -> Int -> (Int,Int) -> ([Int],g)
getManyInts g0 count range =
    if (count <= 0)  then  ([], g0) 
                     else  let 
                                (v,g1)  = uniformR range g0
                                (vs,gf) = getManyInts g1 (count-1) range
                           in        
                                (v:vs, gf) 

In order to navigate from integer to Enum values, we will use a slightly improved Content type:

data Content = Bomb | One | Two | Three
               deriving (Eq, Show, Enum, Bounded)

At that point, we can write a small test program that provides 5*5=25 random values of Content type:

main :: IO ()
main = do
    g0 <- newStdGen
    let  count = 5*5
         (loC, hiC) = (minBound :: Content, maxBound :: Content)
         intRange   = (fromEnum loC, fromEnum hiC)
         (xs, g1)   = getManyInts g0 count intRange
         cs         = (map toEnum xs) :: [Content]

    putStrLn $ "contents: " ++ (show cs)

Test program output:

contents: [Two,Three,One,One,One,Two,One,Two,One,Bomb,Two,Two,One,Three,Bomb,Two,Two,Bomb,Three,One,One,Bomb,One,One,Bomb]

Addendum:

A better, polymorphic function:

Next, we can provide a function that returns a list of random values for any similar type:

{-#  LANGUAGE  ScopedTypeVariables  #-}
{-#  LANGUAGE  ExplicitForAll       #-}

getManyEnums :: forall g e. (RandomGen g, Bounded e, Enum e) => g -> Int -> ([e],g)
getManyEnums g0 count = (map toEnum xs, g1) 
  where
    intRange   = (fromEnum (minBound :: e), fromEnum (maxBound :: e)) 
    (xs, g1)   = getManyInts g0 count intRange

This function can be specialized as required. For example:

Testing under ghci:

$ ghci
 GHCi, version 8.10.5: https://www.haskell.org/ghc/  :? for help
 ...
 λ> 
 λ> :load q73268595.hs
 [1 of 1] Compiling Main             ( q73268595.hs, interpreted )
 Ok, one module loaded.
 λ> 
 λ> getManyContents = getManyEnums :: StdGen -> Int ->  ([Content],StdGen)
 λ> 
 λ> g0 <- newStdGen
 λ> 
 λ> (cs,gf) = getManyContents g0 10
 λ> 
 λ> cs
 [One,One,Two,One,One,Three,Three,Three,Bomb,One]
 λ> 
 λ> :q
Leaving GHCi.
$ 

Related