简体   繁体   English

在没有种子的情况下在 Haskell 中生成一个范围内的随机整数

[英]Generate a random integer in a range in Haskell without a seed

How can I generate a random number in Haskell from a range (a, b) without using any seed?如何在不使用任何种子的情况下从范围 (a, b) 在 Haskell 中生成随机数?

The function should return an Int and not an IO Int.该函数应该返回一个 Int 而不是 IO Int。 I have a function X that takes and Int and other arguments and outputs something which is not an IO.我有一个函数 X,它接受 Int 和其他参数并输出一些不是 IO 的东西。

If this is not possible, how can I generate a seed using the Time library and the generate a random number in the range with the mkStdGen ?如果这是不可能的,我如何使用 Time 库生成种子并使用 mkStdGen 生成范围内的随机数?

Any help would be really appreciated.任何帮助将非常感激。

A function cannot return an Int without IO , unless it is a pure function, ie given the same input you will always get the same output.一个函数不能在没有IO情况下返回一个Int ,除非它是一个纯函数,即给定相同的输入,您将始终获得相同的输出。 This means that if you want a random number without IO , you will need to take a seed as an argument.这意味着如果您想要一个没有IO的随机数,则需要将种子作为参数。

Using the random library:使用random库:

  • If you choose to take a seed, it should be of type StdGen , and you can use randomR to generate a number from it.如果您选择使用种子,它应该是StdGen类型,您可以使用randomR从中生成一个数字。 Use newStdGen to create a new seed (this will have to be done in IO ).使用newStdGen创建一个新种子(这必须在IO完成)。

     > import System.Random > g <- newStdGen > randomR (1, 10) g (1,1012529354 2147442707)

    The result of randomR is a tuple where the first element is the random value, and the second is a new seed to use for generating more values. randomR的结果是一个元组,其中第一个元素是随机值,第二个元素是用于生成更多值的新种子。

  • Otherwise, you can use randomRIO to get a random number directly in the IO monad, with all the StdGen stuff taken care of for you:否则,您可以使用randomRIO直接在IO monad 中获取随机数,并为您处理所有StdGen内容:

     > import System.Random > randomRIO (1, 10) 6

Without resorting to all kinds of unsafe practices, it is not possible for such a function to have type Int rather than type IO Int or something similar.如果不采取各种不安全的做法,这样的函数不可能具有类型Int而不是类型IO Int或类似的东西。 Functions (or, in this case, constants) of type Int are pure, meaning that every time that you "invoke" the function (retrieve the value of the constant) you are guaranteed to get the same value "returned". Int类型的函数(或者,在这种情况下,常量)是纯的,这意味着每次“调用”函数(检索常量的值)时,都可以保证获得相同的“返回”值。

If you want to have a different, randomly chosen value returned at every invocation, you will need to use the IO -monad.如果您想在每次调用时返回随机选择的不同值,则需要使用IO -monad。

In some occasions, you may want to have a single randomly produced value for the whole program, ie, one that, from the program's perspective behaves as if it were a pure value.在某些情况下,您可能希望为整个程序随机生成一个值,即,从程序的角度来看,它的行为就像是一个纯值。 Every time you query the value within the same run of the program, you get the same value back.每次在程序的同一运行中查询该值时,都会返回相同的值。 As the whole program is essentially an IO -action you could then generate that value once and pass it around, but this may feel a bit clumsy.由于整个程序本质上是一个IO您可以生成该值一次并将其传递,但​​这可能会感觉有点笨拙。 One could argue that, in this situation, it is still safe to associate the value with a top-level constant of type Int and use unsafePerformIO to construct that constant:有人可能会争辩说,在这种情况下,将值与Int类型的顶级常量相关联并使用unsafePerformIO来构造该常量仍然是安全的:

import System.IO.Unsafe  -- be careful!                                         
import System.Random

-- a randomly chosen, program-scoped constant from the range [0 .. 9]            
c :: Int
c = unsafePerformIO (getStdRandom (randomR (0, 9)))
fmap yourFunctionX $ randomRIO (a, b)

or或者

fmap (\x -> yourFunctionX aParam x anotherParam) $ randomRIO (a, b)

The result will then be of type IO whateverYourFunctionXReturns .结果将是IO whateverYourFunctionXReturns类型IO whateverYourFunctionXReturns

If you import Control.Applicative , you can say如果你import Control.Applicative ,你可以说

yourFunctionX <$> randomRIO (a, b)

or或者

(\x -> yourFunctionX aParam x anotherParam) <$> randomRIO (a, b)

which you may find clearer你可能会觉得更清楚

Note that you can get an infinite list of random values using the IO monad and use that [Int] in non-IO functions.请注意,您可以使用 IO monad 获取无限的随机值列表,并在非 IO 函数中使用该[Int] This way you don't have to carry the seed along with you, but still need to carry the list of course.这样您就不必随身携带种子,但当然仍需要携带清单。 Fortunately, there are plenty of list processing functions to simplify such threading, and you can still use the State monad in complicated cases.幸运的是,有很多列表处理函数可以简化这种线程,在复杂的情况下你仍然可以使用State monad。

Also note that you can easily convert an IO Int to an Int.另请注意,您可以轻松地将 IO Int 转换为 Int。 If foo produces an IO Int, and bar takes an Int as its only parameter and returns a non-IO value, the following will do:如果 foo 生成一个 IO Int,并且 bar 将一个 Int 作为其唯一参数并返回一个非 IO 值,则以下将执行:

foo >>= return . bar

Or using do notation:或者使用 do 表示法:

do 
    a <- foo
    return $ bar a

Or using fmap (monads are functors, and <$> is an infix version of fmap ):或者使用fmap (monad 是函子,而<$>fmap的中缀版本):

bar <$> foo

I used SipHash for that purpose我为此使用了 SipHash

import Data.ByteArray.Hash
import Data.ByteString (pack, cons)
import Data.Word (Word8, Word64)

random :: Word64 -> Word64 -> [Word8] -> Double
random a b cs = (subtract 1) . (/(2**63)) . read . drop 8 . show $ sipHash (SipKey a b) (pack cs)

the read and drop 8 and show serve the purpose to drop a newtype that doesn't (or didn't when I implemented this) support any casting读取和删除 8 和显示用于删除不支持(或在我实现此功能时不支持)任何类型转换的新类型

now you want an Int in a range.现在你想要一个范围内的 Int 。 Integer is easier though:不过整数更容易:

random :: Word64 -> Word64 -> [Word8] -> (Integer, Integer) -> Integer
random a b cs (low,high) = let
    span = high-low
    rand = read . drop 8 . show $ sipHash (SipKey a b) (pack cs)
    in (rand `mod` span) + low

of course, you'll still get the same number for the same arguments every time, so you'll need to vary them ie you still pass around arguments, just not returned values too.当然,对于相同的参数,每次你仍然会得到相同的数字,所以你需要改变它们,即你仍然传递参数,只是不返回值。 Whether that's more convenient than a monad depends (for my purpose it was)这是否比 monad 更方便取决于(就我的目的而言)

this is how I made sure arguments (specifically the [Word8] argument) would always be different:这就是我如何确保参数(特别是 [Word8] 参数)总是不同的:

foo bytes = doSomethingRandom bytes
bar bytes = map (\i -> foo (i:bytes)) [1..n]
baz bytes = doSomething (foo (0:bytes)) (bar (1:bytes))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM