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

时间:2011-12-07 13:50:30

标签: haskell random

如何在不使用任何种子的情况下从范围(a,b)在Haskell中生成随机数?

该函数应返回Int而不是IO Int。 我有一个函数X,它接受Int和其他参数,并输出一些不是IO的东西。

如果无法做到这一点,如何使用时间库生成种子并使用mkStdGen生成范围内的随机数?

任何帮助都会非常感激。

5 个答案:

答案 0 :(得分:39)

除非是Int,否则函数不能返回IO,除非它是纯函数,即给定相同的输入,您将始终获得相同的输出。这意味着如果您想要一个没有IO的随机数,您需要将种子作为参数。

  • 如果您选择接种种子,则该种子应为StdGen,您可以使用randomR从中生成一个数字。使用newStdGen创建新种子(这必须在IO中完成)。

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

    randomR的结果是一个元组,其中第一个元素是随机值,第二个元素是用于生成更多值的新种子。

  • 否则,您可以使用randomRIO直接在IO monad中获取随机数,并为您处理所有StdGen内容:

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

答案 1 :(得分:4)

如果不诉诸各种不安全的做法,这样的函数就不可能有类型Int而不是类型IO Int或类似的东西。类型Int的函数(或者,在这种情况下,常量)是纯的,这意味着每次“调用”函数(检索常量的值)时,您都可以获得相同的值“返回”

如果您希望在每次调用时都返回一个不同的,随机选择的值,则需要使用IO - monad。

在某些情况下,您可能希望为整个程序创建一个随机生成的值,即,从程序的角度来看,它就像纯值一样。每次在程序的同一次运行中查询值时,都会得到相同的值。由于整个程序本质上是一个IO - 动作,你可以生成该值一次并传递它,但这可能会感觉有点笨拙。有人可能会争辩说,在这种情况下,将值与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)))

答案 2 :(得分:2)

fmap yourFunctionX $ randomRIO (a, b)

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

结果将是IO whateverYourFunctionXReturns类型。

如果你import Control.Applicative,你可以说

yourFunctionX <$> randomRIO (a, b)

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

你可以找到更清楚的

答案 3 :(得分:1)

请注意,您可以使用IO monad获取无限的随机值列表,并在非IO函数中使用[Int]。这样你就不必随身携带种子,但仍然需要随身携带。幸运的是,有很多列表处理函数可以简化这种线程,你仍然可以在复杂的情况下使用State monad。

另请注意,您可以轻松地将IO Int转换为Int。如果foo生成IO Int,并且bar将Int作为其唯一参数并返回非IO值,则以下操作将执行:

foo >>= return . bar

或者使用do notation:

do 
    a <- foo
    return $ bar a

答案 4 :(得分:0)

我为此目的使用了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)

读取和删除8和show的目的是删除一个没有(或者当我实现它时没有)支持任何转换的新类型

现在你想要一个范围内的Int。整数更容易:

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

当然,你每次都会得到相同参数的相同数字,所以你需要改变它们,即你仍然传递参数,只是没有返回值。这是否比monad更方便取决于(出于我的目的)

这就是我确定参数(特别是[Word8]参数)总是不同的方式:

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