随机数没有重复

时间:2015-01-01 02:53:11

标签: haskell random unique

模拟从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循环的合法用例,如果是这样,那怎么做呢?

5 个答案:

答案 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最长的答案相同,但是使用了更通用类型的组合器(这是我喜欢的方式,这可能对你更有用,鉴于你的评论在顶部这个答案)