我有你的问题的答案,但首先...
这是max
我能想到的最小的递归实现:
def max(xs: List[Int]): Option[Int] = xs match {
case Nil => None
case List(x: Int) => Some(x)
case x :: y :: rest => max( (if (x > y) x else y) :: rest )
}
(好吧,我的原始版本稍微小了一点,但我在没有选项或类型安全等的 Scheme 中写了它。)它不需要累加器或本地辅助函数,因为它比较前两项列表并丢弃较小的,这个过程 - 递归执行 - 不可避免地会给您留下一个只有一个元素的列表,该列表必须比所有其他元素都大。
好的,为什么你的原始解决方案不起作用......这很简单:你对递归调用的返回值不做任何事情max
。 你所要做的就是改变线路
max(remaining)
至
largest = max(remaining)
你的功能会起作用。这不是最漂亮的解决方案,但它会起作用。实际上,您的代码看起来好像假设更改largest
递归调用内部的值将在调用它的外部上下文中更改它。但是每个新的调用max
都会创建一个全新的版本,largest
该版本只存在于函数的新迭代中。然后,您的代码会丢弃返回值max(remaining)
并返回 的原始值largest
,该值没有改变。
解决此问题的另一种方法是在声明var largest
. 那看起来像这样:
def max(xs: List[Int]): Int = {
var largest = xs.head
def loop(ys: List[Int]) {
if (!ys.isEmpty) {
var next = ys.head
largest = if (largest > next) largest else next
loop(ys.tail)
}
}
loop(xs.tail)
return largest
}
不过,一般来说,最好让递归函数完全自包含(即,不查看或更改外部变量,而只查看它们的输入)并返回一个有意义的值。
在编写此类递归解决方案时,逆向思考通常会有所帮助。首先想想当你到达列表末尾时会是什么样子。退出条件是什么?事情会是什么样子,我在哪里可以找到要返回的值?
如果你这样做,那么case
你用来退出递归函数(通过返回一个简单的值而不是进行另一个递归调用)通常非常简单。另一个case
匹配只需要处理 a) 无效输入和 b) 如果您还没有到最后该怎么办。a)通常很简单,b)通常可以分解为几种不同的情况,在进行另一个递归调用之前,每种情况都有一个简单的事情要做。
如果您查看我的解决方案,您会发现第一个case
处理无效输入,第二个是我的退出条件,第三个是“如果我们不在最后该怎么办”。
在许多其他递归解决方案中,递归Nil
是自然结束的。
这就是我(一如既往)推荐阅读The Little Schemer 的地方。它同时教您递归(和基本方案)(两者都是非常好的学习内容)。
有人指出,Scala 有一些强大的功能可以帮助您避免递归(或隐藏它的混乱细节),但要很好地使用它们,您确实需要了解递归的工作原理。