在Haskell中生成一个不带种子的范围内随机整数

24

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

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

如果这不可能,如何使用Time库生成种子,然后使用mkStdGen在范围内生成随机数?

任何帮助都将不胜感激。


1
这是一个类似问题的好答案:https://dev59.com/hHE85IYBdhLWcg3whD3j#2738824 - ДМИТРИЙ МАЛИКОВ
5
听起来你最好能够期望 randomInt lo hi = lo 或类似的函数。没有种子或 IO,这个函数每次必须返回相同的值。并且下界和任何其他随机的 Int 一样。 :) - augustss
2
任何考虑使用算术方法生成随机数字的人,显然都处于一种罪恶状态。——约翰·冯·诺伊曼(引自Donald E. Knuth所著《计算机程序设计艺术》第2卷第1页(1981年)) - rickythesk8r
2
我相信在Haskell中获取无IO或种子值的随机数最好的方法是使用像这样的函数 - C. A. McCann
5个回答

47
一个函数如果不是一个纯函数,即相同的输入会得到相同的输出,就不能返回一个没有 IOInt。这意味着如果你想要一个没有 IO 的随机数,你需要将种子作为参数传入。
使用 random 库: - 如果你选择使用种子,它应该是 StdGen 类型的,并且你可以使用 randomR 从中生成一个数字。 - 使用 newStdGen 创建一个新的种子(这必须在 IO 中完成)。
 > import System.Random
 > g <- newStdGen
 > randomR (1, 10) g
 (1,1012529354 2147442707)

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

否则,您可以使用randomRIOIO单子中直接获取随机数,并且所有StdGen相关的事情都已经为您处理好了:

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

7
对于那些这样做的人,不要忘记import System.Random - Nick Knowlson

6

如果不采用任何不安全的做法,这个函数的类型只能是IO Int或类似的类型,而不是Int类型。类型为Int的函数(或常量)是纯的,这意味着每次“调用”函数(检索常量的值)时,都会保证返回相同的值。

如果您希望每次调用都返回不同的随机值,则需要使用IO单子。

在某些情况下,您可能希望为整个程序生成一个单一的随机生成值,即从程序的角度来看,它的行为就像是一个纯值。在同一次运行程序中每次查询该值时,都会得到相同的值。由于整个程序本质上是一个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)))

4
特别提醒,务必仔细阅读 http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-IO-Unsafe.html 中提到的建议和注意事项,不要在未经仔细阅读这些内容之前操作。 - Stefan Holdermans
1
@dblhelix,你的链接已经失效了。 - Muhd

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)

你可能会发现更清晰


2
请注意,您可以使用IO单子获取无限的随机值,并在非IO函数中使用该[Int]。这样,您就不必携带种子,但仍然需要携带列表。幸运的是,有很多列表处理函数可以简化这样的线程处理,并且在复杂情况下仍然可以使用State单子。
另请注意,您可以轻松将IO Int转换为Int。如果foo生成IO Int,并且bar以Int作为其唯一参数并返回非IO值,则以下操作将执行:
foo >>= return . bar

或者使用 do 表示法:

do 
    a <- foo
    return $ bar a

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

bar <$> foo

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)

read、drop 8 和 show 的目的是删除一个新类型,该类型不支持任何转换(或在我实现时不支持)

现在你想要一个范围内的 Int。不过 Integer 更容易:

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

当然,每次使用相同的参数仍会得到相同的数字,因此您需要改变它们,即您仍然需要传递参数,只是不返回值。这是否比单子更方便取决于情况(对于我的目的而言是这样的)

这就是我如何确保参数(特别是[Word8]参数)始终不同的方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接