Takumi
Takumi

Reputation: 23

Pick a random element from a list in Haskell

My code aims to create a word search puzzle. There is a data called Orientation representing the direction of each word in the puzzle.

data Orientation =
  Forward | Back | Up | Down | UpForward | UpBack | DownForward | DownBack
  deriving (Eq, Ord, Show, Read)

Now given a input of strings which is [String], I want to randomly assign each string an orientation like [(Orientation, String)]

assignWordDir :: [String] -> [(Orientation, String)]
assignWordDir [] = []
assignWordDir (s:strs) = (ori, s) : assignWordDir
                        where ori = pickOri [Forward, Back, Up, Down, UpForward, UpBack, DownForward, DownBack]

pickOri :: [a] -> IO a
pickOri xs = do
  i <- randomRIO (0, len)
  pure $ xs !! i
  where len = length xs - 1

I cannot compile because the output of pickOri is IO Orientation, is there any suggestions on how to modify my code? Thanks a lot

Couldn't match expected type ‘[(IO Orientation, String)]’
                  with actual type ‘[String] -> [(Orientation, String)]’

Upvotes: 2

Views: 806

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233135

You might consider modifying the functions so that they stay pure by taking a RandomGen parameter. The pickOri function, for example, might be modified thusly:

pickOri :: RandomGen g => g -> [a] -> (a, g)
pickOri rnd xs =
  let len = length xs - 1
      (i, g) = randomR (0, len) rnd
  in (xs !! i, g)

It's necessary to return the new RandomGen value g together with the selected list element, so that it'll generate another pseudo-random number the next time around.

Likewise, you can modify assignWordDir like this:

assignWordDir :: RandomGen g => g -> [b] -> [(Orientation, b)]
assignWordDir _ [] = []
assignWordDir rnd (s:strs) = (ori, s) : assignWordDir g strs
  where (ori, g) =
    pickOri rnd [Forward, Back, Up, Down, UpForward, UpBack, DownForward, DownBack]

Notice that when recursing into to assignWordDir, the recursive function call uses the g it receives from pickOri.

You can use mkStdGen or newStdGen to produce RandomGen values. Here's an example using newStdGen:

*Q65132918> rnd <- newStdGen
*Q65132918> assignWordDir rnd ["foo", "bar", "baz"]
[(UpBack,"foo"),(Up,"bar"),(UpBack,"baz")]
*Q65132918> assignWordDir rnd ["foo", "bar", "baz"]
[(UpBack,"foo"),(Up,"bar"),(UpBack,"baz")]

Notice that when you use the same RandomGen value, you get the same sequence. That's because assignWordDir is a pure function, so that's expected.

You can, however, produce a new random sequence by creating or getting a new StdGen value:

*Q65132918> rnd <- newStdGen
*Q65132918> assignWordDir rnd ["foo", "bar", "baz"]
[(Up,"foo"),(Up,"bar"),(Forward,"baz")]

If you want to play with this in a compiled module, you can keep these functions as presented here, and then compose them with a newStdGen-generated StdGen in the main entry point.

Upvotes: 4

Related Questions