26 of 99 Haskell问题 - 为什么结果包含多个具有相同头部的列表?

时间:2015-04-19 12:48:28

标签: haskell recursion combinations list-comprehension

我想弄清楚99 Haskell problems问题26的解决方案之一是如何工作的。解决方案如下:

combination :: Int -> [a] -> [[a]]
combination 0 _ = [ [] ]
combination i xs = [ y:ys | y:xs' <- tails xs, ys <- combination (i-1) xs']

我无法理解如何有多个列表具有相同的头部。对我来说,y中使用y:ys生成的tails xs部分只能用于撰写一个列表。

示例:

combination 2 [1,2,3]

首先,我们从y获取tails xs部分,它为我们提供了三个列表:[1,(not known yet)][2,(not known yet)][3,(not known yet)]。那么最后我们得到多个结果以1作为头部?

我也无法理解为什么列表[3]不会出现在结果中?它肯定会出现在tails xs生成的一个列表中。我不想在一个单独的问题中提出这个问题 - 我希望这很好。

2 个答案:

答案 0 :(得分:3)

列表推导以某种方式定义嵌套循环。因此,在定义中

combinations n xs =

我们可以阅读代码

        [ y:ys | y:t <- tails xs, ys <- combinations (n-1) t]

作为

        for each (y:t) in (tails xs)
            for each ys in (combinations (n-1) t) 
                produce (y:ys) as a new element of the resulting list

换句话说,从列表中选择n元素意味着选择一些元素,并在列表中选择n-1个元素。

此定义的非确定性特征通过生成所有可能解决方案的列表来表示。我们只选择元素的n-1元素,只在排列下生成 unique 的解决方案。


我们以xs = [1,2,3]为例。 tails [1,2,3]产生什么?

当然是[[1,2,3], [2,3], [3], []]。现在,这相当于

[ r | r <- [[1,2,3], [2,3], [3], []] ]

这意味着,从该列表中抽取r,连续地获取其元素的值。 r无可辩驳的模式; (y:t)是一种可反驳的模式,即它将无法匹配[]元素:

[ (y,t) | (y:t) <- tails [1,2,3]]
  =>  [(1,[2,3]), (2,[3]), (3,[])]

所以你看,t不是&#34;还不知道&#34; 已知,它只是给定列表的尾部。当y与3匹配时,t[]匹配。

此外,

[ (y,q) | (y:t) <- tails [1,2,3], q <- [10,20]]
 =>  [(1,10), (1,20), (2,10), (2,20), (3,10), (3,20)]

这很有说服力,希望清除您的第一个问题:对于每个匹配的(y:t)模式,q都是从[10,20]中提取的,即它也会采用列表中的值(此处为[10,20]),对于每个y连续,就像在嵌套循环中一​​样。


对于combinations 2 [1,2,3]我们的例子

  combinations 2 [1,2,3]
=
  for each (y,t) in [ (1,[2,3]), (2,[3]), (3,[]) ]
      for each ys in (combinations 1 t)
          produce (y:ys)
=
  for y = 1, t = [2,3]
      for each ys in (combinations 1 [2,3]) produce (1:ys) , and then
  for y = 2, t = [3]
      for each ys in (combinations 1 [3])   produce (2:ys) , and then
  for y = 3, t = []
      for each ys in (combinations 1 [])    produce (3:ys)

combinations 1 [][],因为tails [][[]],模式匹配(y:t)[]作为生成器(y:t) <- [[]]的一部分将失败;因此第三个 for (在解决方案的头部会有3)不会产生任何解决方案 - 因为没有其他元素可供使用它有权选择第二个元素,因为我们需要总共选择2个元素; 3确实参与其他解决方案的尾部,因为它应该如此)。

第二个 for 行显然只生成一个解决方案[2,3]。第一个 for 行会产生什么?

      for each ys in (combinations 1 [2,3]) produce (1:ys)

combinations 1只占用一个元素,因此会产生[2][3];因此,第一个for行生成两个解,[1,2][1,3]。或者更详细地说,

      combinations 1 [2,3] 
    =
      for y = 2, t = [3]
          for each ys in (combinations 0 [3]) produce (2:ys) , and then
      for y = 3, t = []
          for each ys in (combinations 0 [])  produce (3:ys)

combinations 0总是生成一个空列表的单个解决方案(单个列表,其中列表为空唯一元素[ [] ],表示从列表中选取0个元素的解决方案)。

总的来说,返回了三个解决方案的列表[[1,2], [1,3], [2,3]]

答案 1 :(得分:2)

这里要注意的核心问题是问题的递归性

我们如何从列表中选择i个项目?

  • 如果列表为空,则表示没有组合。所以这不是一个有趣的案例。
  • 如果列表不是空的 - 我们可以选择我们组合的第一个项目
    • 如果我们选择它,我们仍然需要从列表的其余部分中选择i-1个项目
    • 如果我们不选择它,我们仍然需要从列表的其余部分中选择i个项目

这样做的:

[ y:ys | y:xs' <- tails xs, ys <- combination (i-1) xs']
  • 查看xs的所有尾巴,即查看为i组合选择 first 元素的所有可能性
  • 选择第一个元素,如上所述我们现在有i-1项,因此,我们需要将该元素连接到i-1项与其余项的组合。