生成范围内随机整数的唯一列表

时间:2019-11-24 13:49:27

标签: haskell random random-seed

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

使用以下代码,我可以生成一个随机数字列表,但是,这些数字的类型为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

生成一个具有相同种子的随机列表。

2 个答案:

答案 0 :(得分: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]

答案 1 :(得分:0)

  

我看过各种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)

此函数将单音 action 和生成器作为参数。

您提到您不想在输出列表中进行任何重复。这意味着您需要输入列表的随机置换(又称为 shuffle )的子集。 suitable page of the Haskell Wiki中有相应的代码。这个答案是从该代码中无耻地借来的,尤其是在页面结尾处显示“ 不替换的图形”的情况下,因此没有重复之处。

下面,我将以一个(桥牌/扑克)纸牌游戏为例,其输入列表为[0..51],该游戏模型用52张纸牌模拟了标准订购的纸牌。无疑,您不希望在输出列表中出现任何重复,因为在这种情况下,您手中的两位钻石之王会遭到严重皱眉。

下面的通用代码主要由2个功能组成:

功能swapElems直接来自Wiki页面。它与随机性无关,其目的只是“执行”给定的排列。请注意,并非绝对需要为此目的使用数组。 System.Random.Shuffle中的代码将ad hoc树结构用于同一任务。

函数partShuffle生成所需的单声道操作,并给出一个输入列表和一个样本大小(对于52张中的13张卡的常规过桥手为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,但是如果希望能够生成任意数量的样本,则可以自动执行将生成器状态从一个样本生成传递到下一个样本的任务。样本:

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张牌。出于说明目的,前两个是通过手动传递生成器的状态来生成的,而后八个是使用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