Chris Stryczynski
Chris Stryczynski

Reputation: 33901

How can I generate a random sequence of elements from a list in Haskell?

I've had a look through https://hackage.haskell.org/package/random-1.1/docs/System-Random.html however I can't see how to use a custom "list" for example an alphanumeric list of ['a'..'z'] ++ ['0' .. '9']?

I suppose as a workaround I could instead map a random set of numbers instead.

Upvotes: 2

Views: 409

Answers (2)

jpmarinier
jpmarinier

Reputation: 4733

Assuming you can do with sampling with replacement a fixed number of characters from a fixed string, perhaps it is helpful to repackage a bit the code from SO_q57836652.

Side note: if you don't want replacement, you will have to look at that shuffle thing pointed to by @Redu in the comments, or maybe also here.

You can package your specification into a random “action”, something that's handy and chainable, like this:

import  System.Random
import  Control.Monad.Random
---- import qualified System.Random.Shuffle as SHF -- only for no replacement


-- return many random numbers from some given range:
mkRandSeqMr :: (MonadRandom mr, Random tv) => (tv,tv) -> Int -> mr [tv]
mkRandSeqMr range count =
    let act1 = getRandomR range  in  sequence (replicate count act1)


makeRandomString :: (MonadRandom mr) => String -> Int -> mr String
makeRandomString bigString count = 
    let  range = (0, (length bigString) - 1)
    in   do
             -- "do construct" within the Random monad - not the IO monad
             nums <- mkRandSeqMr range count
             let str = map (bigString !!) nums
             return str


The action can be used for example like this:

main = do
    let
        randomSeed = 4243           -- ideally passed from command line argument
        gen0 = mkStdGen randomSeed  -- for reproducibility of random numbers

        baseString = ['a'..'z'] ++ ['0' .. '9']
        strLen     = 10
        randomStringAction = makeRandomString baseString strLen

        -- need just one random string:
        (randomString1, gen1a) = runRand randomStringAction gen0

        -- harder, need a list of 3 random strings:
        act3 = do
                   -- "do construct" within the Random monad - not the IO monad
                   str1 <- randomStringAction
                   str2 <- randomStringAction
                   str3 <- randomStringAction
                   return [str1, str2, str3]

        (threeRandomStrings, gen1b) = runRand act3 gen0

    putStrLn $ "Single random string: " ++ randomString1
    putStrLn $ "Three random strings: " ++ (show threeRandomStrings)


Program output:

Single random string: x7sspkh6ai
Three random strings: ["x7sspkh6ai","cevyxt7mxh","07mryww0fv"]

Side note: if efficiency is a concern, you might want to use some Data.Map dictionary object to replace the map (bigString !!) construct.

Upvotes: 1

Chris Stryczynski
Chris Stryczynski

Reputation: 33901

The implementation of the work around I mentioned:

Prelude> import System.Random
Prelude System.Random> gen <- newStdGen 
Prelude System.Random> x = ['a'..'z'] ++ ['0' .. '9']
Prelude System.Random> fmap (x !! ) (take 10 $ randomRs (0, length x - 1 ) gen)
"h4tm52rfox"

Upvotes: 1

Related Questions