我的组合函数返回一个空列表

时间:2013-06-07 20:22:03

标签: scala functional-programming

我正在研究S-99: Ninety-Nine Scala Problems并且已经陷入问题26。 生成从列表的N个元素中选择的K个不同对象的组合。 在浪费了几个小时后,我决定查看用Haskell编写的解决方案:

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

看起来非常简单,所以我决定翻译成Scala。 (我知道这是作弊。)这是我到目前为止所得到的:

def combinations[T](n: Int, ls: List[T]): List[List[T]] = (n, ls) match {
case (0, _) => List[List[T]]()
case (n, xs) => {
  for {
    y :: xss <- allTails(xs).reverse
    ys <- combinations((n - 1), xss)
  } yield y :: ys
}
} 

我的助手功能:

def allTails[T](ls: List[T]): List[List[T]] = {
ls./:(0, List[List[T]]())((acc, c) => {
  (acc._1 + 1, ls.drop(acc._1) :: acc._2)
})._2 }

allTails(List(0, 1, 2, 3)).reverse              
//> res1: List[List[Int]] = List(List(0, 1, 2, 3), List(1, 2, 3), List(2, 3), List(3))

但是,我的组合返回一个空列表。任何的想法? 其他解释方案也非常受欢迎。感谢

编辑:问题的描述

  

生成从列表的N个元素中选择的K个不同对象的组合。   一个由12人组成的3人委员会可以选择多少种方式?我们都知道有C(12,3)= 220种可能性(C(N,K)表示众所周知的二项式系数)。对于纯数学家来说,这个结果可能很棒。但我们希望真正产生所有可能性。

     

实施例:   阶&GT;组合(3,列表('a,'b,'c,'d,'e,'f))   res0:List [List [Symbol]] = List(List('a,'b,'c),List('a,'b,'d),List('a,'b,'e),..

4 个答案:

答案 0 :(得分:2)

正如诺亚指出的那样,我的问题是for空列表不会产生。然而,诺亚提出的黑客工作是错误的。它为每个递归步骤的结果添加一个空列表。无论如何,这是我的最终解决方案。我将基本案例改为“case(1,xs)”。 (n匹配1)

def combinations[T](n: Int, ls: List[T]): List[List[T]] = (n, ls) match {
case (1, xs) =>  xs.map(List(_))
case (n, xs) => {
  val tails = allTails(xs).reverse
  for {
    y :: xss <- allTails(xs).reverse
    ys <- combinations((n - 1), xss)
  } yield y :: ys
}
}
//combinations(3, List(1, 2, 3, 4))
//List(List(1, 2, 3), List(1, 2, 4), List(1, 3, 4), List(2, 3, 4))
//combinations(2, List(0, 1, 2, 3))
//List(List(0, 1), List(0, 2), List(0, 3), List(1, 2), List(1, 3), List(2, 3))

def allTails[T](ls: List[T]): List[List[T]] = {
ls./:(0, List[List[T]]())((acc, c) => {
  (acc._1 + 1, ls.drop(acc._1) :: acc._2)
})._2
}
//allTails(List(0,1,2,3)) 
//List(List(3), List(2, 3), List(1, 2, 3), List(0, 1, 2, 3))

答案 1 :(得分:2)

在这里翻译Haskell版本时你犯了一个错误:

case (0, _) => List[List[T]]()

这将返回一个空列表。而Haskell版本

combinations 0 _  = [ [] ]

返回一个包含单个元素的列表,该元素是一个空列表。

这实质上是说有一种方法可以选择零项,这很重要,因为在我们选择更多项的情况下,代码会递归地建立在这种情况下。如果没有办法选择零项目,那么也没有办法选择一个项目,依此类推。这就是您的代码中发生的事情。

如果您修复Scala版本与Haskell版本相同:

case (0, _) => List(List[T]())

它按预期工作。

答案 2 :(得分:1)

您的问题是使用for对列表的理解。如果for检测到一个空列表,那么它会短路并返回一个空列表而不是“消耗”你的头元素。这是一个例子:

scala> for { xs <- List() } yield println("It worked!") // This never prints
res0: List[Unit] = List()

因此,对于你的组合函数来说,一种hacky工作方式是:

  def combinations[T](n: Int, ls: List[T]): List[List[T]] = (n, ls) match {
    case (0, _) => List[List[T]]()
    case (n, xs) => {
      val tails = allTails(xs).reverse
      println(tails)
      for {
        y :: xss <- tails
        ys <- Nil :: combinations((n - 1), xss) //Now we're sure to keep evaulating even with an empty list
      } yield y :: ys
    }
  }

scala> combinations(2, List(1, 2, 3))
List(List(1, 2, 3), List(2, 3), List(3))
List(List(2, 3), List(3))
List(List(3))
List()
res5: List[List[Int]] = List(List(1), List(1, 2), List(1, 3), List(2), List(2, 3), List(3))

答案 3 :(得分:0)

解决它的另一种方法。

def combinations[T](n: Int, ls: List[T]): List[List[T]] = {
var ms: List[List[T]] = List[List[T]]();
val len = ls.size
if (n > len)
  throw new Error();
else if (n == len)
  List(ls)
else if (n == 1)
  ls map (a => List(a))
else {
  for (i <- n to len) {
    val take: List[T] = ls take i;
    val temp = combinations(n - 1, take.init) map (a => take.last :: a)
    ms = ms ::: temp
  }
  ms
}
}

所以combinations(2, List(1, 2, 3))给出:List[List[Int]] = List(List(2, 1), List(3, 1), List(3, 2))