【问题标题】:How to shuffle a list in Haskell using random selection如何使用随机选择在 Haskell 中打乱列表
【发布时间】:2019-12-16 10:17:24
【问题描述】:

我正在尝试使用随机数对任何 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 来选择,但 ls 被定义为 [a]?

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

【问题讨论】:

  • 我认为你的函数类型应该是[a] -&gt; IO [a],而不是[a] -&gt; [IO a]
  • @WillemVanOnsem 我试过了,但出现了两个新错误 - 太大,无法在此处发布

标签: list haskell random functional-programming shuffle


【解决方案1】:

发生的情况是shuffle 的类型签名暗示其do-block 的类型为[IO a]。这意味着这个do-block的monad不是你想要的IO,而是列表[]的monad实例,因为这是这里的“最外层”类型构造函数。因此,do 块要求表达式pick ls 具有类型[t] 用于某些类型t,但pick 的类型签名意味着pick ls 具有类型IO a 用于某些类型@ 987654331@。 GHC 抱怨它期望pick ls 有一个列表类型[a](因为do-block 的类型),但它的实际类型是IO a(因为pick 的类型签名)。

我相信您所犯的概念性错误是您将 IO 视为一种修饰符,使其对 IO 友好。因此,如果IO a 是可以使用有效IO 计算生成的a,那么[IO a] 肯定是as 的列表,每个as 都可以使用有效IO 计算生成。但这是错误的!

相反,您应该将IO a 视为一个IO 动作(就像一个配方),在执行时可以产生一个a。如果你想要一个这样的as 的列表,你不想要一个动作/食谱的列表,每个动作/食谱都会产生一个a(即,你不想要[IO a])。相反,您需要一个生成as 列表的操作/配方,因此您需要一个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 约束来随机播放,因为它需要调用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]

【讨论】:

    【解决方案2】:

    您可能正在寻找如下功能:

    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 重写为一个函数,该函数在删除后返回项目列表。

    【讨论】:

    • 沿着这条路线获得亚二次性能会导​​致使用稀疏范围,如here + here,这在最坏的情况下是对数线性的。 IDK 如果可能有严格的线性解决方案,例如通过使用至少允许恒定时间随机访问的arrays
    • @9000:是的,使用随机存取数据结构可以产生线性解决方案。此外,由于“线性全等生成器”在绘制结果对时存在一些问题模式,因此最好在 0n! 之间生成一个随机数。
    • @9000 是的,线性是可能的。 Fisher-Yates 是首选标准方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 2012-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多