【问题标题】:Generate unique lists of random integers within range生成范围内随机整数的唯一列表
【发布时间】:2019-11-24 13:49:27
【问题描述】:

我正在尝试编写一个函数,该函数将采用两个参数,一个是随机整数应在范围内的上限。另一个参数是要生成的随机数列表的数量。现在我只是想能够生成给定范围内的唯一随机数列表。

使用下面的代码,我可以生成一个随机的数字列表,但是,这些数字的类型是 randomList 5 (0,10) :: IO [Int] 而不是 [Int]。该代码也不能确保一个整数在列表中不重复。

import Test.QuickCheck

t :: Int -> (Int,Int) -> Gen [Int]
t n r = vectorOf n (choose r)

randomList n r = head `fmap` (sample' $ t n r)

虽然我仍然找不到解决方案,但我已经查看了各种 StackOverflow 答案。许多答案只能使用System.Random生成一个具有相同种子的随机列表

【问题讨论】:

  • 对于一长串唯一随机数,使用加密并使用相同的密钥加密 0、1、2……。对 64 位数字使用 64 位加密,对 128 位数字使用 128 位加密。当您有一个唯一随机数的大列表时,然后将大列表中的数字分配给您的各个子列表。
  • 只需将您的列表设为[start..end],然后是shuffle
  • 如果我可以问,您使用 Test.QuickCheck 而不是 System.Random 的原因是什么?
  • @您可以纯粹地生成随机数,但是您需要一个 StdGen 并且要制作一个,您将使用 mkStdGen 函数,该函数将要求一个种子值......现在我可以想象你是问“我会从哪里得到一个随机种子?”。老实说,无论出于何种目的,您需要改组数组,请在 IO monad 中执行该任务,例如 shuffledList >>= return . doSomeFunkyStuff 。现在 doSomeFunkyStuff 函数就像您所要求的那样是纯的。问题出在哪里..?
  • 我想提醒一下.. 许多 Haskell 程序以 main :: IO () 开头。但从另一个角度来看……这类似于一些 JavaScript 编码人员询问他们是否可以从异步代码中返回一个值。是的,您必须在 IO monad 中执行您的任务,例如从用户那里获取输入,获取随机值并返回结果以显示,但其中的所有点点滴滴。像doSomeFunkyStuff 将是纯函数代码,可能存在于另一个模块等......

标签: haskell random random-seed


【解决方案1】:

您只需 System.Random 即可做到这一点。

getStdRandom (randomR (0, 100))

但您也可以使用QuickCheck 来实现。您可以使用choose :: Random a => (a, a) -> Gen a,它可用于任何具有Random 实例的类型。你提供一个范围。或elements :: [a] -> Gen a。它接受一个纯值列表(还没有在Gen 中),选择一个并在Gen 中返回它。

generate $ choose (0,100)
generate $ elements [0..100]

【讨论】:

  • 所有解决方案都将返回IO。如果您在多次运行这些函数以及从 IO [IO Int]IO [Int] 时需要帮助,请告诉我。
  • 如果您需要加密安全的东西,请查看包cryptonite
  • 有没有办法生成[Int]?我找到了一种生成我想要的方法,但是,它给出了IO [Int]
  • 没有办法在不涉及IO 的情况下随机生成[Int]。随机数生成器需要通过读取时间或生成由特殊硬件设备创建的随机种子来获取随机种子。从硬件设备读取时间或数据都是IO 操作。
  • 您可以在IO a 的函数中处理IO [Int] 之类的数据,就好像它是[Int],但该函数最后必须是IO。一旦您进入IO,您就无法逃脱。例如main :: IO (), ``` main = do x
【解决方案2】:

我已经查看了各种 StackOverflow 答案,尽管我仍然不能 找到解决办法。 许多答案只能使用 System.Random 生成一个具有相同种子的随机列表。

您始终可以重复使用随机数生成器的新状态来生成更多样本,除非您使用碰巧“掉球”的功能之一,即无法将新状态与实际随机样本一起返回.

我建议使用Control.Monad.Random 中定义的runRand 函数:

-- Run a random computation using the generator g, returning the result
-- and the updated generator.
runRand :: RandomGen g => Rand g a -> g -> (a, g)

这个函数接受一个单子动作和一个生成器作为参数。

您提到您不希望在输出列表中出现任何重复。这意味着您需要输入列表的随机 permutation(也称为 shuffle)的子集。在suitable page of the Haskell Wiki 中有这方面的代码。这个答案无耻地从该代码中借用,特别是在页面末尾附近显示“Drawing without replacement”,因此没有重复。

下面,我将使用(桥牌/扑克)纸牌游戏的示例,其中输入列表为[0..51],它模拟了具有 52 张纸牌的标准有序纸牌套牌。当然,您不希望在输出列表中出现任何重复,因为在这种情况下,您手中有两张钻石王是非常不受欢迎的。

下面的通用代码主要由2个函数组成:

函数swapElems 直接来自维基页面。它与随机性无关,其目的只是“执行”给定的排列。请注意,并非绝对有必要为此目的使用数组。 System.Random.Shuffle 中的代码为同一任务使用了一个特殊的树结构。

函数partShuffle 生成所需的一元动作,给定一个输入列表和一个样本大小(13 表示普通桥牌,其中 52 张牌中有 13 张)。

import  Data.List    (sort, mapAccumL)
import  Data.Tuple   (swap)
import  Text.Printf  (printf)
import  Data.Array
import  Data.Array.MArray
import  Data.Array.ST
import  System.Random
import  Control.Monad.Random


swapElems  :: [a] -> [(Int, Int)] -> [a]
swapElems xs swaps = elems $ runSTArray (do
    arr <- newListArray (0, maxIx) xs
    mapM_ (swap arr) swaps
    return arr)
    where
        maxIx   = length xs - 1
        swap arr (i,j) = do
            vi <- readArray arr i
            vj <- readArray arr j
            writeArray arr i vj
            writeArray arr j vi


partShuffle :: MonadRandom mr => [a] -> Int -> mr [a]
partShuffle xs sampleSize =
  do
    let  maxIx     = length xs - 1
         ranStep i = do
                       j <- getRandomR (i, maxIx)
                       return (i, j)
    swapList  <- forM  [0 .. (min (maxIx - 1) sampleSize)]  ranStep
    let  xs2  =  swapElems xs swapList
    return $ take sampleSize xs2

函数 stUnfold 未在 Wiki 页面中提及,但如果您希望能够生成任意数量的样本,则可以自动将生成器状态从一个样本生成传递到下一个样本的任务:

stUnfold :: (s -> (a,s)) -> s -> Int -> ([a], s)
stUnfold go s0 count = let  auxList     =  replicate count ()
                            xgo state _ =  swap $ go state
                       in  swap  $  mapAccumL xgo s0 auxList

在签名中输入s代表随机数生成器状态的类型。

 λ> stUnfold  (\n -> (n*n, n+1))  0  10
 ([0,1,4,9,16,25,36,49,64,81],10)
 λ> 

我们现在可以使用这些函数来生成 10 个随机“手”,每个“手”有 13 张牌。为了便于说明,前两个是通过手动传递生成器的状态来生成的,接下来的 8 个是使用stUnfold 实用函数一次生成的。

-- for prettyprinting purposes:
myShow :: [Int] -> String
myShow = concatMap  (printf "%02d ")


main = do
    let  -- setup random number generator:
         seed = 4242
         rng0 = mkStdGen seed
         -- standard bridge/poker card deck of 52 cards
         -- 13 cards out of 52, no replacement, 10 samples:
         itemCount   = 52
         deck        = [0 .. (itemCount-1)]
         sampleSize  = 13
         sampleCount = 10
    -- type of action1 is: Rand StdGen [Int]
    let action1          = partShuffle deck sampleSize

    -- passing random generator manually:
    let  (sample1, rng1a) = runRand action1 rng0
    putStrLn  $  myShow $ sample1
    let  (sample2, rng1b) = runRand action1 rng1a
    putStrLn  $  myShow $ sample2

    -- automated random generator passing:
    let  (samples, rng2) = stUnfold (runRand action1) rng1b (sampleCount - 2)
    mapM_  (\ls -> putStrLn  $  myShow ls)  samples

程序输出:

$ cards
49 38 15 14 03 24 22 02 37 43 44 31 39 
48 34 46 07 21 29 28 01 50 08 14 12 51 
27 31 48 19 20 01 16 04 15 35 30 50 46 
02 42 29 48 35 05 19 45 18 34 44 09 51 
37 47 49 05 19 16 50 21 02 22 39 11 12 
04 13 07 23 16 36 27 22 20 21 11 49 38 
45 02 27 36 13 04 14 12 11 26 09 38 00 
03 14 13 40 32 05 06 29 30 27 43 15 38 
48 15 00 04 11 10 13 07 21 36 09 38 37 
14 48 33 10 05 07 50 03 51 41 29 44 12 
$

请注意,您也可以使用返回 N 个样本的单个单子操作。例如,Wiki 页面中的 grabble 函数会返回这样的一元操作。

也可以使用replicateM 函数获得一个生成一个样本的单子动作来生成N 个样本的动作。下面的代码稍微掩盖了幕后发生的事情,但它确实产生了与上面完全相同的输出。

    let action1   =  partShuffle deck sampleSize
        action10  =  replicateM 10 action1
    let (samples, rng3) = runRand action10 rng0
    mapM_  (\ls -> putStrLn  $  myShow ls)  samples

【讨论】:

    猜你喜欢
    • 2018-12-17
    • 2011-08-02
    • 1970-01-01
    • 2015-12-22
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    相关资源
    最近更新 更多