5

I'm trying to get a set of random points (x,y) for drawing graph nodes to a screen. I need one randomly generated point for each node name passed in.

I found this code on a SO page, and modified it slightly to work for me, but it doesn't really do what I need.

I need a list of random (as random as possible) (Int,Int).

Anyway, here is what I have so far, and of course, it gives the same values every time, so it isn't particularly random :)

rndPoints :: [String] -> [Point]
rndPoints [] = []
rndPoints xs = zip x y where
          size = length xs
          x = take size (tail (map fst $ scanl (\(r, gen) _ -> randomR (25::Int,1000::Int) gen) (random (mkStdGen 1)) $ repeat ()))
          y = take size (tail (map fst $ scanl (\(r, gen) _ -> randomR (25::Int,775::Int) gen) (random (mkStdGen 1)) $ repeat ()))

Any help would be much appreciated.

user677786
  • 455
  • 1
  • 6
  • 13
  • [possible duplicate](http://stackoverflow.com/questions/2110535/sampling-sequences-of-random-numbers-in-haskell?rq=1) – Nishanth Apr 25 '13 at 06:37
  • You could use Arbitrary from the QuickCheck package? – Jeff Foster Apr 25 '13 at 08:42
  • Apart from the problem of getting the same point distribution for each time the function is called, it would still be a bad idea to use twice `mkStdGen 1`. This would lead to awkward correlation between x- and y-coordinate, in the worst case resulting in only points along a line. — Anyway, having two lines that include large amounts of duplicate code is pretty much _always_ bad, there's something called [the DRY principle](http://en.wikipedia.org/wiki/DRY_principle). – leftaroundabout Apr 25 '13 at 15:44

2 Answers2

7

First, let's clean up your code a bit. There is a plural version of randomR that delivers an infinite list of random values: randomRs. This simplifies things a bit:

rndPoints1 :: [String] -> [Point]
rndPoints1 [] = []
rndPoints1 xs = zip x y
  where
    size = length xs
    x = take size $ randomRs (25, 1000) (mkStdGen 1)
    y = take size $ randomRs (25,  775) (mkStdGen 1)

We can simplify that further, by using zip's property that it stops after the shorter list is exhausted:

rndPoints2 :: [a] -> [Point]
rndPoints2 xs = map snd $ zip xs $ zip x y
  where
    x = randomRs (25, 1000) (mkStdGen 1)
    y = randomRs (25,  775) (mkStdGen 1)

Notice I've also generalized the type of incoming list to just [a]. Since the values are never used, they needn't be Strings!

Now, it gives the same value every time because it uses mkStdGen to create a pseudo-random generator from the same seed (1) each time. If you want it to be different each time, then you need to create a generator in IO which can be based on the radom state of the computer. Rather than put the whole computation in IO, it is cleaner to pass in a StdGen:

rndPoints3 :: StdGen -> [Point]
rndPoints3 sg = zip x y
  where
    (sg1, sg2) = split sg
    x = randomRs (25, 1000) sg1
    y = randomRs (25,  775) sg2

pointsForLabels :: [a] -> StdGen -> [(a, Point)]
pointsForLabels xs sg = zip xs $ rndPoints3 sg

example3 :: [a] -> IO [(a, Point)]
example3 xs = newStdGen >>= return . pointsForLabels xs

Here, newStdGen creates a new pseudo-random generator each time, but it is in IO. That is passed eventually to a pure (non-IO) function rndPoints3 that takes the generator, and returns an infinite list of random Points. Within that function, split is used to create two generators from it, and each is used to derive the random list of coordinates.

pointsForLables now separates out the logic of matching up a new random point for each label. I also changed it to return the more likely useful pairs of labels and Points.

Finally, example3 lives in IO, and creates the generator and passes it all into the otherwise pure code.

MtnViewMark
  • 5,120
  • 2
  • 20
  • 29
  • Thanks for the response! So if I want to use the list of tuples that is passed back from example3, it would need to be inside a do block in the caller so that the list could then be used as parameters to another function that takes just a list [(a,Point)]? I apologize, I'm having the hardest time with monads in this language! – user677786 Apr 25 '13 at 06:52
  • How random do you want things. The rule in haskell is, "if it could be different each time, it lives in the IO monad." It has to, because a function always returns the same result for the same input. Put another way, Randomness lives in the real world, and is not part of a purely mathematical function. – John F. Miller Apr 25 '13 at 07:03
  • Don't put the bulk of your program in a `do` block. Write the next step as a pure function of type `[(a, Point)] -> Whatever` and insert that into the chain in `example3`: `>>= return . frobThePairs . pointsForLabels xs`. Of course, you can make `frobThePairs` as complex as you like and it is still pure. – MtnViewMark Apr 25 '13 at 07:11
  • This answer is delightfully simple and results in a nice concise solution. Thanks – Cortado-J Jun 08 '19 at 11:37
0

I ended up using MonadRandom for this. I think the code was a little clearer and easier for me to understand. You could adapt the following code to address the original question.

import Control.Applicative
import Control.Monad.Random

type Point = (Float, Float)
type Poly = [Point]

randomScalar :: (RandomGen g) => Rand g Float
randomScalar = getRandomR (-500, 500)

randomPoint :: (RandomGen g) => Rand g Point
randomPoint = (,) <$> randomScalar <*> randomScalar

randomPoly :: (RandomGen g) => Int -> Rand g Poly
randomPoly n = sequence (replicate n randomPoint)