下面的函数计算List元素的可能组合。
不返回相同的列表元素,也只返回唯一的元素:
def combinations[T](l: List[T]): List[(T,T)] = l match {
case Nil => Nil
case h::Nil => Nil
case h::t => t.map(x=>(h,x)) ++ combinations(t)
}
列表(1,2,3)返回列表((1,2),(1,3),(2,3))
这个解决方案(不是我的)很优雅,但我想知道它背后的直觉。代码中是否包含我不知道的列表元素的泛型属性?我知道为什么这个解决方案有效,但我不确定如何达到这个解决方案?
答案 0 :(得分:3)
当您考虑如何手动构建所有组合时,它实际上非常直观。例如,取List(1, 2, 3, 4)
。为了有条不紊地创建所有组合,我将获取列表1
中的第一个元素,然后将其与所有剩余元素组合。
(1, 2), (1, 3), (1, 4)
这些是包含1
的所有可能组合。现在让我们找到包含2
的所有组合,但我们不需要包含1
的组合,因为我们已经拥有它们。这意味着我们将采用与列表中其余元素的组合。
(2, 3), (3, 4)
然后使用3
:
(3, 4)
你看到了这种模式吗?我们取列表的第一个元素,然后将它与列表中的所有剩余元素(尾部)配对。这就是代码的这一部分:
case h :: t => t.map(x => (h, x))
// 1 :: List(2, 3, 4) => List((1, 2), (1, 3), (1, 4))
然后我们移动到列表的下一个元素,并执行相同的操作。这是递归步骤:++ combinations(t)
,并使用++
汇总结果。
如果我们从1
开始,那么第一次递归调用是combinations(List(2, 3, 4))
,我们重复逻辑:
case h :: t => t.map(x => (h, x))
// 2 :: List(3, 4) => List((2, 3), (3, 4))
最后:
case h :: t => t.map(x => (h, x))
// 3 :: List(4) => List((3, 4))
所以我们以及List((1, 2), (1, 3), (1, 4)) ++ List((2, 3), (3, 4)) ++ List((3, 4))
当然,存在零个或一个元素的其他情况不能产生更多组合:
case Nil => Nil
case h :: Nil => Nil
正如@ 0__所述,h :: Nil
可以真正由h :: t
处理,因为我们会有:
case h :: t => t.map(x => (h, x)) ++ combinations(t)
// ^ Nil ^ Nil maps to Nil ^ Will hit the first case on the next call, which is also Nil
答案 1 :(得分:1)
使用List
定义功能/递归解决方案时,最简单的方法是覆盖发生的基本情况。然后,您可以定义部分解决方案并将它们添加到一起。
你想要输出中的元素对,所以你首先要找出一个空列表(Nil
)或一个只包含一个元素(h :: Nil
)的列表,它没有部分解决方案,因此这两个案例的结果为Nil
。最后一种情况是你有一个头元素h
和一个非空尾。因此,使用h
函数为每个尾部元素生成所有map
对,并为尾部递归重复。
请注意,从技术上讲,中间案例无关紧要。以下就足够了:
def combinations[A](xs: List[A]): List[(A, A)] = xs match {
case Nil => Nil
case h :: t => t.map(h -> _) ++ combinations(t)
}