繁体   English   中英

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

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

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

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

如果这是不可能的,我如何使用 Time 库生成种子并使用 mkStdGen 生成范围内的随机数?

任何帮助将非常感激。

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

使用random库:

  • 如果您选择使用种子,它应该是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

如果不采取各种不安全的做法,这样的函数不可能具有类型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)))
fmap yourFunctionX $ randomRIO (a, b)

或者

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

结果将是IO whateverYourFunctionXReturns类型IO whateverYourFunctionXReturns

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

yourFunctionX <$> randomRIO (a, b)

或者

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

你可能会觉得更清楚

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

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

foo >>= return . bar

或者使用 do 表示法:

do 
    a <- foo
    return $ bar a

或者使用fmap (monad 是函子,而<$>fmap的中缀版本):

bar <$> foo

我为此使用了 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 和显示用于删除不支持(或在我实现此功能时不支持)任何类型转换的新类型

现在你想要一个范围内的 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))

暂无
暂无

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

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