我想要做的是创建一个随机整数列表,没有重复。作为第一步,我有一个函数,它列出了n个随机样本。如何以更加Haskell惯用的方式写这个,其中不需要传入空列表来启动列表?我确信我遗漏了一些基本和基本的东西。
-- make a list of random integers.
-- takes a size, and an empty list.
-- returns a list of that length of random numbers.
f :: Int -> [Int] -> IO [Int]
f l xs | length xs >= l = return (xs)
f l xs = do
r <- randomRIO (1, 40) :: IO Int
f l $ r : x
用法:
*Main> f 6 []
[10,27,33,35,31,28]
最终,此函数将进行过滤以检查重复插入,但这是一个单独的问题。虽然这可能看起来像是家庭作业,但事实并非如此,而是我自己试图掌握State monad用于随机数生成的一部分,并发现我被困在更早的地方。
答案 0 :(得分:3)
好吧,你可以对递归调用的输出进行操作:
f :: Int -> IO [Int]
f 0 = return []
f n = do
r <- randomRIO (1, 40)
xs <- f (n-1)
return $ r : xs
但请注意,您对结果执行的操作很快很重要。在这种情况下,r : xs
是恒定时间。但是,如果用(例如)替换最后一行:
return $ xs ++ [r]
这会将函数的复杂性从线性变为二次,因为每个++
调用必须先扫描所有先前生成的数字序列,然后再附加新数据。
但是你可以这样做:
f n = sequence $ replicate n (randomRIO (1, 40))
replicate
创建一个由[IO Int]
个操作构成的n
长度randomRIO
列表,sequence
获取[IO a]
并将其转换为IO [a]
{1}}按顺序执行所有操作并收集结果。
更简单的是,您可以使用已经是您想要的功能的replicateM
:
import Control.Monad(replicateM)
f n = replicateM n (randomRIO (1, 40))
或以无点样式:
f :: Int -> IO [Int]
f = flip replicateM $ randomRIO (1, 40)
答案 1 :(得分:1)
这使用Set
来跟踪已生成的数字:
import System.Random
import qualified Data.Set as Set
generateUniqueRandoms :: (Int, Int) -> Int -> IO [Int]
generateUniqueRandoms range@(low, high) n =
let maxN = min (high - low) n
in
go maxN Set.empty
where
go 0 _ = return []
go n s = do
r <- getUniqueRandom s
xs <- go (n-1) (Set.insert r s)
return $ r : xs
getUniqueRandom s = do
r <- randomRIO range
if (Set.member r s) then getUniqueRandom s
else return r
以下是一些示例输出:
Main> generateUniqueRandoms (1, 40) 23
[29,22,2,17,5,8,24,27,10,16,6,3,14,37,25,34,30,28,7,31,15,20,36]
Main> generateUniqueRandoms (1, 40) 1000
[33,35,24,16,13,1,26,7,14,11,15,2,4,30,28,6,32,25,38,22,17,12,20,5,18,40,36,39,27,9,37,31,21,29,8,34,10,23,3]
Main> generateUniqueRandoms (1, 40) 0
[]
然而,值得注意的是,如果n
接近范围的宽度,则可以更有效地对范围内所有数字的列表进行混洗并获取第一个n
1}}那个。