关于Haskell中随机数的另一个问题 [英] Another question about random numbers in Haskell

查看:64
本文介绍了关于Haskell中随机数的另一个问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作以下版本的《口袋妖怪黄金和白银》中的Voltorb游戏哈斯克尔.现在,为了生成电路板,我想要一个(l,r,v)三元组的列表,其中l是线,r是行,v是字段的值.

I am trying to make a version of the Voltorb game from Pokemon Gold and Silver in Haskell. Now for generation of the board, I want to have a list of (l,r,v) triplets where l is the line, r is the row and v is the value of the field.

值l和r是通过列表理解实现的,因为它们每次都应该相同.至于v,虽然我找不到实现它的选项,所以它是随机"的0、1、2或3(我知道Haskell纯粹是功能性的,并且没有真正的随机性,这就是我之所以这样做的部分原因与此斗争).

Values l and r are implemented with list comprehension since they should be the same every time. As for v though I can't find an option to implement it so that it is 0,1,2 or 3 "randomly" (I know that Haskell is purely functional and there is no true randomness, that's part of the reason why I struggle with this).

如果有人可以提供帮助,我将非常感激.如果您也可以简短说明为何该解决方案有效,那将对我有很大帮助.

If anyone could help with this I'd be very grateful. If you could also maybe give a short explanation as to why that solution works, that would help me a lot.

我当前对l和r的实现:

My current implementation of l and r:

field n = [(l,r,v) | l <- [0..n], r <- [0..n]]

推荐答案

如果我正确地理解了这个问题,则每个(Int,Int)板位必须有一个 随机值.因此,无法通过在理解列表中添加第三个子句来解决该问题,例如:

If I understand the question correctly, there is to be one random value per (Int, Int) board position. So the problem cannot be solved by adding a third clause into the comprehension list, such as:

field n = [(l,r,v) | l <- [0..n], r <- [0..n], v <- someRandomStuff]

因为 field 表达式的长度将是(n + 1)x(n + 1)x(随机填充物的长度),而您想要的只是(n + 1)x(n + 1).

as the length of the field expression would then be (n+1)x(n+1)x(length of random stuff), and what you want is just (n+1)x(n+1).

一种可能性在于分两个步骤进行操作:

A possibility consists in operating in two steps:

  1. 生成介于0到3之间的所需(n + 1)*(n + 1)个随机值
  2. 将其与(l,r)值相结合

我假设读者了解命令式语言的伪随机数生成.

I assume the reader understands pseudo-random number generation from imperative languages.

给出种子,可以使用函数

Given a seed, you can use a throw-away random number generator returned by function mkStdGen to generate the random values, using function randomRs. Let's use a ghci session as a testbed.

关于步骤1:

 λ> import System.Random
 λ> :t randomRs
randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
 λ> 
 λ> seed1=42
 λ> 
 λ> getVSeq n seed = let rng0 = mkStdGen seed  in take  ((n+1)^2) (randomRs (0,3) rng0)
 λ> 
 λ> getVSeq 5 seed1
[1,1,3,0,2,1,0,1,0,1,3,1,2,0,2,3,1,1,3,2,0,2,2,0,2,0,0,0,1,0,2,1,0,2,0,1]
 λ> 
 λ> length $ getVSeq 5 seed1
36
 λ> field0 n = [(l,r) | l <- [0..n], r <- [0..n]]
 λ> field0 5
[(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,0),(1,1),(1,2),(1,3),(1,4),(1,5),(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(3,0),(3,1),(3,2),(3,3),(3,4),(3,5),(4,0),(4,1),(4,2),(4,3),(4,4),(4,5),(5,0),(5,1),(5,2),(5,3),(5,4),(5,5)]
 λ> 

 λ> 
 λ> 
 λ> length  $ field0 5
36
 λ> 

现在关于步骤2,功能 zip 几乎解决了我们的问题,除了我们没有完全得到三元组:

Now regarding step 2, function zip almost solves our problem, except we do not exactly get triplets:

 λ> 
 λ> sol0 n seed = zip (field0 n) (getVSeq n seed)
 λ> sol0 5 seed1
[((0,0),1),((0,1),1),((0,2),3),((0,3),0),((0,4),2),((0,5),1),((1,0),0),((1,1),1),((1,2),0),((1,3),1),((1,4),3),((1,5),1),((2,0),2),((2,1),0),((2,2),2),((2,3),3),((2,4),1),((2,5),1),((3,0),3),((3,1),2),((3,2),0),((3,3),2),((3,4),2),((3,5),0),((4,0),2),((4,1),0),((4,2),0),((4,3),0),((4,4),1),((4,5),0),((5,0),2),((5,1),1),((5,2),0),((5,3),2),((5,4),0),((5,5),1)]
 λ> 

因此,我们需要对 sol0 的结果进行一些分析:

So we need to massage the result of sol0 a little bit:

 λ> 
 λ> sol1 n seed = let flatten = (\((a,b),c) -> (a,b,c))  in  map flatten (sol0 n seed)
 λ> sol1 5 seed1
[(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]
 λ> 

这就是我了解的您想要的.如果这是在您的应用程序中唯一使用随机数,那么这可能就足够了.否则,恐怕需要在Haskell函数编程上下文中建立一些有关随机数生成的知识.您可能想在此处或<一个href ="https://en.wikibooks.org/wiki/Haskell/Libraries/Random" rel ="nofollow noreferrer">有.

So this is what I understood you wanted. If this is the sole use of random numbers in your application, this could be good enough. Otherwise, I am afraid there is a need to build some knowledge about random number generation in a Haskell functional programming context. You might want to start here or there.

同样,正如Thomas M. DuBuisson所提到的,这已经在几个SO问题中得到了解决.您可以使用本地搜索引擎.例如,这是最近的一个.

Also, as mentioned by Thomas M. DuBuisson, this has been addressed in several SO questions. You can use the local search engine. Here is one of the recent ones for example.

在这种情况下,您需要有一个函数,该函数需要一个预生成的生成器,并将字段"三元组列表和生成器的(最终状态)返回为(list,finalRng)对.

In that case, you need to have a function that takes a pre-built generator, and returns BOTH the "field" triplet list and the (final state of the) generator as a (list,finalRng) pair.

您可以将艰苦(随机)的工作分包给一个函数,该函数返回仅包含v值列表和生成器最终状态的另一对更简单的函数对.该函数可以递归方式编写,如下所示.

You can subcontract the hard (random) work to a function that returns another, simpler pair with just the list of v values and the final state of the generator. That function can be written in recursive fashion, as show below.


import  System.Random
import  Control.Monad.Random

-- returns the random v values AND the final state of the generator
seqAndGen :: RandomGen tg => (Int,Int) -> Int-> tg -> ([Int], tg)
seqAndGen range count rng0 =
    if (count <= 0)
        then ([],rng0)
        else
            let (v,rng1) = randomR range rng0
                nextSeq  = seqAndGen range (count-1) rng1  -- recursive call
            in
                (v:(fst nextSeq), snd nextSeq)

-- returns the "field" values AND the final state of the generator
fieldAndGen :: RandomGen tg => Int -> tg -> ([(Int,Int,Int)], tg)
fieldAndGen n rng0 =
    let  field0  = [(l,r) | l <- [0..n], r <- [0..n]]
         range   = (0,3)       -- at that level, range gets hardwired
         count   = (n+1)*(n+1) -- number of field/board positions
         pair    = seqAndGen range count rng0  -- the hard work
         vSeq    = fst pair
         endRng  = snd pair
         flatten = \((a,b),c) -> (a,b,c)
         field   = map flatten  (zip field0 vSeq)
    in
         (field, endRng)

主程序:

main = do
    let mySeed = 42
        n      = 5
    putStrLn $ "seed=" ++ (show mySeed) ++ "  " ++ "n=" ++ (show n)
    -- get a random number generator:
    let rng0    = mkStdGen mySeed  

    let (field, endRng) = fieldAndGen n rng0
        fieldv = map  (\(a,b,c) -> c)  field
    putStrLn $ "endRng = " ++ (show endRng)
    putStrLn $ "field  = " ++ (show field)

程序输出:


seed=42  n=5
endRng = 1388741923 1700779863
field  = [(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]

请注意,存在一个可能的变体,您无需传递生成器,而是传递由函数 splitAt 为此目的.但这假设您仅将随机性用于v值,而没有别的使用,因此它的通用性和灵活性稍差一些.

Note that there is a possible variant where instead of passing around the generator, you pass around the infinite list of v values as generated by function randomRs. It is handy to use function splitAt for such a purpose. But that assumes you are using randomness just for v values and nothing else, so it is a bit less general and less flexible.

这篇关于关于Haskell中随机数的另一个问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆