Why doesn't my recursive function return the max value of a List

痴心易碎 提交于 2019-12-24 01:37:31

问题


I have the following recursive function in Scala that should return the maximum size integer in the List. Is anyone able to tell me why the largest value is not returned?

  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
  }

Print out statements show me that I've successfully managed to bring back the largest value in the List as the head (which was what I wanted) but the function still returns back the original head in the list. I'm guessing this is because the reference for xs is still referring to the original xs list, problem is I can't override that because it's a val.

Any ideas what I'm doing wrong?


回答1:


I have an answer to your question but first...

This is the most minimal recursive implementation of max I've ever been able to think up:

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 )
} 

(OK, my original version was ever so slightly more minimal but I wrote that in Scheme which doesn't have Option or type safety etc.) It doesn't need an accumulator or a local helper function because it compares the first two items on the list and discards the smaller, a process which - performed recursively - inevitably leaves you with a list of just one element which must be bigger than all the rest.

OK, why your original solution doesn't work... It's quite simple: you do nothing with the return value from the recursive call to max. All you had to do was change the line

max(remaining)

to

largest = max(remaining)

and your function would work. It wouldn't be the prettiest solution, but it would work. As it is, your code looks as if it assumes that changing the value of largest inside the recursive call will change it in the outside context from which it was called. But each new call to max creates a completely new version of largest which only exists inside that new iteration of the function. Your code then throws away the return value from max(remaining) and returns the original value of largest, which hasn't changed.

Another way to solve this would have been to use a local (inner) function after declaring var largest. That would have looked like this:

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
  } 

Generally, though, it is better to have recursive functions be entirely self-contained (that is, not to look at or change external variables but only at their input) and to return a meaningful value.

When writing a recursive solution of this kind, it often helps to think in reverse. Think first about what things are going to look like when you get to the end of the list. What is the exit condition? What will things look like and where will I find the value to return?

If you do this, then the case which you use to exit the recursive function (by returning a simple value rather than making another recursive call) is usually very simple. The other case matches just need to deal with a) invalid input and b) what to do if you are not yet at the end. a) is usually simple and b) can usually be broken down into just a few different situations, each with a simple thing to do before making another recursive call.

If you look at my solution, you'll see that the first case deals with invalid input, the second is my exit condition and the third is "what to do if we're not at the end".

In many other recursive solutions, Nil is the natural end of the recursion.

This is the point at which I (as always) recommend reading The Little Schemer. It teaches you recursion (and basic Scheme) at the same time (both of which are very good things to learn).

It has been pointed out that Scala has some powerful functions which can help you avoid recursion (or hide the messy details of it), but to use them well you really do need to understand how recursion works.




回答2:


You should use the return value of the inner call to max and compare that to the local largest value. Something like the following (removed println just for readability):

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
  }

Bye.




回答3:


The following is a typical way to solve this sort of problem. It uses an inner tail-recursive function that includes an extra "accumulator" value, which in this case will hold the largest value found so far:

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)
}



回答4:


Nevermind I've resolved the issue...

I finally came up with:

  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
  }

Kinda glad we have loops - this is an excessively complicated solution and hard to get your head around in my opinion. I resolved the problem by making sure the recursive call was the last statement in the function either that or xs.head is returned as the result if there isn't a second member in the array.




回答5:


The most concise but also clear version I have ever seen is this:

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)
}

This has been adapted from the solutions to a homework on the Scala official Corusera course: https://github.com/purlin/Coursera-Scala/blob/master/src/example/Lists.scala

but here I use the rich operator max to return the largest of its two operands. This saves having to redefine this function within the def max block.




回答6:


What about this?

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

  }



回答7:


What about this one ?

 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
  }



回答8:


My answer is using recursion is,

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)
  }
}


来源:https://stackoverflow.com/questions/18860221/why-doesnt-my-recursive-function-return-the-max-value-of-a-list

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!