Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - How do I get Random Points (Int,Int)

Tags:

random

haskell

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.

like image 689
user677786 Avatar asked Apr 25 '13 06:04

user677786


People also ask

How do you get a random int in Haskell?

Getting a random number Random is a RandomGen , a generator for a random sequence. You can use mkStdGen to make one, giving it a starting point you've chosen. If you don't care about where it starts, you can use getStdGen to get a RandomGen that was initialized with a random seed when the system was started.


1 Answers

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.

like image 102
MtnViewMark Avatar answered Sep 17 '22 16:09

MtnViewMark