如果我们使用更传统的符号和更短的行稍微重写您的 randomNext 函数,它会给出如下内容:
import System.Random
type Point = (Float, Float)
nextRandoms1 :: Int -> [Point] -> [Point]
nextRandoms1 seed origins =
let add2d = (\(x1,y1) (x2,y2) -> (x1+x2, y1+y2))
count = length origins
range = (-5::Float, 5)
xs = take count $ randomRs range (mkStdGen (seed+0))
ys = take count $ randomRs range (mkStdGen (seed+1))
in
zipWith add2d origins (zip xs ys)
正如您所指出的,该函数不返回任何内容以允许生成更多随机值。
更巧妙的是,它使用 2 个具有相邻种子值的不同随机序列。但是该库不保证这两个系列不相关。实际上,一些随机数生成器使用它们的种子作为共享非常大的伪随机序列的偏移量。
其次,它处理生成位置增量并将它们添加到初始位置。
为了避免这些问题,我们可以从修改函数开始,该函数遵循采用初始随机生成器状态并返回更新状态作为结果的一部分的通用约定:
randomPointUpdates :: StdGen -> (Float, Float) -> Int -> ([Point], StdGen)
randomPointUpdates gen0 range count =
if (count <= 0)
then ([], gen0) -- generator unaltered
else
let (dx, gen1) = randomR range gen0
(dy, gen2) = randomR range gen1
point = (dx, dy)
(rest, gen) = randomPointUpdates gen2 range (count-1)
in
(point : rest, gen)
这个randomPointUpdates 函数对点数使用递归。它只生成 2D 位置增量,根本不处理加法。
在这个函数之上,我们现在可以编写另一个处理加法的函数。由于 range 是硬连线的,它只需要两个参数:初始生成器状态和初始点位置列表:
nextRandoms :: StdGen -> [Point] -> ([Point], StdGen)
nextRandoms gen0 origins =
let add2d = (\(x1,y1) (x2,y2) -> (x1+x2, y1+y2))
count = length origins
range = (-5::Float, 5)
(changes, gen1) = randomPointUpdates gen0 range count
points = zipWith add2d origins changes
in
(points, gen1)
我们可以使用ghci 解释器测试第二个函数:
λ>
λ> :load q66762139.hs
Ok, one module loaded.
λ>
λ> origins = [(1,2),(2,5),(4,7),(3,3)] :: [Point]
λ> gen0 = mkStdGen 4243
λ>
λ> fst $ nextRandoms gen0 origins
[(3.8172607,-0.54611135),(4.0293427,6.095909),(-0.6763873,6.4596577),(3.042204,-1.2375655)]
λ>
接下来,我们可以使用它来编写一个函数,提供无限的更新位置集,同样使用递归:
randomPointSets :: StdGen -> [Point] -> [[Point]]
randomPointSets gen0 origins =
let (pts1, gen1) = nextRandoms gen0 origins
in pts1 : (randomPointSets gen1 pts1)
请注意,最后一行中的 pts1 : 代码位是“记住”之前设置的位置,可以这么说。
除了递归,我们还可以在这里使用unfoldr :: (s -> Maybe (a, s)) -> s -> [a] 库函数,s 是生成器的状态。
测试程序:
printAsLines :: Show α => [α] -> IO ()
printAsLines xs = mapM_ (putStrLn . show) xs
main = do
let seed = 4243
gen0 = mkStdGen seed
origins = [(1,2),(2,5),(4,7),(3,3)] :: [Point]
allPointSets = randomPointSets gen0 origins -- unlimited supply
somePointSets = take 5 allPointSets
putStrLn $ show origins
printAsLines somePointSets
测试程序输出:
$ q66762139.x
[(1.0,2.0),(2.0,5.0),(4.0,7.0),(3.0,3.0)]
[(3.8172607,-0.54611135),(4.0293427,6.095909),(-0.6763873,6.4596577),(3.042204,-1.2375655)]
[(7.1006527,1.5599048),(8.395166,3.1540604),(-2.486746,9.749242),(2.2286167,-1.868607)]
[(11.424954,-0.13780117),(6.5587683,2.593749),(-2.8453062,7.9606133),(2.1931071,-4.915463)]
[(13.615167,-1.636116),(10.159166,1.8223867),(1.733639,6.011344),(6.2104306,-3.4672318)]
[(16.450119,-2.8003001),(12.556836,5.0577183),(2.8106451e-2,4.4519606),(2.2063198,-0.5508909)]
$
旁注:
在这里,我们使用了生成器状态的手动链接。对于更复杂的伪随机数使用,这种技术可能变得过于繁琐。如果是这样,可以使用来自Control.Monad.Random package 的更强大的一元表示法。