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.
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.
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 String
s!
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 Point
s. 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 Point
s.
Finally, example3
lives in IO
, and creates the generator and passes it all into the otherwise pure code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With