【问题标题】:How to randomly shuffle a list如何随机打乱列表
【发布时间】:2020-02-26 08:16:36
【问题描述】:

我有随机数生成器

rand :: Int -> Int -> IO Int
rand low high = getStdRandom (randomR (low,high))

和一个从列表中删除元素的辅助函数

removeItem _ []                 = []
removeItem x (y:ys) | x == y    = removeItem x ys
                    | otherwise = y : removeItem x ys

我想通过从列表中随机选择一个项目,将其删除并将其添加到列表的前面来对给定列表进行洗牌。我试过了

shuffleList :: [a] -> IO [a]
shuffleList [] = []
shuffleList l = do 
                     y <- rand 0 (length l)
                     return( y:(shuffleList (removeItem  y l) ) )

但无法让它工作。我明白了

hw05.hs:25:33: error:
    * Couldn't match expected type `[Int]' with actual type `IO [Int]'
    * In the second argument of `(:)', namely
     ....

有什么想法吗?

谢谢!

【问题讨论】:

  • 在使用结果return (y:res)之前需要res &lt;- ...递归IO调用。

标签: haskell do-notation


【解决方案1】:

由于shuffleList :: [a] -&gt; IO [a],我们有shuffleList (xs :: [a]) :: IO [a]

显然,我们不能将 (:) :: a -&gt; [a] -&gt; [a]a 元素绑定到 IO [a] 值上,而是希望将其绑定到列表 [a] 上,IO [a] 值描述了该列表的计算:

   do
         y <- rand 0 (length l)
         -- return ( y : (shuffleList (removeItem  y l) ) )
         shuffled  <-     shuffleList (removeItem  y l)
         return      y : shuffled

do 表示法中,&lt;- 右侧的值具有 M aM b 等类型,对于某些 monad M(此处为 IO),以及左侧的值&lt;-有对应的类型ab等。

x &lt;- mx 中的x :: a 绑定到由M 类型计算产生/计算的a 类型的纯值,mx :: M a 表示的值表示,计算实际上是作为整个do块表示的组合计算的一部分执行的,而组合计算作为一个整体来执行。

如果例如do 块中的下一行是y &lt;- foo x,这意味着将纯函数foo :: a -&gt; M b 应用于x 并计算出结果为M b 类型的值,表示M-类型计算,然后运行并生成/计算类型为 b 的纯值,然后将名称 y 绑定到该值。

因此,Monad 的本质是在 (可能)不纯的 内部/之间对 的切片,这就是纯 的这两条时间线计算和可能不纯的计算,将纯世界与现实世界的杂质安全分离和隔离。或者从另一方面看,由与现实世界交互的真正不纯代码运行的纯代码(如果MIO)。毕竟,这是计算机程序必须做的事情。


您的removeItem 错误。您应该按位置选择和删除项目,即按索引,而不是按值;并且在任何情况下,在从列表中选择 一个 项后,不要删除超过一项。

y &lt;- rand 0 (length l) 中的y 确实是一个索引。就这样对待它。也将其重命名为i,作为一个简单的助记符。

【讨论】:

  • 我在上面的代码中遇到错误:无法匹配类型a' with Int' `a' 是受类型签名绑定的刚性类型变量:shuffleList :: forall a。 [a] -> IO [a]...
  • 您需要将签名更改为shuffleList :: [Int] -&gt; IO [Int]。但这是错误的解决方法。您的 removeItem 是问题所在。您应该按位置而不是按值选择和删除项目。
【解决方案2】:

通常,使用 Haskell 可以更好地最大化 功能性 代码的数量,而牺牲非功能性(IO 或随机性相关)代码。

在您的情况下,您的“最大”功能组件不是removeItem,而是shuffleList 的一个版本,它采用输入列表和(如Will Ness 所述)确定性整数位置 .列表功能splitAt :: Int -> [a] -> ([a], [a]) 可以在这里派上用场。像这样:

funcShuffleList :: Int -> [a] -> [a]
funcShuffleList _ [] = []
funcShuffleList pos ls =
    if (pos <=0) || (length(take (pos+1) ls) < (pos+1))
      then ls -- pos is zero or out of bounds, so leave list unchanged
      else let (left,right) = splitAt pos ls
           in  (head right) : (left ++ (tail right))

测试:

 λ> 
 λ> funcShuffleList  4  [0,1,2,3,4,5,6,7,8,9]
 [4,0,1,2,3,5,6,7,8,9]
 λ> 
 λ> funcShuffleList 5 "@ABCDEFGH"
 "E@ABCDFGH"
 λ> 


一旦你有了这个,你就可以用更简单的方式引入随机性问题。而且你不需要明确地涉及 IO,因为任何对随机性友好的 monad 都会这样做:

shuffleList :: MonadRandom mr => [a] -> mr [a]
shuffleList [] = return []
shuffleList ls =
    do
       let maxPos = (length ls) - 1
       pos <- getRandomR (0, maxPos)
       return (funcShuffleList pos ls)

... IO 只是MonadRandom 的一个实例。

您可以使用默认的 IO 托管随机数生成器运行代码:

main = do
    let inpList = [0,1,2,3,4,5,6,7,8]::[Integer]

    putStrLn $ "inpList  = " ++ (show inpList)

    -- mr automatically instantiated to IO:
    outList1 <- shuffleList inpList
    putStrLn $ "outList1 = " ++ (show outList1)
    outList2 <- shuffleList outList1
    putStrLn $ "outList2 = " ++ (show outList2)

程序输出:

$ pickShuffle
inpList  = [0,1,2,3,4,5,6,7,8]
outList1 = [6,0,1,2,3,4,5,7,8]
outList2 = [8,6,0,1,2,3,4,5,7]
$ 
$ pickShuffle
inpList  = [0,1,2,3,4,5,6,7,8]
outList1 = [4,0,1,2,3,5,6,7,8]
outList2 = [2,4,0,1,3,5,6,7,8]
$ 

此处的输出不可重现,因为默认生成器是按其启动时间(以纳秒为单位)播种的。

如果您需要完全随机排列,您可以查看herethere - Knuth a.k.a. Fisher-Yates algorithm

【讨论】:

  • 1.接受输入列表和整数位置的函数 removeItem。 2. 与removeItem 的OP 代码不同,您向它们展示的重复调用会导致不均匀的改组。 3.测量length是多余的,可以通过整数比较来完成,length ls应该在函数调用之前知道。
  • 4.当只有一张牌被洗牌时,“shuffleList”这个名字会让人困惑
  • 感谢您的提示。我确实同意“shuffleList”具有高度误导性,但它是 OP 的命名法。也许使用 moveElemToHeadmoveRandomElemToHeadM 与 M 代表一元。
  • 关于第 2 点,在我的示例中,我只是对shuffleList 进行了第二次调用,以表明可以轻松地对调用进行排序;没有更高的要求。我认为从问题的文本中不清楚 OP 是否打算将 shuffleList 函数作为通向适当平衡的整个列表改组的垫脚石。我刚刚在回答的末尾提到了 Fisher-Yates,以应对这种风险。
  • 我认为他们的算法已经符合 haskellwiki 链接中的第一个版本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-05
  • 2012-07-03
  • 2016-03-12
  • 2019-12-16
  • 1970-01-01
相关资源
最近更新 更多