【问题标题】:Haskell - How do I get Random Points (Int,Int)Haskell - 我如何获得随机点(Int,Int)
【发布时间】:2013-04-25 06:07:09
【问题描述】:

我正在尝试获取一组随机点 (x,y) 以将图形节点绘制到屏幕上。我需要为传入的每个节点名称随机生成一个点。

我在 SO 页面上找到了此代码,并对其进行了一些修改以适合我,但它并没有真正满足我的需要。

我需要一个随机列表(尽可能随机)(Int,Int)。

无论如何,这是我目前所拥有的,当然,它每次都给出相同的值,所以它不是特别随机:)

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 ()))

任何帮助将不胜感激。

【问题讨论】:

  • 你可以使用 QuickCheck 包中的 Arbitrary 吗?
  • 除了每次调用函数得到相同点分布的问题外,使用两次mkStdGen 1仍然是个坏主意。这将导致 x 坐标和 y 坐标之间的尴尬相关,在最坏的情况下导致仅沿一条线的点。 — 无论如何,包含大量重复代码的两行几乎总是很糟糕,有一种叫做the DRY principle的东西。

标签: haskell random


【解决方案1】:

首先,让我们稍微清理一下您的代码。 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]。由于从未使用过这些值,因此它们不必是 Strings!

现在,它每次都给出相同的值,因为它每次都使用mkStdGen 从相同的种子 (1) 创建一个伪随机生成器。如果你想让它每次都不一样,那么你需要在IO创建一个生成器,它可以基于计算机的随机状态。与其将整个计算放在IO 中,不如传入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,该函数采用生成器,并返回随机Points 的无限 列表。在该函数中,split 用于从中创建两个生成器,每个生成器用于导出随机坐标列表。

pointsForLables 现在分离出为每个标签匹配新随机点的逻辑。我还更改了它以返回更可能有用的标签对和Points。

最后,example3 存在于 IO 中,并创建生成器并将其全部传递到其他纯代码中。

【讨论】:

  • 感谢您的回复!因此,如果我想使用从 example3 传回的元组列表,它需要位于调用者的 do 块内,以便该列表可以用作另一个函数的参数,该函数只接受一个列表 [(a ,观点)]?抱歉,我在这种语言的单子上遇到了最困难的时间!
  • 你想要的东西有多随机。 haskell 中的规则是,“如果每次都不同,它就存在于 IO monad 中。”它必须这样做,因为函数总是为相同的输入返回相同的结果。换句话说,随机性存在于现实世界中,而不是纯粹数学函数的一部分。
  • 不要将大部分程序放在do 块中。将下一步编写为[(a, Point)] -> Whatever 类型的纯函数,并将其插入example3 的链中:>>= return . frobThePairs . pointsForLabels xs。当然,frobThePairs 可以随心所欲地复杂化,它仍然是纯粹的。
  • 这个答案非常简单,并产生了一个简洁的解决方案。谢谢
【解决方案2】:

我最终为此使用了 MonadRandom。我认为代码更清晰,更容易理解。您可以修改以下代码来解决原始问题。

import Control.Applicative
import Control.Monad.Random

type Point = (Float, Float)
type Poly = [Point]

randomScalar :: (RandomGen g) => Rand g Float
randomScalar = getRandomR (-500, 500)

randomPoint :: (RandomGen g) => Rand g Point
randomPoint = (,) <$> randomScalar <*> randomScalar

randomPoly :: (RandomGen g) => Int -> Rand g Poly
randomPoly n = sequence (replicate n randomPoint)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-20
    • 2012-03-10
    • 2014-06-02
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多