出于练习目的,我一直在尝试以功能方式实现几个Scala的List方法,其中一个方法是partition
。假设以下签名:
def partition[T](l: List[T], f: T => Boolean): (List[T], List[T])
它返回一个由两个列表组成的元组 - 第一个包含l
中满足传递的谓词f
的所有元素,另一个包含所有其他元素。
我提出了以下递归解决方案,遗憾的是它不是尾递归的:
def partition[T](l: List[T], f: T => Boolean): (List[T], List[T]) = {
l match {
case Nil => (Nil, Nil)
case head :: rest => {
val (left, right) = partition(rest, f)
if (f(head))
(head :: left, right)
else
(left, head :: right)
}
}
}
在这个堆栈溢出问题(Can all recursive functions be re-written as tail-recursions?)中,清楚的是在某些情况下可以使用累加器。在给定的一个中,我会声称这是不可能的,因为它会以相反的方式返回最终列表。
你能给我一个尾递归解决方案吗?也许即使继续传递(我还没有真正理解它是如何工作的以及如何应用它)?
答案 0 :(得分:1)
您需要保留两个累加器,一个用于left
,另一个用于right
。当您完成输入列表后,只需返回两个累加器(将它们反转以恢复原始顺序):
def partition[T](l: List[T], f: T => Boolean): (List[T], List[T]) = {
@annotation.tailrec
def aux(tl: List[T], left: List[T], right: List[T]): (List[T], List[T]) = tl match {
case Nil => (left.reverse, right.reverse)
case head :: rest => {
if (f(head))
aux(rest, head :: left, right)
else
aux(rest, left, head :: right)
}
}
aux(l, List(), List())
}
使用它:
scala> def partition[T](l: List[T], f: T => Boolean): (List[T], List[T]) = {
| @annotation.tailrec
| def aux(tl: List[T], left: List[T], right: List[T]): (List[T], List[T]) = tl match {
| case Nil => (left.reverse, right.reverse)
| case head :: rest => {
| if (f(head))
| aux(rest, head :: left, right)
| else
| aux(rest, left, head :: right)
| }
| }
|
| aux(l, List(), List())
| }
partition: [T](l: List[T], f: T => Boolean)(List[T], List[T])
scala> partition(List(1, 2, 3, 4, 5), (i: Int) => i%2 == 0)
res1: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5))
答案 1 :(得分:1)
你可以将一个元组作为累加器,并确保在返回之前反转列表:
def partition[T](l: List[T])(f: T => Boolean): (List[T], List[T]) = {
@tailrec
def partitionInternal(l: List[T])(acc: (List[T], List[T])): (List[T], List[T]) = {
l match {
case Nil => acc
case head :: tail =>
if (f(head)) partitionInternal(tail)(head :: acc._1, acc._2)
else partitionInternal(tail)(acc._1, head :: acc._2)
}
}
val (lf, r) = partitionInternal(l)((List.empty[T], List.empty[T]))
(lf.reverse, r.reverse)
}
另一种解决方案是追加(:+
)而不是前置(::
),但是你为每个条目支付O(n)的价格。
另一个想法是使用ListBuffer[T]
代替List[T]
进行内部递归实现,该实现具有恒定时间附加。您需要做的就是在结尾处致电.toList
:
def partition[T](l: List[T])(f: T => Boolean): (List[T], List[T]) = {
@tailrec
def partitionInternal(l: List[T])(acc: (ListBuffer[T], ListBuffer[T])): (ListBuffer[T], ListBuffer[T]) = {
l match {
case Nil => acc
case head :: tail =>
val (leftAcc, rightAcc) = acc
if (f(head)) partitionInternal(tail)((leftAcc += head, rightAcc))
else partitionInternal(tail)((leftAcc, rightAcc += head))
}
}
val (lf, r) = partitionInternal(l)((ListBuffer.empty[T], ListBuffer.empty[T]))
(lf.toList, r.toList)
}
另外,请注意我为List[T]
和T => Boolean
中的函数创建了单独的参数列表。这是为了帮助编译器在应用方法时推断出正确的类型参数,因为类型推断在参数列表之间流动。