为什么我的递归函数不返回List的最大值

时间:2013-09-17 21:29:21

标签: scala recursion

我在Scala中有以下递归函数,它应返回List中的最大大小整数。有人能够告诉我为什么没有返回最大值吗?

  def max(xs: List[Int]): Int = {
    var largest = xs.head
    println("largest: " + largest)
    if (!xs.tail.isEmpty) {
      var next = xs.tail.head
      println("next: " + next)
      largest = if (largest > next) largest else next
      var remaining = List[Int]()
      remaining = largest :: xs.tail.tail
      println("remaining: " + remaining)
      max(remaining)
    }
    return largest
  }

打印语句告诉我,我已经成功地将列表中的最大值作为头部(这是我想要的)恢复,但该功能仍然返回列表中的原始头部。我猜这是因为xs的引用仍然是指原始的xs列表,问题是我无法覆盖它,因为它是一个val。

任何想法我做错了什么?

8 个答案:

答案 0 :(得分:3)

您应该使用内部调用的返回值max,并将其与本地最大值进行比较。 类似于以下内容(为了便于阅读,删除了println):

def max(xs: List[Int]): Int = {
    var largest = xs.head
    if (!xs.tail.isEmpty) {
      var remaining = List[Int]()
      remaining = largest :: xs.tail
      var next = max(remaining)
      largest = if (largest > next) largest else next
    }
    return largest
  }

再见。

答案 1 :(得分:2)

以下是解决此类问题的典型方法。它使用内部尾递归函数,该函数包含一个额外的“累加器”值,在这种情况下,它将保存到目前为止找到的最大值:

def max(xs: List[Int]): Int = {
  def go(xs: List[Int], acc: Int): Int = xs match {
    case Nil => acc // We've emptied the list, so just return the final result
    case x :: rest => if (acc > x) go(rest, acc) else go(rest, x) // Keep going, with remaining list and updated largest-value-so-far
  }

  go(xs, Int.MinValue)
}

答案 2 :(得分:2)

我对你的问题有一个答案,但首先......

这是我曾经能够想到的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中写了没有Option或类型安全等等。)它不需要累加器或本地助手函数,因为它比较了第一个列表中的两个项目丢弃较小的一个进程 - 一个递归执行的进程 - 不可避免地会给你一个只有一个元素的列表,它必须比其他元素都大。

好的,为什么你的原始解决方案不起作用......这很简单:你对递归调用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有一些强大的功能,可以帮助你避免递归(或隐藏它的混乱细节),但要好好使用它们,你真的需要了解递归是如何工作的。

答案 3 :(得分:0)

没关系我已经解决了这个问题...

我终于想到了:

  def max(xs: List[Int]): Int = {
    var largest = 0
    var remaining = List[Int]()
    if (!xs.isEmpty) {
      largest = xs.head
      if (!xs.tail.isEmpty) {
        var next = xs.tail.head
        largest = if (largest > next) largest else next
        remaining = largest :: xs.tail.tail
      }
    }
    if (!remaining.tail.isEmpty) max(remaining) else xs.head
  }

有点高兴我们有循环 - 这是一个过于复杂的解决方案,在我看来很难理解。我解决了这个问题,确保递归调用是函数中的最后一个语句,如果数组中没有第二个成员,则返回xs.head作为结果。

答案 4 :(得分:0)

我见过的最简洁但也很清晰的版本是:

def max(xs: List[Int]): Int = {
    def maxIter(a: Int, xs: List[Int]): Int = {
        if (xs.isEmpty) a
        else a max maxIter(xs.head, xs.tail)
    }
    maxIter(xs.head, xs.tail)
}

这已经从Scala官方Corusera课程的作业解决方案改编而来:https://github.com/purlin/Coursera-Scala/blob/master/src/example/Lists.scala

但是在这里我使用富运算符max来返回其两个操作数中最大的一个。这节省了必须在def max块中重新定义此函数。

答案 5 :(得分:0)

这个怎么样?

def max(xs: List[Int]): Int = {
    maxRecursive(xs, 0)
  }

  def maxRecursive(xs: List[Int], max: Int): Int = {
    if(xs.head > max && ! xs.isEmpty)
       maxRecursive(xs.tail, xs.head)
    else
      max

  }

答案 6 :(得分:0)

这个怎么样?

 def max(xs: List[Int]): Int = {
    var largest = xs.head
    if( !xs.tail.isEmpty ) {
      if(xs.head < max(xs.tail)) largest = max(xs.tail)     
    }  
    largest
  }

答案 7 :(得分:0)

我的回答是使用递归,

def max(xs: List[Int]): Int =
  xs match {
    case Nil => throw new NoSuchElementException("empty list is not allowed")
    case head :: Nil => head
    case head :: tail =>
      if (head >= tail.head)
        if (tail.length > 1)
            max(head :: tail.tail)
        else
          head
      else
        max(tail)
  }
}