2013-04-25 60 views
4

我想要繪製圖形節點到屏幕的一組隨機點(x,y)。我需要爲傳入的每個節點名稱隨機生成一個點。Haskell - 我如何獲得隨機點(Int,Int)

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

任何幫助將非常感激。

+0

[可能重複(http://stackoverflow.com/questions/2110535/sampling-sequences-of-random-numbers-in-haskell?rq=1) – Nishanth 2013-04-25 06:37:11

+0

你可以隨心所欲的使用從快速檢查包? – 2013-04-25 08:42:56

+0

除了每次調用函數時獲得相同點分佈的問題,使用兩次'mkStdGen 1'仍然是一個壞主意。這將導致x座標和y座標之間的相關性變得尷尬,在最壞的情況下導致沿線的點。 - 無論如何,包含大量重複代碼的兩行代碼幾乎都是壞的,有一些稱爲[DRY原則](http://en.wikipedia.org/wiki/DRY_principle)。 – leftaroundabout 2013-04-25 15:44:13

回答

6

首先,讓我們清理一下你的代碼。有一個複數版本的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,它是清潔的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中,並創建生成器並將其全部傳遞到純代碼中。

+0

感謝您的回覆!因此,如果我想使用從example3傳回的元組列表,它將需要位於調用程序中的do塊內,以便該列表可以用作另一個只需要列表的函數的參數[(a ,點)]?我很抱歉,我用這種語言與monad最難過! – user677786 2013-04-25 06:52:55

+0

你想要什麼隨機數。哈斯克爾的規則是,「如果它每次都不一樣,它就住在IO monad中。」它必須,因爲一個函數總是爲相同的輸入返回相同的結果。換句話說,隨機性生活在現實世界中,而不是純數學函數的一部分。 – 2013-04-25 07:03:51

+0

不要將大部分程序放在'do'塊中。將下一步寫成'[(a,Point)] - > Whatever'類型的純函數,並將其插入'example3'中的鏈中:'>> = return。 frobThePairs。 pointsForLabels xs'。當然,你可以使frobThePairs像你喜歡的那樣複雜,而且它仍然是純粹的。 – MtnViewMark 2013-04-25 07:11:04

0

我最終爲此使用了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)