我已经查看了各种 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