模拟从40中选择的6个数字的抽奖,我想使用系统随机生成器在Haskell中创建一个数字列表,但是要消除经常出现的重复数据。
如果我有以下内容:
import System.Random
main :: IO ()
main = do
rs <- forM [1..6] $ \_x -> randomRIO (1, 40) :: (IO Int)
print rs
这是中途。但是如何过滤掉重复的内容呢?在我看来,我需要一个某种类型的while循环来构造一个已经在列表中的列表过滤元素,直到列表是所需的大小。如果我可以生成一个无限的随机数列表并在IO monad中过滤它,我相信它会起作用,但我不知道如何处理它。似乎while循环在Haskell中通常被弃用,所以我不确定Haskeller真正的方式。这是一个while循环的合法用例,如果是这样,那怎么做呢?
答案 0 :(得分:4)
如果您不介意使用库,请安装random-shuffle package并像这样使用它:
import System.Random.Shuffle
import Control.Monad.Random
main1 = do
perm <- evalRandIO $ shuffleM [1..10]
print perm
如果你想看看如何使用Haskell中的列表实现一个天真的Fischer-Yates shuffle,请看一下这段代码:
shuffle2 xs g = go [] g (length xs) xs
where
go perm g n avail
| n == 0 = (perm,g)
| otherwise = let (i, g') = randomR (0,n-1) g
a = avail !! i
-- can also use splitAt to define avail':
avail' = take i avail ++ drop (i+1) avail
in go (a:perm) g' (n-1) avail'
main = do
perm <- evalRandIO $ liftRand $ shuffle2 [1..10]
print perm
go
辅助函数的参数是:
perm
- 迄今构建的排列g
- 当前的生成器值n
- 可用商品的长度avail
- 可用的项目 - 即尚未选择成为排列的一部分的项目 go
只是将avail
中的随机元素添加到正在构造的排列中,并使用新的可用列表和新生成器递归调用自身。
要仅从k
中抽取xs
个随机元素,只需在go
而不是k
开始length xs
:
shuffle2 xs k g = go [] g k xs
...
您还可以使用临时数组(在ST或IO monad中)来实现Fischer-Yates类型算法。 shuffleM
中的random-shuffle
函数使用了一种您可能感兴趣的完全不同的方法。
更新:以下是在F-Y风格算法中使用ST数组的示例:
import Control.Monad.Random
import Data.Array.ST
import Control.Monad
import Control.Monad.ST (runST, ST)
shuffle3 :: RandomGen g => Int -> g -> ([Int], g)
shuffle3 n g0 = runST $ do
arr <- newListArray (1,n) [1..n] :: ST s (STUArray s Int Int)
let step g i = do let (j,g') = randomR (1,n) g
-- swap i and j
a <- readArray arr i
b <- readArray arr j
writeArray arr j a
writeArray arr i b
return g'
g' <- foldM step g0 [1..n]
perm <- getElems arr
return (perm, g')
main = do
perm <- evalRandIO $ liftRand $ shuffle3 20
print perm
答案 1 :(得分:4)
您正在寻找的Data.List
来自import Data.List
import System.Random
main = do
g <- newStdGen
print . take 6 . nub $ (randomRs (1,40) g :: [Int])
,以过滤重复项。
{{1}}
答案 2 :(得分:2)
我已经使用C ++中的Fisher Yates Shuffle和一个体面的随机数生成器取得了巨大的成功。如果您愿意分配一个数组来保存数字1到40,这种方法非常有效。
答案 3 :(得分:1)
采用严格的IO方式需要分解nub
,将条件带入尾递归。
import System.Random
randsf :: (Eq a, Num a, Random a) => [a] -> IO [a]
randsf rs
| length rs > 6 = return rs
| otherwise = do
r <- randomRIO (1,40)
if elem r rs
then randsf rs
else randsf (r:rs)
main = do
rs <- randsf [] :: IO [Int]
print rs
如果您知道自己做了什么unsafeInterleaveIO
来自System.IO.Unsafe
可以很方便,允许您从IO生成惰性列表。像getContents
这样的函数以这种方式工作。
import Control.Monad
import System.Random
import System.IO.Unsafe
import Data.List
rands :: (Eq a, Num a, Random a) => IO [a]
rands = do
r <- randomRIO (1,40)
unsafeInterleaveIO $ liftM (r:) rands
main = do
rs <- rands :: IO [Int]
print . take 6 $ nub rs
答案 4 :(得分:0)
您评论道:
目标是学习如何使用过滤单独构建列表。这是一个原始的新手问题
也许你应该改变问题标题呢!无论如何,这是一项非常普遍的任务。我通常定义一个具有一般monadic类型的组合器来做我想要的,给它一个描述性名称(我在这里不太成功:-)然后使用它,如下所示
import Control.Monad
import System.Random
-- | 'myUntil': accumulate a list with unique action results until the list
-- satisfies a test
myUntil :: (Monad m, Eq a) => ([a] -> Bool) -> m a -> m [a]
myUntil test action = myUntil' test [] action where
myUntil' test resultSoFar action = do
if test resultSoFar then
return resultSoFar
else do
x <- action
let newResults = if x `elem` resultSoFar then resultSoFar
else resultSoFar ++ [x] -- x:resultSoFar
myUntil' test newResults action
main :: IO ()
main = do
let enough xs = length xs == 6
drawNumber = randomRIO (0, 40::Int)
numbers <- myUntil enough drawNumber
print numbers
注意:这不是获取6个不同数字的最佳方式,但是作为一个示例,如何一般,使用适用于的过滤器单独构建列表整个清单
它本质上与Vektorweg最长的答案相同,但是使用了更通用类型的组合器(这是我喜欢的方式,这可能对你更有用,鉴于你的评论在顶部这个答案)