【问题标题】:26 of 99 Haskell problems - why the result contains multiple lists with the same head?99 个 Haskell 问题中的 26 个 - 为什么结果包含多个具有相同头部的列表?
【发布时间】:2015-06-26 03:02:19
【问题描述】:

我试图弄清楚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:ys 中的y 部分将使用tails xs 生成,只能用于组成一个列表。

例子:

combination 2 [1,2,3]

首先,我们从tails xs 中提取y 部分,这给了我们三个列表:[1,(not known yet)][2,(not known yet)][3,(not known yet)]。那么最后我们怎么会得到多个以 1 为头部的结果呢?

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

【问题讨论】:

  • 感谢您的澄清,我添加了一个答案,更多地关注为什么存在尾部(用于为我们的组合选择第一个元素)
  • 您可能想尝试一下:[ y:ys | y&lt;-[1] , ys&lt;-[[2],[3],[4]] ]。这里y 只有一个可能的值,但这种理解将产生三个(列表)列表,而不仅仅是一个。
  • @chi 好点。这就是我所缺少的(嗯,不仅如此,这是我主要关心的问题)。

标签: haskell recursion combinations list-comprehension


【解决方案1】:

列表推导在某种程度上定义了嵌套循环。因此,在定义中

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元素,只生成在排列下唯一的解决方案。


我们以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 不是“还不知道”。它已知的,它只是给定列表的尾部。而当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) &lt;- [[]] 的一部分将失败;所以第三行 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]]

【讨论】:

  • 这是一个非常详细的答案。谢谢
【解决方案2】:

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

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

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

在做:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多