如何使用随机选择在Haskell中洗牌列表



我正在尝试使用随机数洗牌任何 a 的列表。我在这里问这个问题的原因是因为我已经做了一个函数,我无法弄清楚它为什么不起作用。

pick :: [a] -> IO a
pick xs = do
n <- randomRIO (0, length xs - 1)
return $ xs !! n
shuffle :: [a] -> [IO a]
shuffle ls = do
x <- pick ls
let y = remove x ls
(return x) : shuffle y
-- Remove an element from a list (Only first appearance)
remove :: (Eq a) => a -> [a] -> [a]
remove _ []     = []
remove r (x:xs) = if x == r then xs else x : remove r xs

我得到的错误:

num.hs:31:10: error:
* Couldn't match type `IO' with `[]'
Expected type: [a]
Actual type: IO a
* In a stmt of a 'do' block: x <- pick ls
In the expression:
do x <- pick ls
let y = remove x ls
(return x) : shuffle y
In an equation for `shuffle':
shuffle ls
= do x <- pick ls
let y = ...
(return x) : shuffle y
|
31 |     x <- pick ls
|          ^^^^^^^

对我来说没有意义的是它说它收到了一个类型 [a] 而不是 IO a for pick,但 ls 被定义为 [a]?

如果这有什么根本性的问题,我只是不明白,有没有另一种方法可以在 Haskell 中洗牌一个如此简单的列表?最好没有任何进口。

正在发生的事情是shuffle的类型签名意味着其 do-block 具有类型[IO a]。 这意味着这个 do-block 的 monad 不是您想要的IO,而是列表[]的 monad 实例,因为这是这里的"最外层"类型构造函数。 因此,对于某些类型t,表达式pick ls需要具有类型[t],但pick的类型签名意味着pick ls对于某些类型a具有类型IO a。 GHC抱怨说,它希望pick ls有一个列表类型[a](因为do-block的类型(,但其实际类型是IO a(因为pick的类型签名(。

我相信你犯的概念错误是你认为IO是类型上的一种修饰符,使其对IO友好。 因此,如果IO a是可以使用有效的 IO 计算生成的a,那么[IO a]是可以使用有效的 IO 计算生成的a的列表,这必须是真的。 但这是错误的!

相反,您应该将IO a视为一个 IO操作(如配方(,在执行时可以产生a。 如果你想要一个这样的a的列表,你不想要一个动作/配方的列表,每个动作/配方都会产生一个a(即,你不需要[IO a](。 相反,您需要一个生成a列表的单个操作/配方,因此您需要一个IO [a]

因此,shuffle应该具有类型签名:

shuffle :: [a] -> IO [a]

进行此更改将导致最后一个表达式出现另一个错误:

(return x) : shuffle y

这里的问题来自相同的概念错误:您正在采取(微不足道的(操作/配方来生成x并尝试创建操作/配方列表(尽管现在shuffle y不再是列表,因此存在类型不匹配(。 相反,您希望将其替换为:

xs <- shuffle y  -- use `shuffle y :: IO [a]` action to get `xs :: [a]`
return (x:xs)    -- make an action to return the whole list (type `IO [a]`)

您还会发现需要添加一个Eq a约束来 shuffle,因为它需要调用remove;此外,除非您正确处理空列表情况,否则这将挂起。shuffle的最终版本将是:

shuffle :: (Eq a) => [a] -> IO [a]
shuffle [] = return []
shuffle ls = do
x <- pick ls
let y = remove x ls
xs <- shuffle y
return (x:xs)

这应该有效:

> shuffle [1..10]
[6,8,7,2,5,10,1,9,4,3]

您可能正在寻找如下函数:

shuffle :: [a] ->IO[a]
shuffle [] = return []
shuffle ls = do
x <- pick ls
fmap (x:) (shuffle (remove x ls))

因此,您首先从ls中选择一个元素,然后在列表列表中递归。然后我们可以返回一个列表(x:xs).

以上可以更加优雅。我把这当作一个练习。例如,每次迭代计算列表的length通常不是一个好主意,因为这会使算法O(n2(。此外,您可能希望将pick重写为在删除后返回项目列表的函数。

相关内容

  • 没有找到相关文章

最新更新