【问题标题】:How to remember previous random value in Haskell如何在 Haskell 中记住以前的随机值
【发布时间】:2021-03-23 11:21:26
【问题描述】:

我正在用 Haskell 编写一个随机游走程序。基本思想是首先随机生成一系列点,然后让这些点随机移动到下一个位置,依此类推。但是,我不能让函数迭代,因为它不记得之前的计算值。如何解决?

以下是我写的代码。问题是每次它只是从我最初给出的位置开始移动。

import Graphics.Gloss
import System.Random

draw :: IO ()
draw = animate FullScreen white (picture [(1,2),(2,5),(4,7),(3,3)])

picture :: [Point] -> Float -> Picture
picture origin num = pictures [translate x y (circle 10) | (x,y) <- randomNext (round num) origin]

randomNext :: Int -> [Point] -> [Point]
randomNext num origin = zipWith (\(x1,y1) (x2,y2) -> (x1+x2,y1+y2)) r origin
    where r = zip (oner num) (oner (num+1)) 
          oner n = take (length origin) $ randomRs (-5::Float,5) (mkStdGen n)

【问题讨论】:

  • 您可能想看看类似的问题,SO-q66708304。本质上,您的 randomNext 函数不应采用随机种子并仅返回值,而应将 generator gen0 作为额外参数,并返回值加上生成器的更新状态,通常作为(values, gen1) 对。
  • @RodrigodeAzevedo - num 最终传递给 mkStdGen 所以它似乎是随机种子。

标签: haskell random


【解决方案1】:

如果我们使用更传统的符号和更短的行稍微重写您的 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 -&gt; Maybe (a, s)) -&gt; s -&gt; [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 的更强大的一元表示法。

【讨论】:

  • 请注意,从 1.2 版开始,随机包也有单子链工具。
  • @jpmarinier 这里还有一个问题。我需要编写一个与动画库交互的函数。要求函数形式为Float -&gt; Picture(其实可以写成Float -> [Point]),即产生下一帧动画。自程序启动以来经过的时间(以秒为单位)。我想的方法很简单:只要用!!就可以得到对应的点。第一秒是(randomPointSets gen0 origins)!! 1、第二个是……!! 2 以此类推。然而随着时间的推移,程序运行的越来越慢。如何处理?非常感谢!
  • @XMZg 您可能想就此提出一个单独的问题,因为它可能会吸引比我更熟悉图形的帮助者的注意力,并帮助其他用户。操作员 !!预计效率低下,因为 Haskell 列表只是链表。在 Haskell 中,不是索引,而是使用库函数(例如 map)进行循环。例如,您有一个 draw1 :: α → IO () 函数,它在给定单个 α 值的情况下执行某些操作。你有一个值列表vs :: [α]。表达式mapM_ draw1 vs 将为您提供对应于所有列表元素的单个组合操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-05
  • 2015-03-07
  • 2012-12-08
相关资源
最近更新 更多