haskell中的随机数。然后洗牌

时间:2015-11-15 18:07:05

标签: haskell random numbers

我正在尝试编写一个函数,当给定列表时会以随机顺序返回一个lsit。

这就是我想到的方法(列表长度为52): 生成1到52之间的随机数,取该列表的元素。

a = [1,2,3..] !! getRandom 52

然后递归调用相同的函数..生成1到51之间的随机数,并使用我们选择删除的第一个元素调用列表。

(delete a [1,2,3..]) !! getRandom 51

依此类推。在将所有元素放入列表中后,我们得到相同的列表但是已经洗牌。这是我的随机数函数:

getRandom :: Int -> IO Int
getRandom x = getStdRandom (randomR (1,x))

但是因为这个函数返回IO Int而不是Int我不能用它来从列表中取随机元素。我能在这做什么?

在大学任务上,我需要洗牌一副52张牌。我们还没有通过Monads和先进的东西。那么,是否有一种简单的方法来获取52张牌并将其洗牌?

3 个答案:

答案 0 :(得分:3)

在haskell中,你不能凭空创造一个随机数。您可以从种子创建它,但每次使用相同的种子时,您都会得到相同的随机数序列。

或者你接受外界的种子。然后你就可以进入"每次运行程序时都会有一个不同的种子,或者你让一个库从系统时间中选择一个,或者它是这样做的 - 无论你是在IO-land中。如果你走这条路线,那么你在IO操作中选择你的随机数并用它随机洗牌。洗牌本身将是一个纯粹的操作。然后,您可以将混洗列表打印到IO,但是无法逃脱IO-land。

如果这项任务的重点是学习如何改变名单,那么只要你得到适当的洗牌(这很麻烦),你获得随机数的方式可能并不重要。 / p>

所以写一个函数

shuffle :: Int->[a]->[a]

其中第一个参数是随机种子。然后你可以留在纯净土地并使用System.Random函数,如果你需要,你可以创建更多的随机数。如果您的程序在调用时以完全相同的方式对列表进行洗牌,请不要失望。

答案 1 :(得分:0)

您实际上并不需要IO monad生成随机数,因为您可以通过为System.Random模块中的mkStdGen函数提供种子来创建随机数生成器。这样,只需提供相同的种子,您的计算就可以重现。

Import System.Random
let rg1     = mkStdGen seed
let randils = randoms rg1 :: [Double] -- infinite list of random values

根据Rosetta网站,Knuth算法的核心是这样的,N个可置换项的编号从0到N-1: https://www.rosettacode.org/wiki/Knuth_shuffle

for i from (N-1) down to 1  do:
    let j = random integer in range 0 <=j <=i
    swap items[i] with items[j]

所以这是函数式编程,可变数组有一个小问题。 Rosetta上有两个Haskell示例代码。第一个只是对普通的Haskell列表使用随机访问,因此接受效率低下。第二个将Monadic的东西与Data.Sequence模块一起使用。

下面的代码是一个可能的折衷方案,该代码在使用折叠将它们应用于常规Data.Map关联数组之前,会生成一个随机的交换操作列表。

关键函数是randomPerm,它以随机Double值的列表作为主要参数。

简单的主函数仅生成一个样本。在我的系统上,输出为«[5,2,8,3,0,4,6,1,7]»。您可以使用以下表达式来概括并获得无限数量的随机排列: «L.map(randomPerm itemCount)(chunksOf(itemCount-1)randils)»

    -- Haskell code to generate random permutations of N items,
    -- using language-provided random numbers generation facilities.

    -- Knuth's shuffle  algorithm  for a zero-based array of size N:
    -- https://www.rosettacode.org/wiki/Knuth_shuffle


import  Data.List as L
import  Data.Map  as M
import  System.Random
type MI = M.Map Int Int  -- auxiliary associative array type


-- main loop of Knuth's algorithm:
makeSwapPairList :: Int -> [Double] -> [(Int,Int)]
makeSwapPairList itemCount rands = 
    let ls = reverse $ enumFromTo 1 (itemCount-1)
    in  L.map makeSwapPair (zip ls rands)

makeSwapPair :: (Int,Double) -> (Int,Int)
makeSwapPair (p, randX) = (p, floor ((fromIntegral (p+1))*randX))

-- generates a map where integers below itemCount are identically mapped
makeTrivialMap :: Int -> MI
makeTrivialMap itemCount = 
  let  ls1 = enumFromTo 0 (itemCount-1)
  in   M.fromList (zip ls1 ls1)

-- apply just one swap operation to an MI map:
applySwap :: MI -> (Int,Int) -> MI
applySwap ma p =
    let (i,j)=p ; mi = ma ! i ; mj = ma ! j
    in  M.insert i mj  (M.insert j mi ma)

-- apply a list of swap operations to an MI map:
applySwapList :: MI -> [(Int,Int)] -> MI
applySwapList ma pl = L.foldl' applySwap ma pl

-- returns a random permutation as a list of Int values:
randomPerm :: Int -> [Double] -> [Int]
randomPerm itemCount rands =
    let ma0 = makeTrivialMap itemCount
        ma1 = applySwapList ma0 (makeSwapPairList itemCount rands)
        pls1 = M.toAscList ma1
    in L.map snd pls1


-- simplified main program, for just one random permutation 
main =
  do
    let seed = 4242
    let itemCount = 9  -- should become 52

    -- random number generation in the [0,1( interval:
    let rg1     = mkStdGen seed
    let randils = randoms rg1 :: [Double] -- infinite list of random values
    let rands1  = L.take (itemCount-1) randils -- just enough for one sample

    let myRandomPerm = randomPerm itemCount -- function currying
    let perm1 = myRandomPerm rands1
    putStrLn (show perm1)

答案 2 :(得分:-1)

在此处使用mapM。尝试:

main = do 
    lst <- mapM getRandom [1..100]
    print lst

mapM就像地图一样,但是对于monadic操作(例如IO)。