首先,让我们稍微清理一下您的代码。有一个复数版本,randomR
它提供了一个无限的随机值列表:randomRs
. 这简化了一些事情:
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)
我们可以进一步简化这一点,通过使用zip
' 在较短的列表耗尽后停止的属性:
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)
请注意,我还将传入列表的类型概括为[a]
. 由于从未使用过这些值,因此它们不必是String
s!
现在,它每次都给出相同的值,因为它每次都使用相同的种子 ( )mkStdGen
创建一个伪随机生成器。1
如果您希望它每次都不同,那么您需要创建一个生成器,IO
其中可以基于计算机的随机状态。与其将整个计算放入 中,不如IO
传入 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
在这里,newStdGen
每次都会创建一个新的伪随机生成器,但它在IO
. 最终将其传递给一个纯(非IO
)函数,该函数rndPoints3
采用生成器,并返回一个无限的随机Point
s 列表。在该函数中,split
用于从中创建两个生成器,每个生成器用于导出坐标的随机列表。
pointsForLables
现在分离出为每个标签匹配一个新的随机点的逻辑。我还更改了它以返回更可能有用的标签对和Point
s。
最后,example3
生活在 中IO
,并创建生成器并将其全部传递到其他纯代码中。